/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 */

package brotli4cj

import std.io.*

public class BitReader {

    private static let LOG_BITNESS: Int32 = Utils.getLogBintness()

    private static let DEBUG: Int32 = Utils.isDebugMode()

    static let BITNESS: Int32 = 1 << LOG_BITNESS

    private static let BYTENESS: Int32 = BITNESS / 8

    private static let CAPACITY: Int32 = 4096

    private static let SLACK: Int32 = 64

    private static let BUFFER_SIZE: Int32 = CAPACITY + SLACK

    private static let SAFEGUARD: Int32= 36

    private static let WATERLINE: Int32 = CAPACITY - SAFEGUARD

    private static let HALF_BITNESS: Int32 = BITNESS / 2

    private static let HALF_SIZE: Int32 = BYTENESS / 2

    private static let HALVES_CAPACITY: Int32 = CAPACITY / HALF_SIZE

    private static let HALF_BUFFER_SIZE: Int32 = BUFFER_SIZE / HALF_SIZE

    private static let HALF_WATERLINE: Int32 = WATERLINE / HALF_SIZE

    private static let LOG_HALF_SIZE: Int32 = LOG_BITNESS - 4

    static func readMoreInput(s: State): Unit {
        if (s.halfOffset > HALF_WATERLINE) {
            doReadMoreInput(s)
        }
    }

    static func doReadMoreInput(s: State): Unit {
        if (s.endOfStreamReached != 0) {
            if (halfAvailable(s) >= -2) {
                return
            }
            throw BrotliRuntimeException("No more input")
        }
        let readOffset = s.halfOffset << LOG_HALF_SIZE
        var bytesInBuffer = CAPACITY - readOffset
        Utils.copyBytesWithin(s.byteBuffer, 0, readOffset, CAPACITY)
        s.halfOffset = 0
        while (bytesInBuffer < CAPACITY) {
            let spaceLeft = CAPACITY - bytesInBuffer
            let len = Utils.readInput(s, s.byteBuffer, bytesInBuffer, spaceLeft)
            if (len <= 0) {
                s.endOfStreamReached = 1
                s.tailBytes = bytesInBuffer
                bytesInBuffer += HALF_SIZE - 1
                break
            }
            bytesInBuffer += len
        }
        bytesToNibbles(s, bytesInBuffer)
    }

    static func checkHealth(s: State, endOfStream: Int32): Unit {
        if (s.endOfStreamReached == 0) {
            return
        }
        let byteOffset: Int32 = (s.halfOffset << LOG_HALF_SIZE) + ((s.bitOffset + 7) >> 3) - BYTENESS
        if (byteOffset > s.tailBytes) {
            throw BrotliRuntimeException("Read after end")
        }
        if ((endOfStream != 0) && (byteOffset != s.tailBytes)) {
            throw BrotliRuntimeException("Unused bytes after end")
        }
    }

    static func assertAccumulatorHealthy(s: State): Unit {
        if (s.bitOffset > BITNESS) {
            throw Exception("Accumulator underloaded: " + s.bitOffset.toString())
        }
    }

    static func fillBitWindow(s: State): Unit {
        if (DEBUG != 0) {
            assertAccumulatorHealthy(s)
        }
        if (s.bitOffset >= HALF_BITNESS) {
            if (BITNESS == 64) {
                s.accumulator64 = (Int64(s.intBuffer[Int64(s.halfOffset)]) << HALF_BITNESS) | Utils.rightShift(s.accumulator64, Int64(HALF_BITNESS))
            } else {
                s.accumulator32 = (Int32(s.shortBuffer[Int64(s.halfOffset)]) << HALF_BITNESS) | Utils.rightShift(s.accumulator32, HALF_BITNESS)
            }
            s.halfOffset++
            s.bitOffset -= HALF_BITNESS
        }
    }

    static func doFillBitWindow(s: State): Unit {
        if (DEBUG != 0) {
            assertAccumulatorHealthy(s)
        }
        if (BITNESS == 64) {
            s.accumulator64 = (Int64(s.intBuffer[Int64(s.halfOffset)]) << HALF_BITNESS) | Utils.rightShift(s.accumulator64, Int64(HALF_BITNESS))
        } else {
            s.accumulator32 = (Int32(s.shortBuffer[Int64(s.halfOffset)]) << HALF_BITNESS) | Utils.rightShift(s.accumulator32, HALF_BITNESS)
        }
        s.halfOffset++
        s.bitOffset -= HALF_BITNESS
    }

    static func peekBits(s: State): Int32 {
        if (BITNESS == 64) {
            return Int32(Utils.rightShift(s.accumulator64, Int64(s.bitOffset)))
        } else {
            return Utils.rightShift(s.accumulator32, s.bitOffset)
        }
    }

  /**
   * Fetches bits from accumulator.
   *
   * WARNING: accumulator MUST contain at least the specified amount of bits,
   * otherwise BitReader will become broken.
   */
    static func readFewBits(s: State, n: Int32): Int32 {
        let v = peekBits(s) & ((1 << n) - 1)
        s.bitOffset += n
        return v
    }

    static func readBits(s: State, n: Int32): Int32 {
        if (HALF_BITNESS >= 24) {
            return readFewBits(s, n)
        } else {
            return if (n <= 16) { readFewBits(s, n) } else { readManyBits(s, n) }
        }
    }

    private static func readManyBits(s: State, n: Int32): Int32 {
        let low = readFewBits(s, 16)
        doFillBitWindow(s)
        return low | (readFewBits(s, n - 16) << 16)
    }

    static func initBitReader(s: State): Unit {
        s.byteBuffer = Array<UInt8>(Int64(BUFFER_SIZE)) { _ => 0 }
        if (BITNESS == 64) {
            s.accumulator64 = 0
            s.intBuffer = Array<Int32>(Int64(HALF_BUFFER_SIZE)) { _ => 0 }
        } else {
            s.accumulator32 = 0
            s.shortBuffer = Array<Int16>(Int64(HALF_BUFFER_SIZE)) { _ => 0 }
        }
        s.bitOffset = BITNESS
        s.halfOffset = HALVES_CAPACITY
        s.endOfStreamReached = 0
        prepare(s)
    }

    private static func prepare(s: State): Unit {
        readMoreInput(s)
        checkHealth(s, 0)
        doFillBitWindow(s)
        doFillBitWindow(s)
    }

    static func reload(s: State): Unit {
        if (s.bitOffset == BITNESS) {
            prepare(s)
        }
    }

    static func jumpToByteBoundary(s: State): Unit {
        let padding = (BITNESS - s.bitOffset) & 7
        if (padding != 0) {
            let paddingBits = readFewBits(s, padding)
            if (paddingBits != 0) {
                throw BrotliRuntimeException("Corrupted padding bits")
            }
        }
    }

    static func halfAvailable(s: State): Int32 {
        var limit = HALVES_CAPACITY
        if (s.endOfStreamReached != 0) {
            limit = (s.tailBytes + (HALF_SIZE - 1)) >> LOG_HALF_SIZE
        }
        return limit - s.halfOffset
    }

    static func copyRawBytes(s: State, data: Array<UInt8>, offset: Int32, length: Int32): Unit {
        var tempoffset = offset
        var templength = length
        if ((s.bitOffset & 7) != 0) {
            throw BrotliRuntimeException("Unaligned copyBytes")
        }
        while ((s.bitOffset != BITNESS) && (templength != 0)) {
            data[Int64(match (0) { case _ => tempoffset++; tempoffset - 1 })] = UInt8(peekBits(s))
            s.bitOffset += 8
            templength--
        }
        if (templength == 0) {
            return
        }
        let copyNibbles = min(halfAvailable(s), templength >> LOG_HALF_SIZE)
        if (copyNibbles > 0) {
            let readOffset = s.halfOffset << LOG_HALF_SIZE
            let delta = copyNibbles << LOG_HALF_SIZE
            s.byteBuffer.copyTo( data, Int64(readOffset), Int64(tempoffset), Int64(delta))
            tempoffset += delta
            templength -= delta
            s.halfOffset += copyNibbles
        }
        if (templength == 0) {
            return
        }
        if (halfAvailable(s) > 0) {
            fillBitWindow(s)
            while (templength != 0) {
                data[Int64(match (0) { case _ => tempoffset++; tempoffset - 1 })] = UInt8(peekBits(s))
                s.bitOffset += 8
                templength--
            }
            checkHealth(s, 0)
            return
        }
        while (templength > 0) {
            let chunkLen = Utils.readInput(s, data, tempoffset, templength)
            if (chunkLen == -1) {
                throw BrotliRuntimeException("Unexpected end of input")
            }
            tempoffset += chunkLen
            templength -= chunkLen
        }
    }

  /**
   * Translates bytes to halves (int/short).
   */
    static func bytesToNibbles(s: State, byteLen: Int32): Unit {
        var byteBuffer = s.byteBuffer
        let halfLen = byteLen >> LOG_HALF_SIZE
        if (BITNESS == 64) {
            let intBuffer = s.intBuffer
            for (i in 0..halfLen) {
                intBuffer[Int64(i)] = (Int32(byteBuffer[Int64(i * 4)]) & 255) | (Int32((byteBuffer[Int64((i * 4) + 1)]) & 255) << 8) | ((Int32(byteBuffer[Int64((i * 4) + 2)]) & 255) << 16) | ((Int32(byteBuffer[Int64((i * 4) + 3)]) & 255) << 24)
            }
        } else {
            let shortBuffer = s.shortBuffer
            for (i in 0..halfLen) {
                shortBuffer[Int64(i)] = (Int16(Int32(byteBuffer[Int64(i * 2)])) & 255 | (Int16(Int32(byteBuffer[Int64((i * 2) + 1)])) & 255) << 8)
            }
        }
    }
}