/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

package std.unittest

import std.sync.ReentrantMutex
import std.random.Random
import std.fs.*
import std.env.getTempDirectory

// std.fs.File.createTempt can only create up to 26 files
// so we have this to workaround it
class TempDirectory {
    private static const ID_SIZE: Int64 = 16
    private static const MAX_ATTEMPTS: Int64 = 100
    private static const BUFFER_SIZE: Int64 = 100
    private static let VOCAB = "abcdefghijklmnopqrstuvwxyz0123456789".toRuneArray()

    private let lock = ReentrantMutex()
    private let rnd = Random()
    private let buffer = Array<Rune>(ID_SIZE * BUFFER_SIZE, repeat: r'0')
    private var currentOffset = buffer.size
    private var tempDir: ?Path = None

    prop path: Path {
        get() {
            synchronized(lock) {
                match (tempDir) {
                    case Some(dir) => return dir
                    case None =>
                        let dir = getDir()
                        tempDir = dir
                        return dir
                }
            }
        }
    }

    private static func getDir(): Path {
        try {
            let dir = getTempDirectory()
            if (exists(dir)) {
                return dir
            }
        } catch (cause: Exception) {
            eprintln("Failed to find the temp directory: ${cause}")
        }

        return Path(".")
    }

    private func nextId(): Array<Rune> {
        func fillBuffer() {
            for (index in 0..buffer.size) {
                buffer[index] = VOCAB[rnd.nextInt64(VOCAB.size)]
            }
            currentOffset = 0
        }

        synchronized(lock) {
            if (currentOffset >= buffer.size) {
                fillBuffer()
            }

            let startOffset = currentOffset
            currentOffset += ID_SIZE
            buffer[startOffset..startOffset + ID_SIZE].clone()
        }
    }

    private func nextId(prefix: String, suffix: String): String {
        let sb = StringBuilder()
        sb.append(prefix)
        sb.append(nextId())
        sb.append(suffix)
        return sb.toString()
    }

    func createTempFile(dir!: Path = path, prefix!: String = "tempFile", suffix!: String = ".tmp"): File {
        for (_ in 0..MAX_ATTEMPTS) {
            let path = createTempPath(dir: dir, prefix: prefix, suffix: suffix)
            try {
                return File.create(path) // File.create is not atomic, may clash
            } catch (_) {
            }
        }
        throw Exception("Too many attempts to create a temporary file")
    }

    func createTempPath(dir!: Path = path, prefix!: String = "tempFile", suffix!: String = ".tmp"): Path {
        for (_ in 0..MAX_ATTEMPTS) {
            let path = dir.join(nextId(prefix, suffix))
            if (!exists(path)) {
                return path
            }
        }
        throw Exception("Too many attempts to create a temporary file")
    }
}