/*
 * 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 class for ConsoleWriter.
 */

package std.env

import std.io.OutputStream
import std.sync.*

foreign func CJ_CONSOLE_Flush(fd: Int32): Unit

public class ConsoleWriter <: OutputStream {
    private let mutex: Mutex = Mutex()

    /* Write buffer size is 512 bytes */
    private let bufferSize: Int64 = 512

    /* Write buffer */
    private let buffer: Array<UInt8> = Array<UInt8>(this.bufferSize, repeat: 0)

    private let fd: Int32

    /**
     * Init the ConsoleWriteStream, initialize the buffer.
     */
    init(fd: Int32) {
        this.fd = fd
    }

    public func flush(): Unit {
        synchronized(mutex) {
            unsafe { CJ_CONSOLE_Flush(this.fd) }
        }
    }

    public func write(buffer: Array<Byte>): Unit {
        synchronized(mutex) {
            directWrite(buffer, buffer.size)
        }
    }

    public func writeln(buffer: Array<Byte>): Unit {
        synchronized(mutex) {
            directWrite(buffer, buffer.size, newline: true)
        }
    }

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

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

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

    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 {
        writeInt64(v, newline: false)
    }

    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 {
        writeUInt64(v, newline: false)
    }

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

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

    public func write(v: Float64): Unit {
        writeFloat64(v, newline: false)
    }

    public func write(v: Rune): Unit {
        // up to 6 bytes in utf-8
        let itemBytes: Array<UInt8> = Array<UInt8>(6, repeat: 0)
        let len = Rune.intoUtf8Array(v, itemBytes, 0)
        write(itemBytes[..len])
    }

    public func writeln(v: String): Unit {
        writeln(unsafe { v.rawData() })
    }

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

    public func writeln(v: Bool): Unit {
        writeln(if (v) {
            "true"
        } else {
            "false"
        })
    }

    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 {
        writeInt64(v, newline: true)
    }

    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 {
        writeUInt64(v, newline: true)
    }

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

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

    public func writeln(v: Float64): Unit {
        writeFloat64(v, newline: true)
    }

    public func writeln(v: Rune): Unit {
        let itemBytes: Array<UInt8> = Array<UInt8>(6, repeat: 0)
        let len = Rune.intoUtf8Array(v, itemBytes, 0)
        writeln(itemBytes[..len])
    }

    private func writeInt64(v: Int64, newline!: Bool = false): Unit {
        synchronized(mutex) {
            var writePos: Int64 = 0
            unsafe {
                let cp = acquireArrayRawData(this.buffer)
                writePos = CJ_BUFFER_Int64ToCPointer(v, cp.pointer, this.bufferSize)
                releaseArrayRawData(cp)
            }
            if (newline) {
                this.buffer[writePos] = b'\n'
                writePos++
            }
            directWrite(this.buffer, writePos)
        }
    }

    private func writeUInt64(v: UInt64, newline!: Bool = false): Unit {
        synchronized(mutex) {
            var writePos: Int64 = 0
            unsafe {
                let cp = acquireArrayRawData(this.buffer)
                writePos = CJ_BUFFER_UInt64ToCPointer(v, cp.pointer, this.bufferSize)
                releaseArrayRawData(cp)
            }

            if (newline) {
                this.buffer[writePos] = b'\n'
                writePos++
            }

            directWrite(this.buffer, writePos)
        }
    }

    private func writeFloat64(v: Float64, newline!: Bool = false): Unit {
        synchronized(mutex) {
            var writePos: Int64 = 0
            unsafe {
                let cp = acquireArrayRawData(this.buffer)
                writePos = CJ_BUFFER_Float64ToCPointer(v, cp.pointer, this.bufferSize)
                releaseArrayRawData(cp)
            }

            if (newline) {
                this.buffer[writePos] = b'\n'
                writePos++
            }

            directWrite(this.buffer, writePos)
        }
    }

    /**
     * Write buffer data into stdOut.
     *
     * @param arr write Array.
     * @param off The write data is placed into b from the index.
     * @param cnt write cnt bytes.
     *
     * @return Int64
     *
     * @since 0.24.1
     */
    private func directWrite(arr: Array<UInt8>, cnt: Int64, newline!: Bool = false): Int64 {
        unsafe {
            var pos: Int64 = 0
            var ptr = acquireArrayRawData(arr)

            while (cnt - pos >= Int64(Int32.Max)) {
                CJ_CONSOLE_Write(this.fd, ptr.pointer + pos, Int64(Int32.Max), false)
                pos += Int64(Int32.Max)
            }

            CJ_CONSOLE_Write(this.fd, ptr.pointer + pos, cnt - pos, newline)
            releaseArrayRawData(ptr)
        }
        return cnt
    }
}