/*
 * 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.
 */

package std.core

@When[backend == "cjnative"]
@Intrinsic
func isThreadObjectInited(): Bool

@When[backend == "cjnative"]
@Intrinsic
func getThreadObject(): Thread

@When[backend == "cjnative"]
@Intrinsic
func dumpAllThreadsInfo(): RawArray<ThreadSnapshotInfo>

@When[backend == "cjnative"]
@Intrinsic
func dumpCurrentThreadInfo(): ThreadSnapshotInfo

@FastNative
foreign func CJ_MRT_SetCJThreadName(handle: CPointer<Unit>, name: CPointer<UInt8>, len: UIntNative): Unit

@FastNative
foreign func CJ_MRT_GetCJThreadId(handle: CPointer<Unit>): Int64

@FastNative
foreign func CJ_MRT_GetCurrentCJThread(): CPointer<Unit>

@FastNative
foreign func CJ_MRT_GetCJThreadState(handle: CPointer<Unit>): Int64

const INVALID_ID: Int64 = -1

/**
 * Enum representing the possible states of a thread.
 * Implements ToString for human-readable representation.
 */
public enum ThreadState <: ToString {
    Ready | Running | Pending | Terminated | ...

    /**
     * Converts the enum value to its string representation
     * @return String representation of the thread state
     */
    public func toString(): String {
        match (this) {
            case Ready => "Ready"
            case Running => "Running"
            case Pending => "Pending"
            case Terminated => "Terminated"
            case _ => "Unknown"
        }
    }

    /**
     * Parses an integer value into a ThreadState enum.
     * @param value Integer value representing the thread state
     * @return Corresponding ThreadState enum value
     * @throws IllegalArgumentException if the value is not supported
     */
    static func parse(value: Int64): ThreadState {
        match (value) {
            case 0 => Terminated
            case 1 => Ready
            case 2 => Running
            case 3 => Pending
            case _ => throw IllegalArgumentException("The type is not supported.")
        }
    }
}

/**
 * Structure containing information about a thread snapshot.
 * Used for debugging and monitoring thread states.
 */
struct ThreadSnapshotInfo {
    let name: RawArray<UInt8> = EMPTY_UINT8_RAW_ARRAY
    let id: Int64 = 0
    let stackTrace: RawArray<StackTraceData> = RawArray<StackTraceData>()
    let state: Int64 = 0
}

/**
 * Class representing a snapshot of a thread's state at a point in time.
 * Provides functionality for retrieving and printing thread information.
 */
public class ThreadSnapshot <: ToString {
    public let id: Int64
    public let name: String
    public let stackTrace: Array<StackTraceElement>
    public let state: ThreadState

    /**
     * Constructor for ThreadSnapshot.
     * @param id Thread identifier
     * @param name Thread name
     * @param state Current thread state
     * @param stackTrace Stack trace elements
     */
    init(id: Int64, name: String, state: ThreadState, stackTrace: Array<StackTraceElement>) {
        this.id = id
        this.name = name
        this.state = state
        this.stackTrace = stackTrace
    }

    /**
     * Converts the thread snapshot information to a string representation
     * 
     * @return Returns a formatted string containing thread ID, name, state and stack trace information
     */
    public func toString(): String {
        var stringBuffer = "ThreadSnapshot(id=${id}, name=${name}, state=${state})\n"
        stringBuffer = stringBuffer + "stack trace:\n"
        for (element in stackTrace) {
            if (element.declaringClass.isEmpty() && element.methodName.isEmpty()) { // simple stack trace format
                stringBuffer = stringBuffer + "\t at ${element.fileName}:${element.lineNumber}\n"
            } else {
                let packageDelimiter: String = if (element.declaringClass.isEmpty()) { "" } else { "." }
                stringBuffer = stringBuffer +
                    "\t at ${element.declaringClass}${packageDelimiter}${element.methodName}(${element.fileName}:${element.lineNumber})\n"
            }
        }
        return stringBuffer
    }
    
    /**
     * Parses ThreadSnapshotInfo into a ThreadSnapshot object.
     * @param info ThreadSnapshotInfo to parse
     * @return ThreadSnapshot object
     */
    private static func parseThreadInfo(info: ThreadSnapshotInfo): ThreadSnapshot {
        let infoSize = arraySize(info.stackTrace)
        let emptyStackTrace = StackTraceElement("", "", "", 0)
        let stackTraceArray = Array<StackTraceElement>(infoSize, repeat: emptyStackTrace)
        var eleSize = 0
        for (i in 0..infoSize) {
            let data = arrayGet(info.stackTrace, i)
            if (data.lineNumber == 0 || data.lineNumber == -1 || arraySize(data.fileName) == 0) {
                continue
            }
            let stkElem = StackTraceElement(
                String(data.className),
                String(data.methodName),
                String(data.fileName),
                data.lineNumber
            )
            stackTraceArray[eleSize] = stkElem
            eleSize++
        }
        return ThreadSnapshot(info.id, String(info.name), ThreadState.parse(info.state), stackTraceArray.clone(0..eleSize))
    }

    /**
     * Retrieves snapshots of all currently active threads.
     * @return Array of ThreadSnapshot objects
     */
    public static func dumpAllThreads(): Array<ThreadSnapshot> {
        let dumpInfo: RawArray<ThreadSnapshotInfo> = dumpAllThreadsInfo()
        let threadNum = arraySize(dumpInfo)
        let emptySnapshot = ThreadSnapshot(0, "", Terminated, [])
        let allThreadArray = Array<ThreadSnapshot>(threadNum, repeat: emptySnapshot)
        for (i in 0..threadNum) {
            allThreadArray[i] = parseThreadInfo(arrayGet(dumpInfo, i))
        }
        return allThreadArray
    }

    /**
     * Retrieves a snapshot of the current thread.
     * @return ThreadSnapshot of the current thread
     */
    public static func dumpCurrentThread(): ThreadSnapshot {
        let dumpInfo: ThreadSnapshotInfo = dumpCurrentThreadInfo()
        return parseThreadInfo(dumpInfo)
    }

}

public class Thread {
    static let exceptionHandler = AtomicBox<Option<(Thread, Exception) -> Unit>>(None)
    static let errorHandler = AtomicBox<Option<(Error) -> Unit>>(None)

    private let _name = AtomicBox<String>("")
    private let _hasCancellation = AtomicBool(false)
    private let _id = AtomicInt64(INVALID_ID)
    var _threadLocalMap: Option<ThreadLocalMap> = None
    // The `_rtCJThreadHandle` is a pointer to the underlying CJ thread,
    // when the CJ thread terminates, it will reclaim the memory pointered to by this pointer.
    // So, we should avoid the concurrent "use-after-free" problem.
    // The problem may occur when a thread access the pointer via a future object.
    // ---------------------------------------------------
    //   Thread 1                            Target thread
    // future.thread.name = "..."         Terminates and frees memory
    // ---------------------------------------------------
    // The solution is that we manage "reference counts" of the pointer,
    // and make sure freeing the memory only after there is no reference counts.
    // Note that the concurrent access problem occurs only when spawning a thread with a future object.
    private var _rtCJThreadHandle = CPointer<Unit>()
    // When the count == -1, it means the target thread has been terminated.
    private let _rtCJThreadHandleRefCount = AtomicInt64(0)

    // Should only be called within core package
    init() {}

    /**
     * Set the CJThread handle and get the CJThread id.
     * Note that this method will be imported implicitly and called when creating a thread with a future.
     */
    protected func setRuntimeCJThreadHandle(handle: CPointer<Unit>): Unit {
        _rtCJThreadHandle = handle
        let cjThreadId = unsafe { CJ_MRT_GetCJThreadId(handle) }
        _id.store(cjThreadId)
    }

    /**
     * Wait until CJThread handle is set properly.
     * This method will be called by the entry function (i.e., `Future.execute`)
     * when creating a thread with a future to avoid a potential data race,
     * where the new thread may exit and destroy CJThreadHandle before setting it.
     *         T1                                      T2
     *  let fut = Future();
     *  CJ_MRT_RuntimeNewTask(Future.execute)
     *  fut.thread.setRuntimeCJThreadHandle()
     *                                          waitForCJThreadHandle()
     *                                          ...
     *                                          exit
     */
    func waitForCJThreadHandle(): Unit {
        while (_id.load() == INVALID_ID) {
            // sleep(0) is equivalent to yielding the current thread
            intrinsicSleep(0)
        }
    }

    /**
     * Called by the thread entry function `Future.execute`.
     * Wait until the reference count == 0, and mark it as terminated.
     */
    func clearRuntimeCJThreadHandle(): Unit {
        while (true) {
            let old = _rtCJThreadHandleRefCount.load()
            if (old == 0 && _rtCJThreadHandleRefCount.compareAndSwap(0, -1)) {
                _rtCJThreadHandle = CPointer<Unit>()
                return
            }
        }
    }

    /**
     * If the thread is running,
     * hold the thread handle and increase the count to keep it available.
     */
    private func acquireCJThreadHandle(): CPointer<Unit> {
        while (true) {
            let old = _rtCJThreadHandleRefCount.load()
            if (old == -1) { // `-1` means the thread has been terminated
                return CPointer<Unit>()
            } else if (_rtCJThreadHandleRefCount.compareAndSwap(old, old + 1)) {
                // `_rtCJThreadHandle` is always not null
                if (_rtCJThreadHandle.isNull()) {
                    throw Error("Invalid CJThread handle")
                }
                return _rtCJThreadHandle
            }
        }
        throw Error("Never reached")
    }

    private func releaseCJThreadHandle(): Unit {
        _rtCJThreadHandleRefCount.fetchAdd(-1)
    }

    // If a Cangjie thread is created without a Future object,
    // its Thread object will be lazy-constructed.
    public static prop currentThread: Thread {
        get() {
            if (isThreadObjectInited()) {
                return getThreadObject()
            } else {
                let thread = Thread()
                // We should also set the runtime CJThread handle here
                let rtCJThreadHandle = unsafe { CJ_MRT_GetCurrentCJThread() }
                // `rtCJThreadHandle` is always not null.
                if (rtCJThreadHandle.isNull()) {
                    throw Error("Invalid CJThread handle")
                }
                thread.setRuntimeCJThreadHandle(rtCJThreadHandle)
                setThreadObject(thread)
                thread
            }
        }
    }

    public prop id: Int64 {
        get() {
            var cjThreadId = _id.load()
            // Since we assign `_rtCJThreadHandle` and `_id` at the same time,
            // `_id` is always invalid.
            if (cjThreadId == INVALID_ID) {
                throw Error("Invalid thread id")
            }
            return cjThreadId
        }
    }

    public mut prop name: String {
        get() {
            _name.load()
        }
        set(newName) {
            _name.store(newName)
            // Assign the name to the underlying CJ thread if possible.
            let handle = unsafe { acquireArrayRawData(newName.rawData()) }
            let rtCJThreadHandle = acquireCJThreadHandle()
            if (!rtCJThreadHandle.isNull()) {
                unsafe {
                    CJ_MRT_SetCJThreadName(rtCJThreadHandle, handle.pointer, UIntNative(newName.size))
                }
                releaseCJThreadHandle()
            }
            unsafe { releaseArrayRawData(handle) }
        }
    }

    /**
     * Check whether the current Cangjie thread has termination requests,
     * i.e., whether its future has been marked as cancelled.
     */
    public prop hasPendingCancellation: Bool {
        get() {
            _hasCancellation.load()
        }
    }

    func setCancellationRequest(): Unit {
        this._hasCancellation.store(true)
    }

    public static func handleUncaughtExceptionBy(exHandler: (Thread, Exception) -> Unit): Unit {
        Thread.exceptionHandler.store(exHandler)
    }

    @When[env != "ohos"]
    public static func handleUncaughtErrorBy(erHandler: (Error) -> Unit): Unit {
        Thread.errorHandler.store(erHandler)
    }
    
    /**
     * Get thread state
     * 
     * Returns the current thread's state by querying the underlying CJ thread.
     * Returns Terminated if thread handle is invalid.
     * 
     * @return ThreadState Current thread state
     */
    public prop state: ThreadState {
        get() {
            let handle = acquireCJThreadHandle()
            if (handle.isNull()) {
                return ThreadState.Terminated
            }
            let state = unsafe { CJ_MRT_GetCJThreadState(handle) }
            releaseCJThreadHandle()

            return ThreadState.parse(state)
        }
    }
}