package locale_config

import std.regex.{Regex,MatchData}
// import std.unittest
import std.collection.{collectArray, ArrayList}

var REGULAR_LANGUAGE_RANGE_REGEX: Option<Regex> = None
var LANGUAGE_RANGE_REGEX: Option<Regex> = None
var UNIX_INVARIANT_REGEX: Option<Regex> = None
var UNIX_TAG_REGEX: Option<Regex> = None

type RawL = String

extend MatchData{
    func matchStr(i:Int64){
        try {
            this.matchString(i)
        }catch(e:Exception){
            ""
        }
    }

    func matchStr(i:String){
        try{
            this.matchString(i)
        }catch(e:Exception){
            ""
        }
    }
}

/**
* \namespace locale_config
* @brief LocaleConfig
*/

func canon_lower(o: Option<String>) {
    match (o) {
        case None => ""
        case Some(s) => s.toAsciiLower()
    }
}

func canon_script(o: String) {
    // log.SimpleLogger().info("Canon script ${o}")
    if (o == "") {
        return ""
    }
    if (!(o.size >= 2 && o[0..1] == '-')) {
        throw IllegalFormatException()
    }
    if (o.get(1).getOrThrow().isAsciiUpperCase() && o.toRuneArray()[2..].iterator().all {t => t.isAsciiLowerCase()}) {
        o
    } else {
        "-" + o.toRuneArray()[1].toAsciiUpperCase().toString() + String(
            collectArray<Rune>(o.toRuneArray()[2..].iterator().map {s => s.toAsciiLowerCase()}))
    }
}

func canon_upper(o: String): String {
    if (o.size == 0) {
        return ""
    }
    if (!(o.size >= 2 && o[0..1] == "-")) {
        throw IllegalFormatException()
    }
    if (o.iterator().all {i => i.isAsciiLowerCase()}) {
        o
    } else {
        o.toAsciiUpper()
    }
}

/**
* @brief 语言与文化识别器。
* 这个类持有一个 [RFC4647] 所规定的语言范围。
* @see https://www.rfc-editor.org/rfc/rfc5646.txt
* @see https://www.rfc-editor.org/rfc/rfc4647.txt
*/
public struct LanguageRange {
    public var l: RawL
    /**
     * Construct LanguageRange from string, with normalization.
     * 
     * LanguageRange must follow the [RFC4647] syntax.
     * It will be case-normalized as recommended in [RFC5646] §2.1.1, namely:
     * 
     *  * `language`, if recognized, is written in lowercase,
     *  * `script`, if recognized, is written with first capital,
     *  * `country`, if recognized, is written in uppercase and
     *  * all other subtags are written in lowercase.
     *  
     * @see [RFC5646] https://www.rfc-editor.org/rfc/rfc5646.txt
     * @see [RFC4647] https://www.rfc-editor.org/rfc/rfc4647.txt
     */
    public init(lt: String, r!: Bool = false) {
        if (r) {
            this.l = lt
            return
        }
        if (lt == "") {
            this.l = lt
            return
        } else {
            if (REGULAR_LANGUAGE_RANGE_REGEX.isNone()) {
                REGULAR_LANGUAGE_RANGE_REGEX = Some(
                    Regex(
                        (#"^(?P<language>(?:[[:alpha:]]{2,3}(?:-[[:alpha:]]{3}){0,3}|\*))(?P<script>-(?:[[:alpha:]]{4}|\*))?(?P<region>-(?:[[:alpha:]]{2}|[[:digit:]]{3}|\* ))?(?P<rest>(?:-(?:[[:alnum:]]{1,8}|\*))*)$"#)
                    ))
            }
            let m = REGULAR_LANGUAGE_RANGE_REGEX.getOrThrow().find(lt,group:true)
            if (m.isSome()) {
                let m = m.getOrThrow()
                // log.getGlobalLogger().info("Regex ${m.groupNumber()} L:${m.matchStr(1)} S:${if(m.groupNumber()>=2){m.matchStr(2)}else{" "}} R:${if(m.groupNumber()>=3){m.matchStr(3)}else{" "}} r:${if(m.groupNumber()>=4){m.matchStr(4)}else{""}}")
                let lang = canon_lower(m.matchString("language"))
                // println("Regex ${m.groupNumber()} L:${m.matchString(1)} S:${if(m.groupNumber()>=2){m.matchString(2)}else{" "}} R:${if(m.groupNumber()>=3){m.matchString(3)}else{" "}} r:${if(m.groupNumber()>=4){m.matchString(4)}else{""}}")
                let script = canon_script(if (m.groupCount() >= 2) {
                    m.matchStr("script")
                } else {
                    ""
                })
                let region = canon_upper(if (m.groupCount() >= 3) {
                    m.matchStr("region")
                } else {
                    ""
                })

                let rest = canon_lower(if (m.groupCount() >= 4) {
                    m.matchStr("rest")
                } else {
                    ""
                })
                // log.SimpleLogger().info("L:${lang} S:${script} R:${region} r:${rest}")
                this.l = lang + script + region + rest
            } else {
                if (LANGUAGE_RANGE_REGEX.isNone()) {
                    LANGUAGE_RANGE_REGEX = Regex(#"^(?:[[:alpha:]]{1,8}|\*)(?:-(?: [[:alnum:]]{1,8}|\*))*$"#)
                }
                match (LANGUAGE_RANGE_REGEX.getOrThrow().find(lt,group:true)) {
                    case Some(_) => this.l = canon_lower(lt)
                    case None => throw IllegalFormatException("Not well formed languague range")
                }
            }
        }
    }

    public static func New(lt: String) {
        return LanguageRange(lt).toString()
    }
    /**
     * @brief 返回用于默认语言环境的 LanguageRange。
     * 
     * 默认语言通过空字符串简单标识。
     */
    public static func invariant() {
        LanguageRange("")
    }

    /**
     * @brief 从 Unix/Linux/GNU 本地化标签创建语言标签。   
     *
     * Unix 本地化标签的格式为
     *
     * > *语言* [ `_` *区域* ] [ `.` *编码* ] [ `@` *变体* ]   
     *
     * @note 语言 和 区域 的格式与 RFC5646 相同。编码 在这里不相关,因为 仓颉 始终使用 Utf-8。剩下的 变体,不幸的是,它的格式相对自由。因此,这个函数将已知的变体转换为相应的 RFC5646 子标签,并用 Unicode POSIX 变体(`-u-va-`)扩展表示其他任何内容。
     *
     * @note 这个函数在这里是公开的,以便那些可能遇到来自于系统配置之外的这种标签的应用程序使用。
     */
    public static func fromUnix(s: String): LanguageRange {
        if (UNIX_TAG_REGEX.isNone()) {
            UNIX_TAG_REGEX = Regex(
                #"^(?P<language>[[:alpha:]]{2,3})(?:_(?P<region>[[:alpha:]]{2}|[[:digit:]]{3}))?(?:\.(?P<encoding>[0-9a-zA-Z-]{1,20}))?(?:@(?P<variant>[[:alnum:]]{1,20}))?$"#
            )
        }
        match (UNIX_TAG_REGEX.getOrThrow().find(s,group:true)) {
            case Some(caps) =>
                let src_variant = if (caps.groupCount() >= 4) {
                    caps.matchStr(4).toAsciiLower()
                } else {
                    ""
                }
                var res = caps.matchStr(1).toAsciiLower()
                let region = caps.matchStr(2)
                var script = ""
                var variant = ""
                var uvariant = ""
                match (src_variant) {
                    case "arabic" => script = "Arab"
                    case "cyrl" => script = "Cyrl"
                    case "cyrillic" => script = "Cyrl"
                    case "devanagari" => script = "Deva"
                    case "hebrew" => script = "Hebr"
                    case "iqtelif" => script = "Latn"
                    case "latn" => script = "Latn"
                    case "latin" => script = "Latn"
                    case "shaw" => script = "Shaw"
                    case "ijekavianlatin" =>
                        script = "Latn"
                        variant = "ijekavsk"
                    case "ije" => variant = "ijekavsk"
                    case "ijekavian" => variant = "ijekavsk"
                    case "valencia" => variant = "valencia"
                    // case "euro"=> 
                    case s =>
                        if (s != "euro") {
                            if (s.size <= 8) {
                                uvariant = s
                            } else {
                                uvariant = s[0..8]
                            }
                            if (res == "aa" && s == "saaho") {
                                res = "ssy"
                            }
                        }
                }
                if (script != "") {
                    res += "-"
                    res += script
                }
                if (region != "") {
                    res += "-"
                    res += region.toAsciiUpper()
                }
                if (variant != "") {
                    res += "-"
                    res += variant
                }
                if (uvariant != "") {
                    res += "-u-va-"
                    res += uvariant
                }
                LanguageRange(res, r: true)
            case None =>
                if (UNIX_INVARIANT_REGEX.isNone()) {
                    UNIX_INVARIANT_REGEX = Regex(#"^(?:c|posix)(?:\.(?:[0-9a-zA-Z-]{1,20}))?$"#)
                }
                match (UNIX_INVARIANT_REGEX.getOrThrow().find(s,group:true)) {
                    case Some(_) => LanguageRange.invariant()
                    case None => throw IllegalFormatException("Not well formed")
                }
        }
    }
}

extend LanguageRange <: ToString {
    public func toString(): String {
        this.l
    }
}

extend LanguageRange <: Equatable<LanguageRange> {
    public operator func ==(l: LanguageRange) {
        this.toString() == l.toString()
    }
}

var LOCALE_ELEMENT_REGEX: Option<Regex> = None

/**
 * @brief 本地化配置。
 *
 * 
 */
public class Locale {
    public var inner: String
    init(i: String) {
        this.inner = i
    }
    /**
    * @brief 从字符串创建 Locale 配置。
    */
    public static func new(s: String): Locale {
        var i = s.lazySplit(",")
        var res = Locale.from(LanguageRange(i.next().getOrThrow()))
        if (LOCALE_ELEMENT_REGEX.isNone()) {
            LOCALE_ELEMENT_REGEX = Regex(#"^(?:(?P<category>[[:alpha:]]{1,20})=)?(?P<tag>(?:[[:alnum:]]|-|\*)+)$"#)
        }
        for (f in i) {
            if (let Some(caps) <- LOCALE_ELEMENT_REGEX.getOrThrow().find(f,group:true)) {
                let tag = LanguageRange(caps.matchStr("tag"))
                if (caps.matchStr("category").size != 0) {
                    let cat = caps.matchStr("category")
                    res.addCategory(cat.toAsciiLower(), tag.toString())
                } else {
                    res.add(tag)
                }
            } else {
                throw IllegalArgumentException("Not well formed")
            }
        }
        res
    }

    public func add(tag: LanguageRange): Unit {
        for (i in this.inner.lazySplit(',')) {
            if (i == tag.toString()) {
                return
            }
        }
        this.inner += ",${tag}"
    }

    public func toTags() {
        Tags(this.inner.split(","))
    }

    public func addCategory(category: String, tag: String): Unit {
        if (this.inner.lazySplit(',').next() == tag) {
            return
        }
        for (i in this.inner.lazySplit(',')) {
            if (i.startsWith(category) && i[category.size..].startsWith("=") && i[category.size + 1..] == tag) {
                return
            }
        }
        this.inner += ",${category}=${tag}"
    }

    public func toTagsFor(category: String) {
        var tags = this.inner.split(",")
        let ti1 = tags.iterator()
        let ti2 = tags.iterator()
        while (let Some(s) <- ti2.next()) {
            if (s.startsWith(category) && s[category.size..].startsWith("=")) {
                return TagsFor(this.inner, collectArray<String>(ti1), category)
            }
            ti1.next()
        }
        TagsFor(this.inner, this.inner.split(","), None)
    }

    public static func from(lr: LanguageRange): Locale {
        Locale(lr.toString())
    }

    public static func invariant(): Locale {
        Locale.from(LanguageRange.invariant())
    }
}


extend Locale <: ToString {
    public func toString() {
        this.inner
    }
}

public struct TagsFor {
    public let src: String
    public let tags: Array<String>
    public let category: Option<String>
    public init(src: String, tags: Array<String>, category: Option<String>) {
        this.src = src
        this.tags = tags
        this.category = category
    }
}

extend TagsFor <: Iterable<TagsForIteratorItem> {
    public func iterator() {
        TagsForIterator(this)
    }
}

private type TagsIteratorItemOld = (Option<String>, LanguageRange)

public class TagsIteratorItem {
    public var category: Option<String>
    public var tag: LanguageRange
    public init(category: Option<String>, tag: LanguageRange) {
        this.category = category
        this.tag = tag
    }
}

extend TagsIteratorItem <: Equatable<TagsIteratorItem> {
    public operator func ==(right: TagsIteratorItem) {
        this.category == right.category && this.tag == right.tag
    }
}

extend TagsIteratorItem <: Equatable<TagsIteratorItemOld> {
    public operator func ==(right: TagsIteratorItemOld): Bool {
        this.category == right[0] && this.tag == right[1]
    }
}

public class TagsIterator <: Iterator<TagsIteratorItem> {
    let tags: Iterator<String>
    public init(t: Tags) {
        this.tags = t.tags.iterator()
    }
    public func next(): Option<TagsIteratorItem> {
        if (let Some(s) <- this.tags.next()) {
            let f = {
                =>
                var st = s.toRuneArray()
                for (i in 0..st.size) {
                    if (st[i] == r'=') {
                        return Some(i)
                    }
                }
                Option<Int64>.None
            }()
            if (let Some(i) <- f) {
                TagsIteratorItem(String(s.toRuneArray()[..i]), LanguageRange(String(s.toRuneArray()[i + 1..]), r: true))
            } else {
                TagsIteratorItem(None, LanguageRange(s))
            }
        } else {
            None
        }
    }
}

public struct Tags {
    public let tags: Array<String>
    public init(tags: Array<String>) {
        this.tags = tags
    }
}

extend Tags <: Iterable<TagsIteratorItem> {
    public func iterator() {
        TagsIterator(this)
    }
}

type TagsForIteratorItem = LanguageRange

public class TagsForIterator <: Iterator<TagsForIteratorItem> {
    var tags: Iterator<String>
    var category: Option<String>
    let src: String
    public init(tf: TagsFor) {
        this.tags = tf.tags.iterator()
        this.category = tf.category
        this.src = tf.src
    }
    public func next(): Option<TagsForIteratorItem> {
        if (let Some(cat) <- this.category) {
            while (let Some(s) <- this.tags.next()) {
                if (s.startsWith(cat) && s[cat.size..].startsWith("=")) {
                    return LanguageRange(s[cat.size + 1..])
                }
            }
            this.category = None
            this.tags = this.src.split(",").iterator()
        }
        while (let Some(s) <- this.tags.next()) {
            let f = {
                =>
                var st = s.toRuneArray()
                for (i in 0..st.size) {
                    if (st[i] == r'=') {
                        return Some(i)
                    }
                }
                Option<Int64>.None
            }()
            if (f.isNone()) {
                return Some(LanguageRange(s))
            }
        }
        None
    }
}

let USER_LOCALE = system_locale()

var GLOBAL_LOCALE = USER_LOCALE

extend Locale{
    /**
    * @brief 由操作系统提供的 Locale 配置。
    */
    public static prop userDefault:Option<Locale>{
        get(){
            USER_LOCALE
        }
    }

    /**
    * @brief 全局 Locale 配置,默认为 locale_config.Locale.userDefault 。
    */
    public static mut prop globalDefault:Option<Locale>{
        get(){
            GLOBAL_LOCALE
        }
        set(v){
            GLOBAL_LOCALE = v
        }
    }
}
let CURRENT_LOCALE = ThreadLocal<Locale>()

extend Locale{
    /**
    * @brief 获取当前线程的 Locale 配置,默认下为 locale_config.Locale.globalDefault 。
    * @note 当被赋值为None时,current将被设置为 locale_config.Locale.globalDefault 的副本。
    */
    public static mut prop current:Option<Locale>{
        get(){
            if(let Some(l)<-CURRENT_LOCALE.get()){
                l
            }else{
                GLOBAL_LOCALE
            }
        }
        set(v){
            if(v.isNone()){
                CURRENT_LOCALE.set(GLOBAL_LOCALE)
            }else{
                CURRENT_LOCALE.set(v)
            }
        }
    }
}