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

@When[backend == "cjnative"]
import {std.io.*, std.process.*, std.fs.*, std.env.*, std.sync.Mutex}

const TRACE_TYPE_SCHEDULE_VALUE: UInt16 = 0x0100
const TRACE_TYPE_RUNTIME_VALUE: UInt16 = 0x0200
const TRACE_TYPE_ALL_VALUE: UInt16 = 0xff00

/*
 * TraceType  is used to enable trace for collecting different types of event sets.
 */
@When[backend == "cjnative" && (os == "Windows" || os == "Linux")]
struct TraceType {
    var data: UInt16

    private init(traceType: UInt16) {
        this.data = traceType
    }

    public static let schedule: TraceType = TraceType(TRACE_TYPE_SCHEDULE_VALUE)
    public static let runtime: TraceType = TraceType(TRACE_TYPE_RUNTIME_VALUE)
    public static let all: TraceType = TraceType(TRACE_TYPE_ALL_VALUE)
    public operator func |(traceType: TraceType): TraceType {
        TraceType(this.data | traceType.data)
    }
}

@When[backend == "cjnative" && (os == "Windows" || os == "Linux")]
foreign {
    func CJ_ScheduleStartTrace(traceOpenType: UInt16): Bool

    func CJ_ScheduleStopTrace(): Bool

    func CJ_ScheduleDumpTrace(len: CPointer<Int32>): CPointer<UInt8>
}

@When[backend == "cjnative" && (os == "Windows" || os == "Linux")]
struct Trace {
    private static let traceLock: Mutex = Mutex()
    private init() {}

    /**
     * function for dump trace.
     */
    public unsafe static func dump(out: OutputStream): Unit {
        let lenPtr = LibC.malloc<Int32>(count: 1)
        if (lenPtr.isNull()) {
            throw IllegalMemoryException("Failed to malloc memory for len.")
        }

        try {
            while (true) {
                let dataPtr = CJ_ScheduleDumpTrace(lenPtr)
                if (dataPtr.isNull()) {
                    break
                }
                var data = Array<UInt8>(Int64(lenPtr.read()), {i => dataPtr.read(i)})
                out.write(data)
            }
        } finally {
            LibC.free(lenPtr)
        }
    }

    /**
     * function for start trace.
     */
    public static func start(traceOpenType: TraceType, out: OutputStream): Bool {
        synchronized(traceLock) {
            let result = unsafe { CJ_ScheduleStartTrace(traceOpenType.data) }
            if (!result) {
                return false
            }
            spawn {
                unsafe { dump(out) }
            }
            return true
        }
    }

    /**
     * function for stop trace.
     */
    public static func stop(): Bool {
        synchronized(traceLock) {
            unsafe { return CJ_ScheduleStopTrace() }
        }
    }
}

@When[backend == "cjnative" && (os == "Windows" || os == "Linux")]
func traceStartByConfig(): Bool {
    let cjTracePath = if (let Some(env) <- getVariable("cjTracePath")) {
        env
    } else {
        return false
    }

    if (!validatePathString(cjTracePath)) {
        return false
    }

    try {
        let resolvedPath = resolvePath(Path(cjTracePath))
        let file = File(resolvedPath, Write)
        return Trace.start(TraceType.all, file)
    } catch (e: Exception) {
        return false
    }
}

@When[backend == "cjnative" && (os == "Windows" || os == "Linux")]
var g_traceStartByConfigFlag: Bool = traceStartByConfig()

@When[backend == "cjnative" && (os == "Windows" || os == "Linux")]
func traceStopByConfig(): Unit {
    if (g_traceStartByConfigFlag) {
        Trace.stop()
    }
}

@When[backend == "cjnative" && (os == "Windows" || os == "Linux")]
var _ = atExit(traceStopByConfig)