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

package stdx.encoding.json.stream



let hex = "0123456789abcdef".toArray()
const HIGH_1_MASK: UInt32 = 0b10000000 // 0x80
const HIGH_2_MASK: UInt32 = 0b11000000 // 0xc0
const HIGH_3_MASK: UInt32 = 0b11100000 // 0xe0
const HIGH_4_MASK: UInt32 = 0b11110000 // 0xf0
const HIGH_5_MASK: UInt32 = 0b11111000 // 0xf8
const HIGH_6_MASK: UInt32 = 0b11111100 // 0xfc
const SHIFT_30: UInt32 = 30
const SHIFT_24: UInt32 = 24
const SHIFT_18: UInt32 = 18
const SHIFT_12: UInt32 = 12
const SHIFT_6: UInt32 = 6
const LOW_6_MASK: UInt32 = 0b00111111 // 0x3f
const LOW_5_MASK: UInt32 = 0b00011111 // 0x1f
const LOW_4_MASK: UInt32 = 0b00001111 // 0x0f
const LOW_3_MASK: UInt32 = 0b00000111 // 0x07
const LOW_2_MASK: UInt32 = 0b00000011 // 0x03
const LOW_1_MASK: UInt32 = 0b00000001 // 0x01

const UTF8_5_MAX: UInt32 = 0x3FFFFFF
const UTF8_4_MAX: UInt32 = 0x10FFFF
const UTF8_3_MAX: UInt32 = 0xFFFF
const UTF8_2_MAX: UInt32 = 0x07FF
const UTF8_1_MAX: UInt32 = 0x7F
const ESCAPE_SIZE_2: Int64 = 2
const ESCAPE_SIZE_6: Int64 = 6

func newEscapeTable(): Array<Byte> {
    let escapeTable: Array<Byte> = Array<Byte>(128, repeat: 0)
    escapeTable[Int64(b'\b')] = b'b'
    escapeTable[Int64(b'\t')] = b't'
    escapeTable[Int64(b'\n')] = b'n'
    escapeTable[Int64(b'\f')] = b'f'
    escapeTable[Int64(b'\r')] = b'r'
    escapeTable[Int64(b'\"')] = b'\"'
    escapeTable[Int64(b'\\')] = b'\\'
    return escapeTable
}

let escapeTable = newEscapeTable()

@OverflowWrapping
func writeEscapeAscii(w: JsonWriter, byte: Int64) {
    if (escapeTable[byte] != 0) {
        if (w.curPos + ESCAPE_SIZE_2 > JsonWriter.DEFAULT_CAPACITY) {
            w.flushOutBuf()
        }
        w.buffer[w.curPos + 1] = escapeTable[byte]
        w.buffer[w.curPos] = b'\\'
        w.curPos += 2
    } else {
        if (w.curPos + ESCAPE_SIZE_6 > JsonWriter.DEFAULT_CAPACITY) {
            w.flushOutBuf()
        }
        w.buffer[w.curPos + 5] = hex[byte & 0xF] // num of low 4 bits
        w.buffer[w.curPos + 4] = hex[byte >> 4] // num of high 4 bits
        w.buffer[w.curPos + 3] = b'0'
        w.buffer[w.curPos + 2] = b'0'
        w.buffer[w.curPos + 1] = b'u'
        w.buffer[w.curPos] = b'\\'
        w.curPos += 6
    }
}

extend String <: JsonSerializable {
    @OverflowWrapping
    public func toJson(w: JsonWriter): Unit {
        w.beforeValue()
        w.buffer[w.curPos] = b'\"'
        w.curPos++

        var beforePos = 0
        let backslash = Int64(b'\\')
        var escapeMask = 0u64
        escapeMask |= 1u64 << Int64(b'\"')
        escapeMask |= (1u64 << 32) - 1u64

        if (w.writeConfig.htmlSafe) {
            var htmlEscapeMask = 0u64
            htmlEscapeMask |= (1u64 << Int64(b'<'))
            htmlEscapeMask |= (1u64 << Int64(b'>'))
            htmlEscapeMask |= (1u64 << Int64(b'&'))
            htmlEscapeMask |= (1u64 << Int64(b'='))
            htmlEscapeMask |= (1u64 << Int64(b'\''))
            escapeMask |= htmlEscapeMask
        }
        for (index in 0..size) {
            let byte = Int64(this[index])
            if (byte > 127) {
                continue
            }
            if (byte == 127 || byte == backslash || (byte < 64 && ((1u64 << byte) & escapeMask) != 0)) {
                insideflush(w, index, beforePos)
                writeEscapeAscii(w, byte)
                beforePos = index + 1
            }
        }

        insideflush(w, this.size, beforePos)
        w.buffer[w.curPos] = b'\"'
        w.curPos++
    }

    @OverflowWrapping
    private func insideflush(w: JsonWriter, curPos: Int64, beforePos: Int64) {
        if (curPos <= beforePos) {
            return
        }
        if (curPos - beforePos >= JsonWriter.FLUSH_THRESHOLD) {
            // write directly to the stream if the string is too long
            w.flushOutBuf()
            unsafe { w.out.write(this.rawData()[beforePos..curPos]) }
        } else {
            if (w.curPos + curPos - beforePos > JsonWriter.DEFAULT_CAPACITY) {
                // flush if the buffer has not enough capacity
                w.flushOutBuf()
            }
            unsafe { this.rawData().copyTo(w.buffer, beforePos, w.curPos, curPos - beforePos) }
            w.curPos = w.curPos + curPos - beforePos
            // flush if the buffer is nearly full
            if (w.curPos >= JsonWriter.FLUSH_THRESHOLD) {
                w.flushOutBuf()
            }
        }
    }
}

extend String <: JsonDeserializable<String> {
    public static func fromJson(r: JsonReader): String {
        const TRUE = "true"
        const FALSE = "false"
        const NULL = "null"
        match (r.peek()) {
            case Some(JsonToken.JsonString) => return r.readString()
            case Some(JsonToken.JsonBool) =>
                return if (r.readBool()) {
                    TRUE
                } else {
                    FALSE
                }
            case Some(JsonToken.JsonNull) =>
                r.readNull()
                return NULL
            case Some(JsonToken.JsonNumber) =>
                r.nextValue(r.stringBuffer)
                let value = unsafe { String.fromUtf8Unchecked(r.stringBuffer.data[0..r.stringBuffer.size]) }
                r.stringBuffer.clear()
                return value
            case _ => throw IllegalStateException("The next Token is not JSON String.")
        }
    }
}