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

public struct Token <: ToBytes {
    /**
         Fields
     */
    public let kind: TokenKind
    public let value: String
    public let pos: Position
    public var delimiterNum: UInt16 = 1 // Delimiter r'#' number for raw string.
    private var isSingleQuote_ = false
    public mut prop isSingleQuote: Bool {
        get() {
            isSingleQuote_
        }
        set(v) {
            if (kind == TokenKind.SINGLE_QUOTED_STRING_LITERAL && v == false) {
                throw IllegalArgumentException(
                "TokenKind(" + getTokenKindValue(kind) + ") and isSingleQuote not matched.\n")
            }
            isSingleQuote_ = v
        }
    }

    /**
         Constructors
     */
    // Create an token with default value.
    public init() {
        kind = TokenKind.ILLEGAL
        value = ""
        pos = Position()
    }

    // Create a new token without value, position from enumerated type token.
    public init(kind: TokenKind) {
        this.kind = kind
        value = getTokenLiteral(kind)
        pos = Position()
        if (kind == SINGLE_QUOTED_STRING_LITERAL) {
            this.isSingleQuote_ = true
        }
    }

    // Create a new token with empty position from token kind and token value.
    public init(kind: TokenKind, value: String) {
        // Forbid illegal init such as Token(TokenKind.ADD, "foo")
        if (!checkKindValue(kind, value)) {
            throw IllegalArgumentException(
                "TokenKind(" + getTokenKindValue(kind) + ") and Value(" + value + ") not matched.\n")
        }
        this.kind = kind
        this.value = value
        pos = Position()
        if (kind == SINGLE_QUOTED_STRING_LITERAL) {
            this.isSingleQuote_ = true
        }
    }

    // Create a new token from token kind, value, fileID, line, column.
    init(k: TokenKind, v: String, f: UInt32, l: Int32, c: Int32, isSingleQuote!: Bool = false) {
        kind = k
        value = v
        pos = Position(f, l, c)
        this.isSingleQuote_ = isSingleQuote
    }

    public func addPosition(fileID: UInt32, line: Int32, colum: Int32): Token {
        return Token(this.kind, this.value, fileID, line, colum, isSingleQuote: this.isSingleQuote_)
    }

    func addPosition(position: NodeFormat_Position) {
        return Token(this.kind, this.value, position.fileId, position.line, position.column, isSingleQuote: this.isSingleQuote_)
    }

    func addPosition(position: Position) {
        return Token(this.kind, this.value, position.fileID, position.line, position.column, isSingleQuote: this.isSingleQuote_)
    }

    // Returns true if and only if the two token's kind id, value, position are identical.
    // XXX: Bound check.
    public operator func ==(r: Token): Bool {
        return this.kind == r.kind && this.value == r.value && this.pos == r.pos
    }

    // Returns true if and only if the two token's kind id, value, position are not identical.
    public operator func !=(r: Token): Bool {
        return !(this == r)
    }

    // Token + Tokens => Tokens
    public operator func +(r: Tokens): Tokens {
        var l: Tokens = Tokens(Array<Token>(1, repeat: this))
        return l + r
    }

    // Token + Token => Tokens
    public operator func +(r: Token): Tokens {
        var l: Tokens = Tokens(Array<Token>(1, repeat: this))
        return l + r
    }

    // Print the information of this token.
    public func dump(): Unit {
        let kv: String = getTokenKindValue(kind)
        let id: UInt16 = getTokenID(kind)
        print("description: ${kv}, token_id: ${id}, ")
        if (this.kind == TokenKind.NL) {
            print("token_literal_value: \\n, ")
        } else {
            print("token_literal_value: ${value}, ")
        }
        pos.dump()
    }

    public func toBytes(): Array<UInt8> {
        var kindId: UInt16 = getTokenID(this.kind)
        var b1: Array<UInt8> = castUInt16ToBytes(kindId)
        var b2: Array<UInt8> = castStringToBytes(this.value)
        var b12: Array<UInt8> = concatBytes(b1, b2)
        var b3: Array<UInt8> = this.pos.toBytes()
        var b23: Array<UInt8> = concatBytes(b12, b3)
        let isSingle: UInt16 = if (this.isSingleQuote) {
            1
        } else {
            0
        }
        var b4: Array<UInt8> = castUInt16ToBytes(isSingle)
        if (getTokenKind(kindId) == TokenKind.MULTILINE_RAW_STRING) {
            var b5: Array<UInt8> = castUInt16ToBytes(this.delimiterNum)
            var b45: Array<UInt8> = concatBytes(b4, b5)
            return concatBytes(b23, b45)
        }
        return concatBytes(b23, b4)
    }

    func toBytes(arr: Array<UInt8>, start1: Int64) {
        var start = start1
        let kindId: UInt16 = getTokenID(this.kind)
        start = castUInt16ToBytes(arr, start, kindId)
        start = castStringToBytes(arr, start, this.value)
        start = this.pos.toBytes(arr, start)
        let isSingle: UInt16 = if (this.isSingleQuote) {
            1
        } else {
            0
        }
        start = castUInt16ToBytes(arr, start, isSingle)
        if (getTokenKind(kindId) == TokenKind.MULTILINE_RAW_STRING) {
            start = castUInt16ToBytes(arr, start, this.delimiterNum)
        }
        return start
    }

    func getSize() {
        let kindId: UInt16 = getTokenID(this.kind)
        let size_kind = 2
        let size_len = 4
        let size_value = this.value.size
        let size_pos = this.pos.getSize()
        var size = size_kind + size_len + size_value + size_pos
        let size_is_single = 2
        size += size_is_single
        if (getTokenKind(kindId) == TokenKind.MULTILINE_RAW_STRING) {
            let size_delimiterNum = 2
            size += size_delimiterNum
        }
        return size
    }
}

func refreshPos(token: Token, f: UInt32, l: Int32, c: Int32): Token {
    var tk: Token
    if (let Some(macroObjPtr) <- MACRO_OBJECT.get()) {
        let pos = getMacroPosition(macroObjPtr)
        tk = Token(token.kind, token.value, pos[0], pos[1], pos[2])
        tk.delimiterNum = token.delimiterNum
        tk.isSingleQuote = token.isSingleQuote
        return tk
    }
    tk = Token(token.kind, token.value, f, l, c)
    tk.delimiterNum = token.delimiterNum
    tk.isSingleQuote = token.isSingleQuote
    return tk
}