/**
 * @file
 * The file declares the Util class.
 */

package httpclient4cj

internal import stdx.compress.zlib.*
internal import stdx.crypto.crypto.*
internal import stdx.encoding.hex.*
internal import stdx.encoding.url.*
internal import stdx.log.*
internal import stdx.net.http.*
internal import stdx.net.http.CookieJar as HttpCookieJar
internal import stdx.net.tls.*
internal import stdx.net.tls.common.*
internal import std.collection.*
internal import std.collection.concurrent.ConcurrentHashMap
internal import std.convert.*
internal import std.fs.{Directory, File, FileInfo, FSException, OpenMode, Path}
internal import std.io.*
internal import std.math.*
internal import std.net.*
internal import std.random.*
internal import std.regex.*
internal import std.sync.*
internal import std.time.*
internal import std.unicode.*

let METHOD_GET = "GET"

/**
 * The class is Util
 * @author guo_tingtingtekla,luoyukai4
 * @since 0.29.3
 */
class Util {
    private static let VERIFY_AS_IP_ADDRESS = "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"

    /**
     * The Function is closeQuietly
     *
     * @param socket of Option<Socket>
     * @since 0.29.3
     */
    static func closeQuietly<T>(socket: Option<T>): Unit where T <: Resource {
        match (socket) {
            case Some(s) => try {
                if (!s.isClosed()) {
                    s.close()
                }
            } catch (ignored: Exception) {
                throw ignored
            }
            case None => ()
        }
    }

    /**
     * The Function is sameConnection
     *
     * @param a of URL
     * @param b of URL
     *
     * @return Type of Bool
     * @since 0.29.3
     */
    static func sameConnection(a: URL, b: URL): Bool {
        return a.hostName == b.hostName && a.port == b.port && a.scheme == b.scheme
    }

    static func skipUntil(input: String, pos: Int64, characters: String): Int64 {
        var posi: Int64 = pos

        while (posi < input.size) {
            let res: Int64 = characters.indexOf(input[Int64(posi)]) ?? -1
            if (res != -1) {
                break
            }
            posi++
        }

        return posi
    }

    static func skipWhitespace(input: String, pos: Int64): Int64 {
        var posi: Int64 = pos

        while (posi < input.size) {
            let c = input[posi]
            if (c != b' ' && c != b'\t') {
                break
            }
            posi++
        }

        return posi
    }

    static func parseSeconds(value: String, defaultValue: Int64): Int64 {
        try {
            let seconds: Int = Int64.parse(value)
            if (seconds < 0) {
                return 0
            } else {
                return seconds
            }
        } catch (e: Exception) {
            return defaultValue
        }
    }

    static func verifyAsIpAddress(host: String): Bool {
        match (Regex(VERIFY_AS_IP_ADDRESS).find(host)) {
            case Some(_) => return true
            case None => return false
        }
    }
}

func addLenient(header: Header, line: String) {
    match (line.indexOf(":", 1)) {
        case Some(index) => header.add(line.substring(0, index), line.substring(index + 1).trim())
        case None => if (!line.startsWith(":")) {
            header.add("", line)
        } else {
            throw ProtocolException("Unexpected header: ${line}")
        }
    }
}

/*
 * The Function is receiveHeaders
 *
 * @param cookieJar of CookieJar
 * @param url of URL
 * @param header of Header
 *
 * @return Type of Unit
 * @since 0.29.3
 */
func receiveHeaders(cookieJar: CookieJar, url: URL, headers: Header): Unit {
    match (cookieJar) {
        case _: NoCookieJar => return
        case _ => ()
    }

    let cookies: ArrayList<Cookie> = CookieUtil.parseAll(url, headers)

    if (cookies.isEmpty()) {
        return
    }
    cookieJar.saveFromResponse(url, cookies.toArray())
}

func disCard(source: Source): Bool {
    try {
        let arr = ByteBuffer()
        while (source.read(arr, 4096) != -1) {
            arr.clear()
        }
        return true
    } catch (e: SocketTimeoutException) {
        return false
    }
}

func equalsIgnoreCase(left: String, right: String): Bool {
    return left.toUpper() == right.toUpper()
}

func UInt64ToHexString(n: UInt64): Array<Byte> {
    var num: UInt64 = n
    var arrSize: Int64 = 0

    for (_ in 0..16) {
        if (num > 0) {
            arrSize++
            num = num >> 4
        } else {
            break
        }
    }

    var arr = Array<Byte>(arrSize, repeat: UInt8(CHAR_ZERO))
    var mask: UInt64 = 0x0f
    num = n

    for (i in arrSize - 1..-1 : -1) {
        if (num > 0) {
            var item = num & mask
            arr[i] = UInt64ToHex[Int64(item)]
            num = num >> 4
        } else {
            break
        }
    }
    return arr
}

func HexStringToUInt64(arr: Array<Byte>): Option<UInt64> {
    var arrSize = arr.size
    var num: UInt64 = 0

    for (i in 0..arrSize) {
        var b = arr[i]
        if (b'0' <= b && b <= b'9') {
            b = b - b'0'
        } else if (b'a' <= b && b <= b'f') {
            b = b - (b'a' - 10)
        } else if (b'A' <= b && b <= b'F') {
            b = b - (b'A' - 10)
        } else {
            return None
        }
        num = num * 16 + UInt64(b)
    }
    return Some(num)
}

func has<T>(op: Option<T>): Bool {
    match (op) {
        case Some(_) => return true
        case None => return false
    }
}

extend Int64 {
    operator func >=(right: UInt16): Bool {
        this >= Int64(right)
    }
    operator func !=(right: UInt16): Bool {
        this != Int64(right)
    }
    operator func ==(right: UInt16): Bool {
        this == Int64(right)
    }
    operator func <(right: UInt16): Bool {
        this < Int64(right)
    }
}

extend Cookie {
    prop name: String {
        get() {
            this.cookieName
        }
    }
    prop value: String {
        get() {
            this.cookieValue
        }
    }
}

extend Header {
    public func clone(): Header {
        let dest = Header()
        for ((k, v) in this) {
            dest.set(k, v)
        }
        dest
    }
    public func remove(k: String): Unit {
        this.del(k)
    }
    public func getString(k: String): ?String {
        match (this.get(k)) {
            case Some(v) => v |> collectString<String>(delimiter: "; ")
            case _ => None
        }
    }
    public func getAll(k: String): ArrayList<String> {
        this.get(k) ?? this.get(k.toLower()) ?? ArrayList()
    }
}

public class Header <: Iterable<(String, ArrayList<String>)> {
    let fields = HashMap<String, ArrayList<String>>()
    let httpHeaders = HttpHeaders()

    public init() {}
    init(fields: Array<HeaderField>) {
        for (f in fields) {
            this.set(f.name, toValueArr(f.value))
        }
    }
    private func toValueArr(value: String): ArrayList<String> {
        value.split(";") |> map {s: String => s.trim()} |> collectArrayList
    }
    public func add(name: String, value: String): Unit {
        if (name.startsWith(":")) {
            let list = fields.get(name) ?? ArrayList<String>()
            list.add(value.trim())
            this.fields.add(name.toLower(), list)
        } else {
            httpHeaders.add(name, value)
        }
    }
    public func set(name: String, value: ArrayList<String>): Unit {
        if (name.startsWith(":")) {
            this.fields.add(name.toLower(), value)
        } else {
            httpHeaders.del(name)
            for (v in value) {
                httpHeaders.add(name, v)
            }
        }
    }
    public func get(name: String): ?ArrayList<String> {
        let arr = httpHeaders.get(name)
        if (arr.size != 0) {
            return arr |> collectArrayList
        }
        return fields.get(name)
    }
    public func del(name: String): Unit {
        fields.remove(name)
        httpHeaders.del(name)
    }
    public func iterator(): Iterator<(String, ArrayList<String>)> {
        httpHeaders |>
            map<(String, Collection<String>), (String, ArrayList<String>)> {kv => (kv[0], kv[1] |> collectArrayList)} |>
            concat(fields)
    }
}

foreign func htons(port: UInt16): UInt16
public func portTrans(port: UInt16): UInt16 {
    // unsafe { htons(port) }
    port
}

let LOG: Logger = getGlobalLogger()


extend String {
    func equals(s: String): Bool {
        this == s
    }
    func toUtf8Array(): Array<UInt8> {
        this.toArray()
    }
    func substring(start: Int64, size: Int64): String {
        this[start..start + size]
    }
    func substring(start: Int64): String {
        this[start..]
    }
    func contains(char: Rune): Bool {
        this.contains(char.toString())
    }
}
extend UInt8 {
    operator func ==(char: Rune): Bool {
        UInt32(this) == UInt32(char)
    }
}