import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import com.dowstats.service.w40k.RgdParserService
import com.dowstats.service.w40k.helpscripts.RgdService
import org.junit.jupiter.api.Test
import java.io.DataInputStream
import java.io.File
import java.io.PrintWriter
import java.nio.ByteBuffer

class ModDiffPrinterScript {

    val rgdDictionary: MutableMap<Int, String> = mutableMapOf()

    val rgdParseService = RgdParserService()

    val rgdService = RgdService()

    val prevVersion = "wanila2.8.0"
    val currentVersion = "wanila"

    val spaceMarinesPath = "space_marines"
    val chaosPath = "chaos"
    val eldarPath = "eldar"
    val orksPath = "orks"
    val guardPath = "guard"
    val necronPath = "necrons"
    val tauPath = "tau"
    val sistersPath = "sisters"
    val darkPath = "dark_eldar"

    val racesWikiPaths = setOf(spaceMarinesPath, chaosPath, eldarPath, orksPath, guardPath, necronPath, tauPath, sistersPath, darkPath)

    fun readDictionary() {
        File("src/test/resources/RGD_DIC.TXT").forEachLine {
            if(it.isNotEmpty() && it[0] != '#'){
                val kv = it.split('=')
                val key = kv.first().drop(2).decodeHex().getUIntAt(0).toInt()
                val value = kv.last()
                rgdDictionary[key] = value
            }
        }
    }

    private fun getUnitsAndSquadsDiff(out: PrintWriter) {

        racesWikiPaths.forEach{racePath ->
            val rgdDataOld = File("E:/home/cnb/$prevVersion/attrib/sbps/races/$racePath").walkTopDown().map {
                val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                    rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                }   else {
                    null
                }
                it.name to rgdData
            }.filter { it.second != null }
                .toMap()

            val rgdDataNew = File("E:/home/cnb/$currentVersion/attrib/sbps/races/$racePath").walkTopDown().map {
                val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                    rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                }else{
                    null
                }
                it.name to rgdData
            }.filter { it.second != null }
                .sortedBy { it.first }
                .toMap()

            val rgdDataUnitsOld = File("E:/home/cnb/$prevVersion/attrib/ebps/races/$racePath/troops").walkTopDown().map {
                val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                    rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                }else{
                    null
                }
                it.name to rgdData
            }.filter { it.second != null }
                .toMap()

            val rgdDataUnitsNew = File("E:/home/cnb/$currentVersion/attrib/ebps/races/$racePath/troops").walkTopDown().map {
                val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                    rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                }else{
                    null
                }
                it.name to rgdData
            }.filter { it.second != null }
                .toMap()

            /*classicRgdData.mapValues { findStrategicPointCaptureRate( it.value?: listOf()) }.entries
                .sortedByDescending { it.value }
                .filter { it.value != null }
                .filter { !it.key.contains("_sp") }
                .forEach{println(it.key + " -> " + it.value )}*/

            val changedSquadNames = rgdDataNew.map {  }

            rgdDataNew.forEach { newSquad ->
                val oldSquad = rgdDataOld[newSquad.key]

                val baseUnitPath = ((newSquad.value?.find { it.name ==  "squad_loadout_ext" }?.value as List<RgdData>)
                    .find { it.name ==  "trooper_base" }?.value as List<RgdData>)
                    .find { it.name == "type" }?.value as String
                val baseUnitName = baseUnitPath.split("\\").last().replace(".lua", ".rgd")

                val newUnit = rgdDataUnitsNew[baseUnitName]
                val oldUnit = rgdDataUnitsOld[baseUnitName]
                if(oldSquad != newSquad.value || oldUnit != newUnit){
                    out.println(newSquad.key.replace(".rgd", "").uppercase())
                    rgdService.printRgdDiff(newSquad.value, oldSquad, out = out)
                    if(oldSquad != newSquad.value && oldUnit != newUnit) out.println("-")
                    rgdService.printRgdDiff(newUnit, oldUnit, out = out)
                    out.println("---")
                }
            }
        }
    }

    data class UnitAsSquad(val squadData: List<RgdData>?,val uintData: List<RgdData>?)

    fun getAllWeaponsDiff(out: PrintWriter) {


        val classicRgdData = File("E:/home/cnb/$prevVersion/attrib/weapon").walkTopDown().map {
            val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
            }   else {
                null
            }
            it.name to rgdData
        }.filter { it.second != null }
            .toMap()

        val newRgdData = File("E:/home/cnb/$currentVersion/attrib/weapon").walkTopDown().map {
            val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
            }   else {
                null
            }
            it.name to rgdData
        }.filter { it.second != null }
            .sortedBy { it.first }
            .toMap()


        newRgdData.forEach {newWeapon ->
            val oldWeapon = classicRgdData[newWeapon.key]
            if(oldWeapon != newWeapon.value){
                out.println(newWeapon.key.replace(".rgd", "").uppercase())
                if(oldWeapon != null){
                    rgdService.printRgdDiff(newWeapon.value, oldWeapon, out = out)
                }else{
                    out.println("NOT EXIST IN PREV VERSION")
                }
                out.println("---")
            }

        }
    }

    fun getAllAbilitiesDiff(out: PrintWriter) {

        val classicRgdData = File("E:/home/cnb/$prevVersion/attrib/abilities").walkTopDown().map {
            val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
            }   else {
                null
            }
            it.name to rgdData
        }.filter { it.second != null }
            .toMap()

        val newRgdData = File("E:/home/cnb/$currentVersion/attrib/abilities").walkTopDown().map {
            val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
            }   else {
                null
            }
            it.name to rgdData
        }.filter { it.second != null }
            .sortedBy { it.first }
            .toMap()


        newRgdData.forEach {newWeapon ->
            val oldWeapon = classicRgdData[newWeapon.key]
            if(oldWeapon != newWeapon.value){
                out.println(newWeapon.key.replace(".rgd", "").uppercase())
                if(oldWeapon != null){
                    rgdService.printRgdDiff(newWeapon.value, oldWeapon, out = out)
                }else{
                    out.println("NOT EXIST IN PREV VERSION")
                }
                out.println("---")
            }

        }
    }

    fun getAllResearchDiff(out: PrintWriter) {

        val classicRgdData = File("E:/home/cnb/$prevVersion/attrib/research").walkTopDown().map {
            val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
            }   else {
                null
            }
            it.name to rgdData
        }.filter { it.second != null }
            .toMap()

        val newRgdData = File("E:/home/cnb/$currentVersion/attrib/research").walkTopDown().map {
            val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
            }   else {
                null
            }
            it.name to rgdData
        }.filter { it.second != null }
            .sortedBy { it.first }
            .toMap()


        newRgdData.forEach {newWeapon ->
            val oldWeapon = classicRgdData[newWeapon.key]
            if(oldWeapon != newWeapon.value){
                out.println(newWeapon.key.replace(".rgd", "").uppercase())
                if(oldWeapon != null){
                    rgdService.printRgdDiff(newWeapon.value, oldWeapon, out = out)
                }else{
                    out.println("NOT EXIST IN PREV VERSION")
                    // rgdService.printRgdDiff(newWeapon.value, wanillaRgdData[newWeapon.key], out = out)
                }
                out.println("---")
            }

        }
    }

    fun getAllUnits(): Map<String, UnitAsSquad> {

        return racesWikiPaths.map{racePath ->

            val rgdData42 = File("E:/home/cnb/$currentVersion/attrib/sbps/races/$racePath").walkTopDown().map {
                val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                    rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                }else{
                    null
                }
                it.name to rgdData
            }.filter { it.second != null }
                .toMap()

            val newUnits = File("E:/home/cnb/$currentVersion/attrib/ebps/races/$racePath/troops").walkTopDown().map {
                val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                    rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                }else{
                    null
                }
                it.name to rgdData
            }.filter { it.second != null }
                .toMap()

            rgdData42.map {newSquad ->

                val baseUnitPath = ((newSquad.value?.find { it.name ==  "squad_loadout_ext" }?.value as List<RgdData>)
                    .find { it.name ==  "trooper_base" }?.value as List<RgdData>)
                    .find { it.name == "type" }?.value as String
                val baseUnitName = baseUnitPath.split("\\").last().replace(".lua", ".rgd")

                baseUnitName to UnitAsSquad(newSquad.value, newUnits[baseUnitName])
            }.toMap()
        }.fold(emptyMap()){sum, map -> map + sum}
    }


    fun getWeaponRgdMap(): Map<String, List<RgdData>?> {

        return File("E:/home/cnb/$currentVersion/attrib/weapon").walkTopDown().map {
            val rgdData = if(it.isFile  && !it.name.contains("hg_dxp3") && !it.name.contains("npc")){
                rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
            }   else {
                null
            }
            it.name.replace(".rgd", "") to rgdData
        }.toMap()
    }

    @Test
    fun getAllWeaponDamage() {
        val weaponMap = getWeaponRgdMap()

        getAllUnits().forEach {
            println(it.key.replace(".rgd",""))
            it.value.uintData
                ?.getRgdTableByName("combat_ext")
                ?.getRgdTableByName("hardpoints")
                ?.forEach { hardpoint ->
                    if (hardpoint.name.contains("hardpoint")) {
                        val hardpointWeapons = (hardpoint.value as List<RgdData>)?.getRgdTableByName("weapon_table")
                            ?.mapNotNull { weapon ->
                                if (weapon.name.contains("weapon")) {
                                    val weapon = (weapon.value as List<RgdData>).getStringByName("weapon")
                                    if (weapon != "" && weapon?.contains("dummy_weapon") == false) {
                                        weapon
                                    } else null
                                } else null
                            }

                        if (hardpointWeapons?.isNotEmpty() == true) {

                            hardpointWeapons.forEach {
                                val weaponId = it.replace("weapon\\", "").replace(".lua", "")
                                println(":" + weaponId + ":")
                                val weaponRgd = weaponMap[weaponId]
                                val reloadTime: Double = weaponRgd?.getDoubleByName("reload_time")!!
                                val accuracy: Double = weaponRgd?.getDoubleByName("accuracy")!!

                                val armourDamage = weaponRgd?.getRgdTableByName("area_effect")
                                    ?.getRgdTableByName("weapon_damage")
                                    ?.getRgdTableByName("armour_damage")

                                val minDamage = armourDamage?.getDoubleByName("min_damage")!!
                                val maxDamage = armourDamage.getDoubleByName("max_damage")!!
                                val avgDamage = (minDamage + maxDamage) / 2

                                val weaponDmgMap: Map<String, Double>? =
                                    armourDamage
                                    ?.getRgdTableByName("armour_piercing_types")?.mapNotNull { armour_piercing ->
                                        if (armour_piercing.name.contains("entry")) {
                                            val entry = armour_piercing.value as List<RgdData>
                                            val dmgType = entry.getRgdTableByName("armour_type")?.getStringByName("\$REF")?.replace("type_armour\\tp_","")?.replace(".lua","")
                                            val dmgValue = entry.getDoubleByName("armour_piercing_value")
                                            dmgType!! to dmgValue!!
                                        } else null
                                    }?.toMap()?.toSortedMap()
                                println()
                                val dmgK = accuracy * (1/(reloadTime - reloadTime.mod(0.125)))
                                weaponDmgMap?.forEach {
                                    val piercingK = it.value / 100
                                    val totalDmg = piercingK * dmgK * avgDamage
                                    print(it.key + "-" + (Math.round(totalDmg * 100.0) / 100.00).toString() + "|")
                                }
                                println()
                                println("-----")
                            }
                        }
                    }
                }
            println("============================")
        }
    }


    private fun getAllBuildingsDiff(out: PrintWriter) {
        racesWikiPaths.forEach { racePath ->

            val classicRgdData =
                File("E:/home/cnb/$prevVersion/attrib/ebps/races/$racePath/structures").walkTopDown().map {
                    val rgdData = if (it.isFile && !it.name.contains("hg_dxp3") && !it.name.contains("npc")) {
                        rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                    } else {
                        null
                    }
                    it.name to rgdData
                }.filter { it.second != null }
                    .toMap()

            val newRgdData = File("E:/home/cnb/$currentVersion/attrib/ebps/races/$racePath/structures").walkTopDown().map {
                val rgdData = if (it.isFile && !it.name.contains("hg_dxp3") && !it.name.contains("npc")) {
                    rgdParseService.parseRgdFileStream(DataInputStream(it.inputStream()))
                } else {
                    null
                }
                it.name to rgdData
            }.filter { it.second != null }
                .toMap()


            newRgdData.forEach { newBuilding ->
                val oldBuilding = classicRgdData[newBuilding.key]
                if (oldBuilding != newBuilding.value) {
                    out.println(newBuilding.key.replace(".rgd", "").uppercase())
                    rgdService.printRgdDiff(newBuilding.value, oldBuilding, out = out)
                    out.println("---")
                }
            }
        }
    }

    @Test
    fun getAllDiffAndPrintToFiles(){
        val weaponDiffFileName = "$prevVersion-$currentVersion-weapons.txt"
        File(weaponDiffFileName).printWriter().use { out ->
            getAllWeaponsDiff(out)
        }
        val unitsDiffFileName = "$prevVersion-$currentVersion-units.txt"
        File(unitsDiffFileName).printWriter().use { out ->
            getUnitsAndSquadsDiff(out)
        }
        val buildingsFileName = "$prevVersion-$currentVersion-building.txt"
        File(buildingsFileName).printWriter().use { out ->
            getAllBuildingsDiff(out)
        }
        val abilityFileName = "$prevVersion-$currentVersion-abilities.txt"
        File(abilityFileName).printWriter().use { out ->
            getAllAbilitiesDiff(out)
        }
        val researchFileName = "$prevVersion-$currentVersion-research.txt"
        File(researchFileName).printWriter().use { out ->
            getAllResearchDiff(out)
        }
    }

    private fun String.decodeHex(): ByteArray {
        check(length % 2 == 0) { "Must have an even length" }
        return chunked(2)
            .map { it.toInt(16).toByte() }
            .toByteArray()
    }

    private fun ByteArray.getUIntAt(idx: Int): UInt =
        ((this[idx].toUInt() and 0xFFu) shl 24) or
                ((this[idx + 1].toUInt() and 0xFFu) shl 16) or
                ((this[idx + 2].toUInt() and 0xFFu) shl 8) or
                (this[idx + 3].toUInt() and 0xFFu)
}