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

extend Rune {
    /**
     * Returns true if this Unicode character is an Ascii letter.
     */
    public func isAsciiLetter(): Bool {
        return (this >= r'A' && this <= r'Z') || (this >= r'a' && this <= r'z')
    }

    /**
     * Returns true if this Unicode character is an Ascii number.
     */
    public func isAsciiNumber(): Bool {
        return this >= r'0' && this <= r'9'
    }

    /**
     * Returns true if this this Rune is an Ascii hexadecimal number.
     */
    public func isAsciiHex(): Bool {
        return this.isAsciiNumber() || (this >= r'a' && this <= r'f') || (this >= r'A' && this <= r'F')
    }

    /**
     * Returns true if this Rune is an ASCII octal number.
     */
    public func isAsciiOct(): Bool {
        return this >= r'0' && this <= r'7'
    }

    /**
     * Return true if this Rune is an ASCII punctuation.
     */
    public func isAsciiPunctuation(): Bool {
        return (this >= r'!' && this <= r'/') || (this >= r':' && this <= r'@') || (this >= r'[' && this <= r'`') || (this >=
            r'{' && this <= r'~')
    }

    /**
     * Return true if this Rune is an ASCII graphic character.
     */
    public func isAsciiGraphic(): Bool {
        return this >= r'!' && this <= r'~'
    }

    /**
     * Return true if this Rune is an ASCII control character.
     */
    public func isAsciiControl(): Bool {
        return (this >= r'\u{0000}' && this <= r'\u{001F}') || this == r'\u{007F}'
    }

    /**
     * Return true if this Rune is an ASCII number or ASCII letter.
     */
    public func isAsciiNumberOrLetter(): Bool {
        return this.isAsciiLetter() || this.isAsciiNumber()
    }

    /** Returns true if this Unicode character is Lowercase. */
    public func isAsciiLowerCase(): Bool {
        return this >= r'a' && this <= r'z'
    }

    /** Returns true if this Unicode character is Uppercase. */
    public func isAsciiUpperCase(): Bool {
        return this >= r'A' && this <= r'Z'
    }

    /** Returns true if this Unicode character is an Ascii code. */
    public func isAscii(): Bool {
        return this >= r'\u{0000}' && this <= r'\u{007F}'
    }

    /** Returns true if this Unicode character is whitespace. */
    public func isAsciiWhiteSpace(): Bool {
        return (this >= r'\u{0009}' && this <= r'\u{000D}') || this == r'\u{0020}'
    }

    /** Returns the uppercase of this Unicode character. */
    public func toAsciiUpperCase(): Rune {
        return if (this.isAsciiLowerCase()) {
            Rune(UInt32(this) - UInt32(ASCII_UPPER_TO_LOWER_DIFF))
        } else {
            this
        }
    }

    /** Returns the lowercase of this Unicode character. */
    public func toAsciiLowerCase(): Rune {
        return if (this.isAsciiUpperCase()) {
            Rune(UInt32(this) + UInt32(ASCII_UPPER_TO_LOWER_DIFF))
        } else {
            this
        }
    }

    /*
     * @throws IllegalArgumentException if `arr[index]` is an invalid utf8 leading code or
     *         there is no valid utf8 code among `arr[0] ~ arr[index - 1]`.
     */
    public static func utf8Size(arr: Array<UInt8>, index: Int64): Int64 {
        if (index >= arr.size) {
            throw IllegalArgumentException("Invalid utf8 byte sequence.")
        }
        let byte = arr[index]
        // fast path for ASCII
        if (byte < HIGH_1_UInt8) {
            return 1
        }
        // 0b_10xx_xxxx is not a Unicode Code Point's boundary, throw exception in this case
        if (byte < HIGH_2_UInt8) {
            var (char, _) = getPreviousFromUtf8(arr, index)
            throw IllegalArgumentException("Byte index ${index} is not a Rune boundary, it is inside '${char}'.")
        }
        // 0b_1111_1xxx and above are invalid UTF-8 sequences (5-6 byte sequences not allowed in Unicode)
        if (byte >= HIGH_5_UInt8) {
            throw IllegalArgumentException("Invalid UTF-8 character.")
        }
        return utf8SizeUnchecked(byte)
    }

    // Considering the performance,this function do not check the parameters.
    // The parameter should only be the boundary of Unicode Code Point,
    // i.e. the parameter could not start with 0b10.
    // Note: This function only handles valid UTF-8 leading bytes (1-4 bytes).
    static func utf8SizeUnchecked(byte: Byte): Int64 {
        return match {
            case byte < HIGH_1_UInt8 => 1
            case byte < HIGH_3_UInt8 => 2
            case byte < HIGH_4_UInt8 => 3
            case _ => 4
        }
    }

    @OverflowWrapping
    public static func utf8Size(c: Rune): Int64 {
        var ch = UInt32(c)
        match {
            case ch <= UTF8_1_MAX => 1
            case ch <= UTF8_2_MAX => 2
            case ch <= UTF8_3_MAX => 3
            case ch <= UTF8_4_MAX => 4
            case _ => throw IllegalArgumentException("Invalid Unicode scalar value: code point exceeds U+10FFFF.")
        }
    }

    /*
     * @throws IllegalArgumentException if arr[index] is an invalid utf8 code or
     * there is no valid utf8 code among arr[0] ~ arr[index - 1].
     */
    @OverflowWrapping
    public static func fromUtf8(arr: Array<UInt8>, index: Int64): (Rune, Int64) {
        if (index >= arr.size) {
            throw IllegalArgumentException("Invalid utf8 byte sequence.")
        }
        return fromUtf8(arr.rawptr, arr.start + index, arr.start + arr.len)
    }

    static func fromUtf8(arr: RawArray<UInt8>, startIndex: Int64, endIndex: Int64): (Rune, Int64) {
        let byte0: UInt8 = arrayGetUnchecked(arr, startIndex)
        return match {
            case byte0 < HIGH_1_UInt8 => (Rune(byte0), 1)
            case byte0 < HIGH_2_UInt8 => throw IllegalArgumentException("Invalid utf8 byte sequence.")
            case byte0 < HIGH_3_UInt8 => (checkUtf8Of2Bytes(arr, startIndex, endIndex), 2)
            case byte0 < HIGH_4_UInt8 => (checkUtf8Of3Bytes(arr, startIndex, endIndex), 3)
            case byte0 < HIGH_5_UInt8 => (checkUtf8Of4Bytes(arr, startIndex, endIndex), 4)
            case _ => throw IllegalArgumentException("Invalid unicode scalar value.")
        }
    }

    /*
     * @throws IllegalArgumentException if there is no valid utf8 code
     *         among `arr[0] ~ arr[index - 1]`.
     */
    @OverflowWrapping
    public static func getPreviousFromUtf8(arr: Array<UInt8>, index: Int64): (Rune, Int64) {
        var i: Int64 = index - 1
        var nowByte: UInt8
        var res: (Rune, Int64) = (r'\0', -1)
        while (i >= 0) {
            nowByte = arr[i]
            if (nowByte >= HIGH_1_UInt8 && nowByte < HIGH_2_UInt8) {
                i--
                continue
            } else {
                res = fromUtf8(arr, i)
                break
            }
        }
        if (res[0] == r'\0' && res[1] == -1) {
            throw IllegalArgumentException("Invalid utf8 byte sequence.")
        }
        return res
    }

    /*
     * @throws IllegalArgumentException if the character occupies a position beyond the array range
     */
    @OverflowWrapping
    public static func intoUtf8Array(c: Rune, arr: Array<UInt8>, index: Int64): Int64 {
        var ch = UInt32(c)
        checkIndexValid(c, arr, index)
        if (ch <= UTF8_1_MAX) {
            arr[index] = UInt8(ch)
            return 1
        } else if (ch <= UTF8_2_MAX) {
            arr[index] = UInt8(((ch >> SHIFT_6) & LOW_5_MASK) | HIGH_2_MASK)
            arr[index + 1] = UInt8(((ch) & LOW_6_MASK) | HIGH_1_MASK)
            return 2
        } else if (ch <= UTF8_3_MAX) {
            arr[index] = UInt8(((ch >> SHIFT_12) & LOW_4_MASK) | HIGH_3_MASK)
            arr[index + 1] = UInt8(((ch >> SHIFT_6) & LOW_6_MASK) | HIGH_1_MASK)
            arr[index + 2] = UInt8((ch & LOW_6_MASK) | HIGH_1_MASK)
            return 3
        } else if (ch <= UTF8_4_MAX) {
            arr[index] = UInt8(((ch >> SHIFT_18) & LOW_3_MASK) | HIGH_4_MASK)
            arr[index + 1] = UInt8(((ch >> SHIFT_12) & LOW_6_MASK) | HIGH_1_MASK)
            arr[index + 2] = UInt8(((ch >> SHIFT_6) & LOW_6_MASK) | HIGH_1_MASK)
            arr[index + 3] = UInt8((ch & LOW_6_MASK) | HIGH_1_MASK)
            return 4
        } else {
            throw IllegalArgumentException("Invalid Unicode scalar value: code point exceeds U+10FFFF.")
        }
    }

    /*
     * @throws IllegalArgumentException if the character occupies a position beyond the array range
     */
    private static func checkIndexValid(c: Rune, arr: Array<UInt8>, index: Int64): Unit {
        if (index < 0) {
            throw IllegalArgumentException("The index cannot be less than 0.")
        }
        let ch = UInt32(c)
        let arrSize = arr.size
        let lengthOfCharBytes = if (ch <= UTF8_1_MAX) {
            1
        } else if (ch <= UTF8_2_MAX) {
            2
        } else if (ch <= UTF8_3_MAX) {
            3
        } else if (ch <= UTF8_4_MAX) {
            4
        } else {
            throw IllegalArgumentException("Invalid Unicode scalar value: code point exceeds U+10FFFF.")
        }
        if (lengthOfCharBytes + index > arrSize) {
            throw IllegalArgumentException(
                "The bytecode position occupied by this character is outside the range of the array.")
        }
    }
}

extend Rune <: Comparable<Rune> {
    /**
     * Compare the relationship between two instance of Rune.
     *
     * @param other Instance of Rune compared with this.
     * @return Value indicating the relationship between two instance of Rune.
     *
     * @since 0.27.3
     */
    public func compare(other: Rune): Ordering {
        match {
            case this < other => Ordering.LT
            case this > other => Ordering.GT
            case _ => Ordering.EQ
        }
    }
}