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

import std.collection.*
import std.fs.*
import std.io.*
import std.sync.*

const STDIN_FD: IntNative = 0
const STDOUT_FD: IntNative = 1
const STDERR_FD: IntNative = 2
var nulFileCache: Option<IntNative> = None
var nulFileObject: ?File = None
let nulFileMutex = Mutex()

@When[os == "Windows"]
const INVALID_HANDLE: IntNative = 0

@When[os == "Windows"]
func isHandleValid(fileHandle: IntNative): Bool {
    return fileHandle != -1
}

@When[os == "Windows"]
func getStdHandle(fd: IntNative): IntNative {
    return unsafe { CJ_OS_GetStdHandle(fd) }
}

@When[os == "Windows"]
func getStdInHandle(): IntNative {
    return getStdHandle(STDIN_FD)
}

@When[os == "Windows"]
func getStdOutHandle(): IntNative {
    return getStdHandle(STDOUT_FD)
}

@When[os == "Windows"]
func getStdErrHandle(): IntNative {
    return getStdHandle(STDERR_FD)
}

@When[os == "Windows"]
func getNulFileHandle(): Option<IntNative> {
    if (let Some(handle) <- nulFileCache) {
        return handle
    }
    synchronized (nulFileMutex) {
        if (let Some(handle) <- nulFileCache) {
            return handle
        }
        let handle = unsafe { CJ_OS_GetNulFileHandle() }
        if (handle == 0) {
            return Option.None
        }
        nulFileCache = handle
        return handle
    }
}

@When[os == "Windows"]
func getNameFromCommand(cmd: ?String): Option<String> {
    let name: ?String = match (cmd) {
        case Some(v) =>
            if (v.contains("/")) {
                let arr = v.split("/", removeEmpty: true)
                Some(arr[arr.size - 1])
            } else if (v.contains("\\")) {
                let arr = v.split("\\", removeEmpty: true)
                Some(arr[arr.size - 1])
            } else {
                Some(v)
            }
        case _ => None
    }
    return name
}

@When[os != "Windows"]
const INVALID_HANDLE: IntNative = -1

@When[os != "Windows"]
func getNulFileHandle(): Option<IntNative> {
    if (let Some(handle) <- nulFileCache) {
        return handle
    }
    synchronized (nulFileMutex) {
        if (let Some(handle) <- nulFileCache) {
            return handle
        }
        let file = try {
            File("/dev/null", ReadWrite)
        } catch (_: FSException) {
            return None
        }
        nulFileObject = file
        nulFileCache = file.fileDescriptor.fileHandle
        return nulFileCache
    }
}

@When[os != "Windows"]
func isHandleValid(fileHandle: IntNative): Bool {
    return fileHandle >= 0
}

@When[os != "Windows"]
func getStdInHandle(): IntNative {
    return STDIN_FD
}

@When[os != "Windows"]
func getStdOutHandle(): IntNative {
    return STDOUT_FD
}

@When[os != "Windows"]
func getStdErrHandle(): IntNative {
    return STDERR_FD
}

@When[os != "Windows"]
func getNameFromCommand(cmd: ?String): Option<String> {
    let name: ?String = match (cmd) {
        case Some(v) =>
            if (v.contains("/")) {
                let arr = v.split("/", removeEmpty: true)
                arr[arr.size - 1]
            } else {
                v
            }
        case _ => None
    }
    return name
}

func cStringToOption(cstring: CString): ?String {
    if (cstring.isNull()) {
        return None
    }

    return cstring.toString()
}

/**
 * cpointerToArray
 *
 * @param cpointer: CPointer<CString>
 * @return Array<String>
 */
func cpointerToArray(cpointer: CPointer<CString>): ?Array<String> {
    if (cpointer.isNull()) {
        return None
    }

    var arrList: ArrayList<String> = ArrayList<String>()
    var cp_index: Int64 = 0
    var val: CString = unsafe { cpointer.read(cp_index) }
    while (!val.isNull()) {
        arrList.add(val.toString())
        cp_index++
        val = unsafe { cpointer.read(cp_index) }
    }
    return arrList.toArray()
}

/**
 * getEnvirments
 *
 * @param envCpointer: CPointer<CString>
 * @return HashMap<String, String>
 */
func getEnvirments(envCpointer: CPointer<CString>): ?Map<String, String> {
    if (envCpointer.isNull()) {
        return None
    }

    var envsMap: HashMap<String, String> = HashMap<String, String>()
    var cp_index: Int64 = 0
    var envItem_c: CString = unsafe { envCpointer.read(cp_index) }
    while (!envItem_c.isNull()) {
        let envItem: String = envItem_c.toString()
        let spiltIndex: Int64 = envItem.indexOf("=") ?? -1
        if (spiltIndex > -1) {
            let envKey = envItem[..spiltIndex]
            let envVal = envItem[spiltIndex + 1..]
            envsMap.add(envKey, envVal)
        }
        cp_index++
        envItem_c = unsafe { envCpointer.read(cp_index) }
    }

    return envsMap
}

func readToByteBuffer(inputStream: InputStream): ByteBuffer {
    let tempSize: Int64 = 4096 // 4KB
    var buffer: ByteBuffer = ByteBuffer(tempSize)
    let tempArr: Array<UInt8> = Array<UInt8>(tempSize, repeat: 0)
    while (true) {
        let readLen = inputStream.read(tempArr)
        if (readLen <= 0) {
            break
        } else if (readLen == tempSize) {
            buffer.write(tempArr)
        } else {
            buffer.write(tempArr.slice(0, readLen))
        }
    }

    return buffer
}

func collectCStringArray(it: Iterable<CString>): Array<CString> {
    let collector: ArrayList<CString> = ArrayList<CString>()
    try {
        for (item in it) {
            collector.add(item)
        }
    } catch (e: Exception) {
        for (element in collector) {
            unsafe {
                LibC.free(element)
            }
        }
        throw e
    }
    return collector.toArray()
}