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

@C
struct ProcessInfo {
    let command: CString = CString(CPointer())
    let commandLine: CPointer<CString> = CPointer<CString>()
    let arguments: CPointer<CString> = CPointer<CString>()
    let workingDirectory: CString = CString(CPointer())
    let environment: CPointer<CString> = CPointer<CString>()
}

foreign {
    // Process
    func CJ_OS_GetProcessHandle(pid: Int32): IntNative

    func CJ_OS_CloseProcessHandle(handle: IntNative): Unit

    func CJ_OS_GetProcessInfoByPid(pid: Int32): CPointer<ProcessInfo>

    func CJ_OS_Terminate(pid: Int32, force: Bool): Int32

    func CJ_OS_GetCurrentPid(): Int32

    func CJ_OS_FreeProcessInfo(processInfo_cp: CPointer<ProcessInfo>): Unit

    func CJ_OS_FreeProcessRtnData(processRtnData_cp: CPointer<ProcessRtnData>): Unit

    func CJ_OS_CreateProcessStartInfo(): CPointer<ProcessStartInfo>

    func CJ_OS_FreeProcessStartInfo(startInfo: CPointer<ProcessStartInfo>): Unit

    func CJ_OS_GetStartTimeFromUnixEpoch(pid: Int32): Int64

    func CJ_OS_GetUserTime(pid: Int32): Int64

    func CJ_OS_GetSystemTime(pid: Int32): Int64

    func CJ_OS_GetProcessAliveStatus(pid: Int32, lastStartTime: Int64): Int8

    /*
     * terminate the calling Process
     * Before exiting the Process, invoke all functions registered through the atexit and on_exit functions.
     * Flush and fend off all standard io streams that are still open;
     * Delete all files created by using the tmpfile function
     *
     * @param Normal end status value
     *
     * manual: https://man7.org/linux/man-pages/man2/exit.2.html
     * signature: void exit(int status);
     */
    @FastNative
    func exit(status: Int32): Unit
}

@When[backend == "cjnative"]
foreign func FiniCJRuntime(): Int32

@When[os != "Windows"]
foreign {
    // Process
    func CJ_OS_StartProcess(startInfo: CPointer<ProcessStartInfo>): CPointer<ProcessRtnData>

    func CJ_OS_WaitSubProcessExit(pid: Int32): Int64

    // File
    func CJ_OS_CloseFile(fd: IntNative): Int64 // -1: failed, (>= 0): success

    func CJ_OS_FileRead(fd: IntNative, buffer: CPointer<Byte>, maxLen: UIntNative): Int64 // -1: failed, 0: end, (>0): the size of buffer be read

    func CJ_OS_FileWrite(fd: IntNative, buffer: CPointer<Byte>, maxLen: UIntNative): Bool // -1: failed, (>=0): the size of buffer be written

    func getenv(name: CString): CString

    func setenv(name: CString, value: CString, overwrite: Int32): Int32

    func unsetenv(name: CString): Int32
}

@When[os != "Windows" && os != "macOS" && os != "iOS"]
foreign func CJ_OS_GetStartTimeFromBoot(pid: Int32): Int64

@When[os != "Windows"]
@C
struct ProcessStartInfo {
    let _command: CString
    let _arguments: CPointer<CString>
    let _arguments_size: UIntNative
    let _workingDirectory: CString
    let _environment: CPointer<CString>
    let _environment_size: UIntNative
    let _stdIn: IntNative
    let _stdOut: IntNative
    let _stdErr: IntNative

    init(command: CString, arguments: CPointer<CString>, arguments_size: UIntNative, workingDirectory: CString,
        environment: CPointer<CString>, environment_size: UIntNative, stdIn: IntNative, stdOut: IntNative,
        stdErr: IntNative) {
        this._command = command
        this._arguments = arguments
        this._arguments_size = arguments_size
        this._workingDirectory = workingDirectory
        this._environment = environment
        this._environment_size = environment_size
        this._stdIn = stdIn
        this._stdOut = stdOut
        this._stdErr = stdErr
    }
}

@When[os != "Windows"]
@C
struct ProcessRtnData {
    let pid: Int32 = -1
    let stdInHandle: IntNative = INVALID_HANDLE
    let stdOutHandle: IntNative = INVALID_HANDLE
    let stdErrHandle: IntNative = INVALID_HANDLE
    let errCode: Int32 = -1
    let errMessage: CString = CString(CPointer<UInt8>())
}

@When[os == "Windows"]
foreign {
    // Process
    func CJ_OS_StartProcess(startInfo: CPointer<ProcessStartInfo>): CPointer<ProcessRtnData>

    func CJ_OS_WaitSubProcessExit(handle: IntNative): CPointer<ExitInfo>
    // Directory
    func CJ_OS_GetSystemDirectory(): CString

    func CJ_OS_GetWindowsDirectory(): CString
    // File
    func CJ_OS_CloseFile(fd: IntNative): Int64 // -1: failed, (>= 0): success

    func CJ_OS_FileRead(fd: IntNative, buffer: CPointer<Byte>, maxLen: UIntNative): Int64 // -1: failed, 0: end, (>0): the size of buffer be read

    func CJ_OS_FileWrite(fd: IntNative, buffer: CPointer<Byte>, maxLen: UIntNative): Bool // -1: failed, (>=0): the size of buffer be written

    func CJ_OS_GetStdHandle(fd: IntNative): IntNative

    func CJ_OS_OpenFile(): IntNative

    func CJ_OS_GetNulFileHandle(): IntNative

    func CJ_OS_GetEnvVal(envName: CString): CString

    func CJ_OS_SetEnvEntry(envEntry: CString): Int32
}

@When[os == "Windows"]
@C
struct ProcessStartInfo {
    let _programName: CString
    let _commandLine: CString
    let _workingDirectory: CString
    let _environment: CString
    let _stdIn: IntNative
    let _stdOut: IntNative
    let _stdErr: IntNative

    init(programName: CString, commandLine: CString, workingDirectory: CString, environment: CString, stdIn: IntNative,
        stdOut: IntNative, stdErr: IntNative) {
        this._programName = programName
        this._commandLine = commandLine
        this._workingDirectory = workingDirectory
        this._environment = environment
        this._stdIn = stdIn
        this._stdOut = stdOut
        this._stdErr = stdErr
    }
}

@When[os == "Windows"]
@C
struct ProcessRtnData {
    let pid: Int32 = -1
    let handle: IntNative = INVALID_HANDLE
    let stdInHandle: IntNative = INVALID_HANDLE
    let stdOutHandle: IntNative = INVALID_HANDLE
    let stdErrHandle: IntNative = INVALID_HANDLE
    let errCode: Int32 = 0
    let errMessage: CString = CString(CPointer<UInt8>())
}

@When[os == "Windows"]
@C
struct ExitInfo {
    let exitCode: Int64 = -1
    let error: Bool = false
}

foreign func CJ_OS_HomeDir(): CString

@When[os == "Windows"]
func nativeGetEnv(name: CString): Option<String> {
    var res: CString = unsafe { CJ_OS_GetEnvVal(name) }
    if (res.isNull()) {
        return None
    }
    try {
        return res.toString()
    } finally {
        unsafe { LibC.free(res) }
    }
}

@When[os != "Windows"]
func nativeGetEnv(name: CString): Option<String> {
    if (name.isNull()) {
        return None
    }

    var res: CString = unsafe { getenv(name) }
    if (res.isNull()) {
        return None
    } else {
        var resStr: String = res.toString()
        return resStr
    }
}

@When[os != "Windows"]
func nativeSetEnv(name: CString, value: CString, overwrite: Int32): Int32 {
    return unsafe { setenv(name, value, overwrite) }
}

@When[os == "Windows"]
func nativeSetEnv(name: CString, value: CString, _: Int32): Int32 {
    var envstr: CString = unsafe { LibC.mallocCString(name.toString() + "=" + value.toString()) }
    let ret = unsafe { CJ_OS_SetEnvEntry(envstr) }
    unsafe { LibC.free(envstr) }
    return ret
}

@When[os != "Windows"]
func nativeRemoveEnv(name: CString): Int32 {
    return unsafe { unsetenv(name) }
}

@When[os == "Windows"]
func nativeRemoveEnv(name: CString): Int32 {
    var envstr: CString = unsafe { LibC.mallocCString(name.toString() + "=") }
    let ret = unsafe { CJ_OS_SetEnvEntry(envstr) }
    unsafe { LibC.free(envstr) }
    return ret
}

@When[backend == "cjnative"]
@FastNative
foreign func CJ_MRT_GetCommandLineArgs(): CPointer<CPointer<UInt8>>

@FastNative
foreign func strlen(str: CPointer<UInt8>): UIntNative