package net.taehui.twilight.system

import com.fasterxml.jackson.databind.ObjectMapper
import net.taehui.twilight.JSON
import net.taehui.twilight.Logger
import net.taehui.twilight.TwilightComponent
import net.taehui.twilight.Utility
import org.apache.hc.client5.http.classic.methods.HttpGet
import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler
import org.apache.hc.client5.http.impl.classic.HttpClients
import org.jsoup.Jsoup
import java.io.IOException
import java.nio.file.Files

object AbilitySystem : Logger {
    class DefaultAbility {
        var abilityFiles = emptyArray<AbilityFile>()
        var abilityWww = emptyArray<String>()
        var abilityMap = emptyMap<String, Double>()

        class AbilityFile {
            var fileName = ""
            var abilityClassVariety = AbilityClassSystem.AbilityClassVariety.ABILITY_CLASS_5K
        }
    }

    class AbilityHashMap {
        val hashMap128 = mutableMapOf<String, Double>()
        val hashMap256 = mutableMapOf<String, Double>()

        fun wipe() {
            hashMap128.clear()
            hashMap256.clear()
        }
    }

    private val abilityHashMap = arrayOf(AbilityHashMap(), AbilityHashMap(), AbilityHashMap())
    var isLoading = false

    fun loadAbility() {
        isLoading = true
        logFuture {
            try {
                val jm = ObjectMapper()
                val defaultAbility = jm.readValue(
                    TwilightComponent.ABILITY_ENTRY_PATH.resolve("Default.json").toFile(),
                    DefaultAbility::class.java
                )
                if (Configure.mode.getAbility) {
                    defaultAbility.abilityWww.forEach { target ->
                        try {
                            val o = Jsoup.connect(target).get()
                            var dataValue = o.selectXpath("/html/head/meta[@name='bmstable']")
                            if (dataValue.isEmpty()) {
                                dataValue = o.selectXpath("/html/body/meta[@name='bmstable']")
                            }
                            if (dataValue.isEmpty()) {
                                dataValue = o.selectXpath("/html/head/body/meta[@name='bmstable']")
                            }
                            if (!dataValue.isEmpty()) {
                                val www = dataValue[0].attr("content")
                                HttpClients.createDefault().use {
                                    fun doModifyDataValue(target: String, dataValue: String): String {
                                        return if (!Utility.isValidWww(dataValue)) {
                                            if (target.substring(target.lastIndexOf('/'))
                                                    .contains(".") || target.endsWith("/")
                                            ) {
                                                "$target/../$dataValue"
                                            } else {
                                                "$target/$dataValue"
                                            }
                                        } else {
                                            dataValue
                                        }
                                    }

                                    val text = it.execute(
                                        HttpGet(doModifyDataValue(target, www)),
                                        BasicHttpClientResponseHandler()
                                    )
                                    val abilityTable = text.byteInputStream().use { src ->
                                        ObjectMapper().readValue(src, JSON.BMSTable::class.java)
                                    }
                                    Files.writeString(
                                        TwilightComponent.ABILITY_ENTRY_PATH.resolve("#" + abilityTable.name + ".json"),
                                        text
                                    )
                                    Files.writeString(
                                        TwilightComponent.ABILITY_ENTRY_PATH.resolve(abilityTable.name + ".json"),
                                        it.execute(
                                            HttpGet(doModifyDataValue(target, abilityTable.data_url)),
                                            BasicHttpClientResponseHandler()
                                        )
                                    )
                                    logInfo("Saved Ability (${abilityTable.name})")
                                }
                            }
                        } catch (e: IOException) {
                            logFault(e)
                        }
                    }
                }
                try {
                    val abilityMap = defaultAbility.abilityMap
                    abilityHashMap[AbilityClassSystem.AbilityClassVariety.ABILITY_CLASS_5K.ordinal].wipe()
                    abilityHashMap[AbilityClassSystem.AbilityClassVariety.ABILITY_CLASS_7K.ordinal].wipe()
                    Files.list(TwilightComponent.ABILITY_ENTRY_PATH).use { abilityFilePaths ->
                        abilityFilePaths.filter { it.fileName.toString().startsWith("#") }.sorted(
                            Comparator.comparingInt { abilityFilePath ->
                                defaultAbility.abilityFiles.indexOfFirst {
                                    it.fileName == abilityFilePath.fileName.toString()
                                }
                            }
                        ).forEach { abilityFilePath ->
                            val abilityClassVariety = defaultAbility.abilityFiles.find {
                                it.fileName == abilityFilePath.fileName.toString()
                            }?.abilityClassVariety
                            if (abilityClassVariety != null) {
                                val abilityHashMap = abilityHashMap[abilityClassVariety.ordinal]
                                val abilityTable = jm.readValue(abilityFilePath.toFile(), JSON.BMSTable::class.java)
                                jm.readValue(
                                    TwilightComponent.ABILITY_ENTRY_PATH.resolve(abilityTable.name + ".json")
                                        .toFile(),
                                    Array<JSON.BMSTableData>::class.java
                                ).forEach {
                                    val ability = abilityMap[abilityTable.symbol + it.level] ?: 0.0
                                    if (ability > 0.0) {
                                        if (it.md5.isNotEmpty()) {
                                            abilityHashMap.hashMap128.putIfAbsent(it.md5, ability)
                                        }
                                        if (it.sha256.isNotEmpty()) {
                                            abilityHashMap.hashMap256.putIfAbsent(it.sha256, ability)
                                        }
                                    }
                                }
                                logInfo("Loaded Ability (${abilityTable.name})")
                            }
                        }
                    }
                } catch (e: IOException) {
                    logFault(e)
                }
                try {
                    DB.learnAbility()
                    logInfo("Learned Ability")
                } catch (e: Throwable) {
                    logFault(e)
                }
            } finally {
                isLoading = false
            }
        }
    }

    fun getAbility(
        abilityClassVariety: AbilityClassSystem.AbilityClassVariety,
        noteID128: String,
        noteID256: String
    ): Double {
        val abilityHashMap = abilityHashMap[abilityClassVariety.ordinal]
        return abilityHashMap.hashMap128.getOrDefault(noteID128, abilityHashMap.hashMap256.getOrDefault(noteID256, 0.0))
    }
}