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

@FastNative
foreign func CJ_CORE_IndexOfByte(orgStr: CPointer<UInt8>, orgSize: Int64, pat: UInt8): Int64

@FastNative
foreign func CJ_CORE_IndexOfString(
    orgStr: CPointer<UInt8>,
    subStr: CPointer<UInt8>,
    orgSize: Int64,
    subSize: Int64,
    start: Int64
): Int64

/**
 * A ByteBuffer obtains a Byte Array to operate the Byte as Stream.
 * ByteBuffer is meant for writing and reading byte streams.
 */
public class ByteBuffer <: IOStream & Seekable {
    var myData: Array<Byte>
    var start: Int64
    var _length: Int64

    private static const DEFAULT_CAPACITY: Int64 = 32

    /**
     * Creates a byte stream of the default capacity.
     */
    public init() {
        this(DEFAULT_CAPACITY)
    }

    /**
     * Constructors
     *
     * @params capacity - Capacity of the ByteBuffer.
     *
     * @throws IllegalArgumentException - If `capcacity` less than 0.
     */
    public init(capacity: Int64) {
        if (capacity < 0) {
            throw IllegalArgumentException("The capacity must be greater than or equal to 0: ${capacity}.")
        }
        this.myData = Array<Byte>(capacity, repeat: 0)
        this.start = 0
        this._length = 0
    }

    public init(source: Array<Byte>) {
        this.myData = source
        this.start = 0
        this._length = source.size
    }

    private init(bytes: Array<Byte>, start: Int64, length: Int64) {
        this.myData = bytes
        this.start = start
        this._length = length
    }

    /**
     * Returns the capacity of the ByteBuffer.
     */
    public prop capacity: Int64 {
        get() {
            return myData.size
        }
    }

    /**
     * Returns a copy of the ByteBuffer.
     */
    public func clone(): ByteBuffer {
        let itemDats = myData.clone()
        return ByteBuffer(itemDats, start, _length)
    }

    /**
     * Clears data from the ByteBuffer.
     */
    public func clear(): Unit {
        for (i in 0..(start + _length)) {
            myData[i] = 0
        }
        start = 0
        _length = 0
    }

    /**
     * Returns a reference to the original Array, any modification changes the original Array.
     */
    public func bytes(): Array<Byte> {
        return myData.slice(start, _length)
    }

    /**
     * Read the data to the buffer.
     *
     * @params buffer - Will read the data to the buffer.
     *
     * @returns Length of read data.
     * @throws IllegalArgumentException - If the buffer is empty.
     */
    public func read(buffer: Array<Byte>): Int64 {
        if (_length == 0) {
            return 0
        }
        let bufSize = buffer.size
        if (bufSize == 0) {
            throw IllegalArgumentException("The buffer is empty.")
        }
        let len = if (_length > bufSize) {
            bufSize
        } else {
            _length
        }
        myData.copyTo(buffer, start, 0, len)
        start += len
        _length -= len
        return len
    }

    /**
     * Read a byte from the buffer.
     *
     *
     * @returns read byte.
     * @throws IllegalArgumentException - If the buffer is empty.
     */
    public func readByte(): ?Byte {
        if (_length == 0) {
            return None
        }
        let byteValue = myData[start]
        start++
        _length--
        return byteValue
    }

    /**
     * Write data from the `buffer` to the ByteBuffer.
     *
     * @params buffer - The data buffer.
     */
    @OverflowWrapping
    public func write(buffer: Array<Byte>): Unit {
        let bufSize = buffer.size
        reserve(bufSize)
        if (_length >= 0) {
            buffer.copyTo(myData, 0, start + _length, bufSize)
            _length += bufSize
        } else {
            buffer.copyTo(myData, 0, start, bufSize)
            _length = bufSize
        }
    }

    /**
     * Write a byte to the ByteBuffer.
     *
     * @params v - The byte to write.
     */
    @OverflowWrapping
    public func writeByte(v: Byte): Unit {
        reserve(1)
        if (_length >= 0) {
            myData[start + _length] = v
            _length++
        } else {
            myData[start] = v
            _length = 1
        }
    }

    public func setLength(length: Int64): Unit {
        if (length < 0) {
            throw IllegalArgumentException("The length must be greater than or equal to 0.")
        }

        let size = myData.size

        if (length > size) {
            reserve(length - size)
        } else {
            myData[length..size].fill(0)
            if (start > length) {
                start = length
            }
        }

        _length = length - start
    }

    /**
     * Increases the capacity to ensure that it can hold at least the number of elements specified by the
     * `additional` argument.
     *
     * @params additional - The desired additional capacity.
     *
     * @throws IllegalArgumentException - If `additional` is less than 0.
     */
    public func reserve(additional: Int64): Unit {
        if (additional < 0) {
            throw IllegalArgumentException("The additional must be greater than or equal to 0.")
        }

        if (_length >= 0 && myData.size - start - _length >= additional) {
            return
        }

        if (_length < 0 && myData.size - start >= additional) {
            return
        }

        let growSize = if (_length >= 0) {
            checkGrowSize(myData.size, additional)
        } else {
            checkGrowSize(this.start, additional)
        }

        grow(growSize)
    }

    /**
     * Seek to an offset, in bytes, in a stream.
     * The specified location cannot be before the data header in the stream.
     * The specified location can be beyond the end of the file.
     *
     * @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.
     *
     * @throws IOException - The specified position is before the data header in the stream.
     */
    public func seek(sp: SeekPosition): Int64 {
        let pos: Int64 = match (sp) {
            case Current(offset) => this.start + offset
            case Begin(offset) => offset
            case End(offset) => this.start + this._length + offset
        }

        if (pos < 0) {
            throw IOException("Can't move the position before the beginning of the stream.")
        }

        this._length += this.start - pos
        this.start = pos

        this.start
    }

    private func grow(minCapacity: Int64): Unit {
        let oldCapacity = myData.size
        var newCapacity = checkGrowSize(oldCapacity, (oldCapacity >> 1))
        if (newCapacity < minCapacity) {
            newCapacity = minCapacity
        }

        let itemData = Array<Byte>(newCapacity, repeat: 0)
        myData.copyTo(itemData, 0, 0, start + _length)
        myData = itemData
    }

    private func checkGrowSize(oldSize: Int64, additional: Int64): Int64 {
        try {
            return oldSize + additional
        } catch (_: OverflowException) {
            throw OverflowException(
                "The maximum value for capacity expansion cannot exceed the maximum value of Int64.")
        }
    }
}