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

/**
 * @file
 *
 * This is a library for StringWriter.
 */

package std.io

@FastNative
foreign func memcpy_s(dest: CPointer<UInt8>, destMax: UIntNative, src: CPointer<UInt8>, count: UIntNative): Int32

@FastNative
foreign func CJ_BUFFER_Int64ToCPointer(num: Int64, buf: CPointer<UInt8>, destMax: Int64): Int64

@FastNative
foreign func CJ_BUFFER_UInt64ToCPointer(num: UInt64, buf: CPointer<UInt8>, destMax: Int64): Int64

@FastNative
foreign func CJ_BUFFER_Float64ToCPointer(num: Float64, buf: CPointer<UInt8>, destMax: Int64): Int64

@FastNative
foreign func CJ_CORE_Float64ToCPointer(num: Float64): CPointer<UInt8>

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

/**
 * This class provides the ability to convert some types to strings with specified string encoding format
 *  and endian configuration and write them to the output stream.
 */
public class StringWriter<T> where T <: OutputStream {
    var outputBOS: BufferedOutputStream<T>

    /**
     * @throws IllegalArgumentException if encoding is UTF16 or UTF32
     */
    public init(output: T) {
        outputBOS = BufferedOutputStream(output)
    }

    public func flush(): Unit {
        this.outputBOS.flush()
    }

    public func write(v: String): Unit {
        if (v.isEmpty()) {
            return
        }
        this.outputBOS.write(unsafe { v.rawData() })
    }

    public func write<T>(v: T): Unit where T <: ToString {
        write(v.toString())
    }

    public func write(v: Bool): Unit {
        this.outputBOS.write(if (v) {
            "true".toArray()
        } else {
            "false".toArray()
        })
    }

    public func write(v: Int8): Unit {
        write(Int64(v))
    }

    public func write(v: Int16): Unit {
        write(Int64(v))
    }

    public func write(v: Int32): Unit {
        write(Int64(v))
    }

    public func write(v: Int64): Unit {
        var cnt: Int64 = if (v >= 0 && v < 10) {
            1
        } else {
            21 // "${Int64.Min}".size + "\0".size = 20 + 1 = 21
        }
        ensureEnoughOutBuf(cnt)

        var res: Int64 = 0
        unsafe {
            let cp = acquireArrayRawData(outputBOS.outBuf)
            res = CJ_BUFFER_Int64ToCPointer(v, cp.pointer + outputBOS.curPos, cnt)
            releaseArrayRawData(cp)
        }

        if (res < 0) {
            throw IOException("Error writing digit ${v}!")
        }
        outputBOS.curPos += res
    }

    private func ensureEnoughOutBuf(dataSize: Int64): Unit {
        if (dataSize > (outputBOS.outBuf.size - outputBOS.curPos)) {
            outputBOS.flushOutBuf()
        }
    }

    public func write(v: UInt8): Unit {
        write(UInt64(v))
    }

    public func write(v: UInt16): Unit {
        write(UInt64(v))
    }

    public func write(v: UInt32): Unit {
        write(UInt64(v))
    }

    public func write(v: UInt64): Unit {
        var cnt: Int64 = if (v >= 0 && v < 10) {
            1
        } else {
            21 // "${Int64.Min}".size + "\0".size = 20 + 1 = 21
        }
        ensureEnoughOutBuf(cnt)
        var res: Int64 = 0
        unsafe {
            let cp = acquireArrayRawData(outputBOS.outBuf)
            res = CJ_BUFFER_UInt64ToCPointer(v, cp.pointer + outputBOS.curPos, cnt)
            releaseArrayRawData(cp)
        }
        if (res < 0) {
            throw IOException("Error writing digit ${v}!")
        }
        outputBOS.curPos += res
    }

    public func write(v: Float16): Unit {
        write(Float64(v))
    }

    public func write(v: Float32): Unit {
        write(Float64(v))
    }

    public func write(v: Float64): Unit {
        unsafe {
            let p: CPointer<UInt8> = CJ_CORE_Float64ToCPointer(v)
            if (p.isNull()) {
                throw IOException()
            }
            let cpSize = Int64(strlen(p)) + 1 // append r'\0' in the end
            ensureEnoughOutBuf(cpSize)

            let cp = acquireArrayRawData(this.outputBOS.outBuf)
            let copyRes = memcpy_s(cp.pointer + this.outputBOS.curPos, UIntNative(cpSize), p, UIntNative(cpSize - 1))
            LibC.free(p)
            releaseArrayRawData(cp)
            if (copyRes != 0) {
                throw IOException("Error writing digit ${v}!")
            }
            this.outputBOS.curPos += cpSize - 1
        }
    }

    public func write(v: Rune): Unit {
        ensureEnoughOutBuf(6) // up to 6 byte for one utf-8 rune
        var res: Int64 = Rune.intoUtf8Array(v, this.outputBOS.outBuf, this.outputBOS.curPos)
        this.outputBOS.curPos += res
    }

    public func writeln(): Unit {
        ensureEnoughOutBuf(1)
        this.outputBOS.outBuf[this.outputBOS.curPos] = b'\n'
        this.outputBOS.curPos += 1
    }

    public func writeln(v: String): Unit {
        write(v)
        writeln()
    }

    public func writeln<T>(v: T): Unit where T <: ToString {
        write(v.toString())
        writeln()
    }

    public func writeln(v: Bool): Unit {
        this.outputBOS.write(if (v) {
            "true\n".toArray()
        } else {
            "false\n".toArray()
        })
    }

    public func writeln(v: Int8): Unit {
        writeln(Int64(v))
    }

    public func writeln(v: Int16): Unit {
        writeln(Int64(v))
    }

    public func writeln(v: Int32): Unit {
        writeln(Int64(v))
    }

    public func writeln(v: Int64): Unit {
        write(v)
        writeln()
    }

    public func writeln(v: UInt8): Unit {
        writeln(UInt64(v))
    }

    public func writeln(v: UInt16): Unit {
        writeln(UInt64(v))
    }

    public func writeln(v: UInt32): Unit {
        writeln(UInt64(v))
    }

    public func writeln(v: UInt64): Unit {
        write(v)
        writeln()
    }

    public func writeln(v: Float16): Unit {
        writeln(Float64(v))
    }

    public func writeln(v: Float32): Unit {
        writeln(Float64(v))
    }

    public func writeln(v: Float64): Unit {
        write(v)
        writeln()
    }

    public func writeln(v: Rune): Unit {
        write(v)
        writeln()
    }
}

extend<T> StringWriter<T> <: Resource where T <: Resource {
    /**
     * Close the current stream.
     */
    public func close(): Unit {
        outputBOS.close()
    }

    /**
     * Returns whether the current flow is closed.
     *
     * @return true if the current stream has been closed, otherwise returns false.
     */
    public func isClosed(): Bool {
        outputBOS.isClosed()
    }
}

extend<T> StringWriter<T> <: Seekable where T <: Seekable {
    /**
     * Seek to an offset, in bytes, in a stream.
     *
     * @params sp - Start position of the offset and size of the offset.
     *
     * @return the number of bytes in the stream from the beginning of the data to the cursor position.
     */
    public func seek(sp: SeekPosition): Int64 {
        outputBOS.seek(sp)
    }
}