Newer
Older
Twilight / src / main / kotlin / net / taehui / twilight / system / AbilitySystem.kt
@Taehui Taehui on 18 Jul 10 KB Revert "v1.0-SNAPSHOT"
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.commons.io.FilenameUtils
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
import java.util.TreeSet
import java.util.concurrent.atomic.AtomicInteger

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.ETC
        }
    }

    class AbilityMap<T> {
        val noteID128Map = mutableMapOf<String, T>()
        val noteID256Map = mutableMapOf<String, T>()

        fun wipe() {
            noteID128Map.clear()
            noteID256Map.clear()
        }

        fun getValue(noteID128: String, noteID256: String, defaultValue: T): T {
            return noteID128Map.getOrDefault(noteID128, noteID256Map.getOrDefault(noteID256, defaultValue))
        }
    }

    private val noteIDAbilityMap = mapOf<AbilityClassSystem.AbilityClassVariety, AbilityMap<Double>>(
        Pair(AbilityClassSystem.AbilityClassVariety.ETC, AbilityMap()),
        Pair(AbilityClassSystem.AbilityClassVariety.INPUT_MODE_6K, AbilityMap()),
        Pair(AbilityClassSystem.AbilityClassVariety.INPUT_MODE_5K, AbilityMap()),
        Pair(AbilityClassSystem.AbilityClassVariety.INPUT_MODE_7K, AbilityMap()),
        Pair(AbilityClassSystem.AbilityClassVariety.INPUT_MODE_9K, AbilityMap())
    )
    private val noteIDAbilityIDsMap = AbilityMap<MutableCollection<String>>()
    private val noteIDAbilityNamesMap = AbilityMap<MutableCollection<String>>()
    private val abilityNameAbilityIDsMap = mutableMapOf<String, MutableCollection<String>>()
    private val abilityNameAbilityIDNoteIDCountMap = mutableMapOf<String, MutableMap<String, AtomicInteger>>()

    private fun getAbility(defaultAbility: DefaultAbility) {
        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)
            }
        }
    }

    private fun loadAbility(defaultAbility: DefaultAbility) {
        val jm = ObjectMapper()
        val abilityMap = defaultAbility.abilityMap
        noteIDAbilityMap[AbilityClassSystem.AbilityClassVariety.ETC]?.wipe()
        noteIDAbilityMap[AbilityClassSystem.AbilityClassVariety.INPUT_MODE_6K]?.wipe()
        noteIDAbilityMap[AbilityClassSystem.AbilityClassVariety.INPUT_MODE_5K]?.wipe()
        noteIDAbilityMap[AbilityClassSystem.AbilityClassVariety.INPUT_MODE_7K]?.wipe()
        noteIDAbilityMap[AbilityClassSystem.AbilityClassVariety.INPUT_MODE_9K]?.wipe()
        noteIDAbilityIDsMap.wipe()
        noteIDAbilityNamesMap.wipe()
        abilityNameAbilityIDsMap.clear()
        abilityNameAbilityIDNoteIDCountMap.clear()
        defaultAbility.abilityFiles.forEach { abilityFile ->
            val abilityFilePath = TwilightComponent.ABILITY_ENTRY_PATH.resolve(abilityFile.fileName)
            val abilityFileName = abilityFilePath.fileName.toString()
            val abilityName = FilenameUtils.removeExtension(
                abilityFileName.substring(abilityFileName.indexOf('#') + 1)
            )
            val noteIDAbilityMap = noteIDAbilityMap[abilityFile.abilityClassVariety]
            val abilityTable =
                jm.readValue(abilityFilePath.toFile().absoluteFile, JSON.BMSTable::class.java)
            val levels =
                abilityTable.level_order.map { "${abilityTable.symbol}$it" }.toTypedArray()
            jm.readValue(
                TwilightComponent.ABILITY_ENTRY_PATH.resolve("$abilityName.json")
                    .toFile().absoluteFile,
                Array<JSON.BMSTableData>::class.java
            ).forEach { abilityTableData ->
                val abilityID = "${abilityTable.symbol}${abilityTableData.level}"
                val ability = abilityMap[abilityID] ?: 0.0
                if (ability > 0.0) {
                    if (abilityTableData.md5.isNotEmpty()) {
                        noteIDAbilityMap?.noteID128Map?.putIfAbsent(abilityTableData.md5, ability)
                    }
                    if (abilityTableData.sha256.isNotEmpty()) {
                        noteIDAbilityMap?.noteID256Map?.putIfAbsent(abilityTableData.sha256, ability)
                    }
                }
                if (abilityTableData.md5.isNotEmpty()) {
                    noteIDAbilityIDsMap.noteID128Map.computeIfAbsent(
                        abilityTableData.md5
                    ) { mutableListOf() }.add(abilityID)
                    noteIDAbilityNamesMap.noteID128Map.computeIfAbsent(
                        abilityTableData.md5
                    ) { mutableListOf() }.add(abilityName)
                }
                if (abilityTableData.sha256.isNotEmpty()) {
                    noteIDAbilityIDsMap.noteID256Map.computeIfAbsent(
                        abilityTableData.sha256
                    ) { mutableListOf() }.add(abilityID)
                    noteIDAbilityNamesMap.noteID256Map.computeIfAbsent(
                        abilityTableData.md5
                    ) { mutableListOf() }.add(abilityName)
                }
                abilityNameAbilityIDsMap.computeIfAbsent(abilityName) {
                    if (levels.isEmpty()) mutableSetOf() else TreeSet { o1, o2 ->
                        levels.indexOf(o1).compareTo(levels.indexOf(o2))
                    }
                }.add(abilityID)
                abilityNameAbilityIDNoteIDCountMap.computeIfAbsent(abilityName) {
                    mutableMapOf()
                }.computeIfAbsent(abilityID) {
                    AtomicInteger()
                }.incrementAndGet()
            }
            logInfo("Loaded Ability ($abilityName.json)")
        }
        DB.learnAbility()
        logInfo("Learned Ability")
    }

    fun load(getAbility: Boolean) {
        val jm = ObjectMapper()
        val defaultAbility = jm.readValue(
            TwilightComponent.ABILITY_ENTRY_PATH.resolve("Default.json").toFile().absoluteFile,
            DefaultAbility::class.java
        )
        if (getAbility) {
            getAbility(defaultAbility)
        }
        loadAbility(defaultAbility)
    }

    fun getAbility(
        abilityClassVariety: AbilityClassSystem.AbilityClassVariety,
        noteID128: String,
        noteID256: String
    ): Double {
        return noteIDAbilityMap[abilityClassVariety]?.getValue(noteID128, noteID256, 0.0) ?: 0.0
    }

    fun getAbilityIDs(
        noteID128: String,
        noteID256: String
    ): Collection<String> {
        return noteIDAbilityIDsMap.getValue(noteID128, noteID256, mutableListOf())
    }

    fun getAbilityNames(
        noteID128: String,
        noteID256: String
    ): Collection<String> {
        return noteIDAbilityNamesMap.getValue(noteID128, noteID256, mutableListOf())
    }

    fun getAbilityIDs(
        abilityName: String
    ): Collection<String> {
        return abilityNameAbilityIDsMap.getOrDefault(abilityName, emptyList())
    }

    fun getAbilityNames(): Collection<String> {
        return abilityNameAbilityIDsMap.keys
    }

    fun getNoteIDCount(abilityName: String, abilityID: String): Int {
        return if (abilityID == "*") abilityNameAbilityIDNoteIDCountMap.getOrDefault(
            abilityName,
            emptyMap()
        ).values.sumOf { it.get() } else abilityNameAbilityIDNoteIDCountMap.getOrDefault(abilityName, emptyMap())
            .getOrDefault(abilityID, AtomicInteger()).get()
    }
}