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

package appauth

import std.convert.*

class URLEncoder {

    static let DONT_NEED_ENCODING: BitSet = URLEncoder.getBitSet()
    static let CASE_DIFF: UInt32 = 32 // ('a' - 'A') = 32

    static func getBitSet(): BitSet {
        let bs: BitSet = BitSet(256)

        // 'a'-'z': 97-122
        for (i in CHAR_a..=CHAR_z) {
            bs.set(i)
        }
        // 'A'-'Z': 65-90
        for (i in CHAR_A..=CHAR_Z) {
            bs.set(i)
        }
        // '0'-'9': 48-57
        for (i in CHAR_0..=CHAR_9) {
            bs.set(i)
        }
        // 在encodes()方法中将空格编码为'+'。'+' = 43
        bs.set(CHAR_SPACE) // ' ':32
        bs.set(CHAR_B1) // '-':45
        bs.set(CHAR_B2) // '_':95
        bs.set(CHAR_B3) // '.':46
        bs.set(CHAR_B4) // '*':42
        return bs
    }

    public static func encode(s: String, enc: String): String {
        if (enc.isEmpty()) {
            throw Exception("charsetName can not be empty!")
        }
        if (!((enc == "UTF-8") || (enc == "utf-8") || (enc == "UTF8") || (enc == "utf8"))) {
            throw Exception("only UTF-8 Character Set is supported!")
        }

        try {
            return encode(s)
        } catch (e: Exception) {
            throw Exception("encode fail: " + e.message)
        }
    }

    /**
     * 使用特定字符集将字符串转换为application/x-wwwww-form-urlencoded格式。
     * 此方法使用提供的字符集获取不安全字符的字节。
     * 仅支持UTF-8字符集。
     *
     * @param 要转化的字符串.
     *
     * @return 返回类型为String,返回编码后的字符串。
     * @since 0.37.2
     */
    public static func encode(s: String): String {
        var needToChange: Bool = false
        let out: StringBuilder = StringBuilder()
        let cas: CharArrayStream = CharArrayStream(512)

        let charBuf: Array<Rune> = s.toRuneArray()
        let len: Int64 = charBuf.size
        var i: Int64 = 0
        while (i < len) {
            var ch: Rune = charBuf[i]
            var c: Int64 = Int64(UInt32(ch))
            if (DONT_NEED_ENCODING.get(c)) {
                if (ch == r' ') {
                    ch = r'+'
                    needToChange = true
                }
                out.append(ch)
                i++
            } else {
                i = convertToExternalEncoding(cas, ch, c, len, i, charBuf)

                handleCharArrayStream(out, cas)
                needToChange = true
            }
        }
        if (needToChange) {
            return out.toString()
        }
        return s
    }

    // 在十六进制转换之前转换为外部编码。
    private static func convertToExternalEncoding(cas: CharArrayStream, char: Rune, numCh: Int64,
                                                len: Int64, i: Int64, charBuf: Array<Rune>): Int64 {
        var index: Int64 = i
        var ch: Rune = char
        var numChar: Int64 = numCh
        do {
            cas.write(ch)
            if (numChar >= 0xD800 && numChar <= 0xDBFF && (index + 1) < len) {
                var dChar: Rune = charBuf[index + 1]
                let dNum: Int64 = Int64(UInt32(dChar))
                if (dNum >= 0xDC00 && dNum <= 0xDFFF) {
                    cas.write(dChar)
                    index++
                }
            }
            index++

            if (index < len) {
                ch = charBuf[index]
                numChar = Int64(UInt32(ch))
            } else {
                break
            }
        } while (!DONT_NEED_ENCODING.get(numChar))
        return index
    }

    private static func handleCharArrayStream(out: StringBuilder, cas: CharArrayStream): Unit {
        let str: String = String(cas.toCharArray())
        let ba: Array<UInt8> = str.toArray()

        for (j in 0 .. ba.size) {
            out.append('%')
            var baChar: Rune = CharacterUtil.forDigit(Int64((ba[j] >> 4) & 0xF), 16)
            // 如果ch是字母,则转换为使用大写字母作为十六进制值的一部分。
            if (isLetter(baChar)) {
                baChar = Rune(UInt32(baChar) - CASE_DIFF)
            }
            out.append(baChar)
            baChar = CharacterUtil.forDigit(Int64(ba[j] & 0xF), 16)
            if (isLetter(baChar)) {
                baChar = Rune(UInt32(baChar) - CASE_DIFF)
            }
            out.append(baChar)
        }
        cas.reset()
    }

    private static func isLetter(ch: Rune): Bool {
        return (ch >= r'a' && ch <= r'z') || (ch >= r'A' && ch <= r'Z')
    }

    /**
     * 使用特定编码方案解码application/x-www-form-urlencoded字符串。
     * 仅支持UTF-8字符集。
     *
     * @param 要转化的字符串.
     * @param 指定字符集
     *
     * @return 返回类型为String,返回解码后的字符串。
     */
    public static func decode(s: String, enc: String): String {
        if (enc.isEmpty()) {
            throw Exception("empty string enc parameter.");
        }
        if (!((enc == "UTF-8") || (enc == "utf-8") || (enc == "UTF8") || (enc == "utf8"))) {
            throw Exception("only UTF-8 Character Set is supported!")
        }

        try {
            return decode(s)
        } catch (e: Exception) {
            throw Exception(e.message)
        }
    }

    /**
     * 使用特定字符集解码application/x-www-form-urlencoded字符串。
     * 提供的字符集用于确定由"%xy"形式的连续序列表示的字符。
     * 仅支持UTF-8字符集。
     *
     * @param 要转化的字符串.
     *
     * @return 返回类型为String,返回解码后的字符串。
     */
    public static func decode(s: String): String {
        var needToChange: Bool = false
        let numChars: Int64 = s.size
        let sb: StringBuilder = StringBuilder()
        var i: Int64 = 0

        var c: Rune = ' '
        var bytes: Array<UInt8> = Array<UInt8>()

        let charBuf: Array<Rune> = s.toRuneArray()
        while (i < numChars) {
            c = charBuf[i]
            match (c) {
                case '+' =>
                    sb.append(' ')
                    i++
                    needToChange = true
                case '%' =>
                    try {
                        // (numChars-i)/3是剩余字节的上限。
                        if (bytes.size == 0) {
                            bytes = Array<UInt8>((numChars - i) / 3, repeat: 0)
                        }
                        i = handlePercentageSign(s, i, numChars, c, bytes, charBuf, sb)
                    } catch (e: IllegalArgumentException) {
                        throw e
                    } catch (e: Exception) {
                        let err: String = "Illegal hex characters in escape (%) pattern - " + e.message
                        throw IllegalArgumentException(err)
                    }
                    needToChange = true
                case _ =>
                    sb.append(c)
                    i++
            }
        }
        if (needToChange) {
            return sb.toString()
        }
        return s
    }

    private static func handlePercentageSign(s: String, index: Int64, numChars: Int64, char: Rune,
                                            bytes: Array<UInt8>, charBuf: Array<Rune>, sb: StringBuilder): Int64 {
        var pos: Int64 = 0
        var i: Int64 = index
        var c: Rune = char
        while ( ((i + 2) < numChars) && (c == r'%')) {
            let strNum: String = "0X" + s[(i+1)..(i+3)]
            let v: Int64 = Int64.parse(strNum)

            if (v < 0) {
                let err: String = "Illegal hex characters in escape " + "(%) pattern - negative value."
                throw IllegalArgumentException(err)
            }
            bytes[pos] = UInt8(v)
            pos++
            i += 3
            if (i < numChars) {
                c = charBuf[i]
            }
        }

        // 结尾不完整的字节编码(如"%x")将导致抛出异常。
        if (i < numChars && c == r'%') {
            throw IllegalArgumentException("Incomplete trailing escape (%) pattern.")
        }

        sb.append(String.fromUtf8(bytes[0..pos]))
        return i
    }
}