package com.dowstats.service.w40k

import com.dowstats.Metadata
import com.dowstats.data.dto.BuildCost
import com.dowstats.data.dto.UnitDataToSave
import com.dowstats.data.dto.WeaponsData
import com.dowstats.data.entities.*
import com.dowstats.data.entities.ModifiersBase.ModifiersBaseCompanion.fillDataFromRgdTable
import com.dowstats.data.entities.ModifiersBase.ModifiersBaseCompanion.filterUseless
import com.dowstats.data.entities.ability.Ability
import com.dowstats.data.entities.sergant.Sergeant
import com.dowstats.data.entities.unit.DowUnit
import com.dowstats.data.entities.unit.Jump
import com.dowstats.data.entities.unit.JumpRequirements
import com.dowstats.data.entities.unit.UnitModifiers
import com.dowstats.data.entities.unit.UnitMoraleBrakeModifiers
import com.dowstats.data.entities.unit.UnitRequirements
import com.dowstats.data.entities.weapon.Weapon
import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
import com.dowstats.data.rgd.RgdDataUtil.getIntByName
import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class UnitRgdExtractService @Autowired constructor(
    private val commonParseRgdService: CommonParseRgdService,
    private val sergeantRgdExtractService: SergeantRgdExtractService,
    private val deathExplosionRgdExtractService: DeathExplosionRgdExtractService,
) {

    val log = LoggerFactory.getLogger(UnitRgdExtractService::class.java)

    data class HealthAndMoraleDeathData(
        val hitpoints: Double?,
        val armour: Double?,
        val regeneration: Double?,
        val moraleDeathPenalty: Double?,
        val maxRepairers: Int?,
    )

    data class RepairData(
        val healthPerSecond: Int?,
        val percentCost: Int?,
    )

    data class MoraleData(
        val max: Double?,
        val broken: Double?,
        val regeneration: Double?,
        val unitMoraleBrakeModifiers: List<UnitMoraleBrakeModifiers>,
    )

    data class SergeantData(
        val filePath: String,
        val cost: BuildCost,
    )

    data class MassData(
        val mass: Int?,
        val upTime: Double?,
    )


    fun extractToUnitEntity(
        fileNameSquad: String,
        fileNameUnit: String,
        modDictionary: Map<Int, String>,
        squadData: List<RgdData>,
        unitData: List<RgdData>,
        weapons: Set<Weapon>,
        abilities: Set<Ability>,
        race: String,
        modFolderData: String,
        mod: Mod,
        races: List<Race>,
        armorTypes: Set<ArmorType>,
        modUnitsFull: Map<String, List<RgdData>>,
    ): UnitDataToSave {

        val unit = DowUnit()
        val unitRace = races.find { it.id == race } ?: throw Exception("Cant get race $race")

        unit.race = unitRace
        unit.armorType = commonParseRgdService.getArmorType(unitData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
        unit.armorType2 = commonParseRgdService.getArmorType(unitData, armorTypes, "type_armour_2")

        val squadUiInfo = squadData.getRgdTableByName("squad_ui_ext")
            ?.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileNameSquad")
        val squadUiData = commonParseRgdService.getUiInfo(squadUiInfo, modDictionary, modFolderData, mod, log)
        unit.name = squadUiData.name
        unit.description = squadUiData.description
        unit.icon = squadUiData.iconPath
        unit.filenameSquad = fileNameSquad
        unit.filenameUnit = fileNameUnit
        unit.uiIndexHint = unitData.getRgdTableByName("ui_ext")?.getIntByName("ui_index_hint") ?: 0
        unit.uiHotkeyName = unitData.getRgdTableByName("ui_ext")?.getStringByName("ui_hotkey_name")
        unit.haveReinforceMenu = squadData.getRgdTableByName("squad_reinforce_ext") != null

        val buildCost = getBuildCost(unitData, squadData)
        unit.buildCostRequisition = buildCost.requisition
        unit.buildCostPower = buildCost.power
        unit.buildCostPopulation = buildCost.population
        unit.buildCostFaith = buildCost.faith
        unit.buildCostSouls = buildCost.souls
        unit.buildCostTime = buildCost.time

        val squadCap = getSquadCap(squadData)
        unit.capInfantry = squadCap.first?.toInt()
        unit.capSupport = squadCap.second?.toInt()

        val squadSize = getSquadSize(squadData)
        unit.squadStartSize = squadSize.first?.toInt()
        unit.squadMaxSize = squadSize.second?.toInt()
        unit.squadLimit = getSquadLimit(squadData)?.toInt()

        val healthData = getHealthAndMoraleDeathPenaltyData(unitData)
        unit.health = healthData.hitpoints?.toInt()
        unit.armour = healthData.armour
        unit.healthRegeneration = healthData.regeneration
        unit.moraleDeathPenalty = healthData.moraleDeathPenalty?.toInt()
        unit.repairMax = healthData.maxRepairers

        val repairData = getRepairData(unitData)
        unit.repairSpeed = repairData.healthPerSecond
        unit.repairCostPercent = repairData.percentCost

        val incomeData = commonParseRgdService.getResourceIncome(unitData.getRgdTableByName("resource_ext"))
        unit.faithIncome = incomeData.faith
        unit.powerIncome = incomeData.power
        unit.requisitionIncome = incomeData.requisition

        val moraleData = getMoraleData(unit, squadData)
        unit.moraleMax = moraleData.max?.toInt()
        unit.moraleBroken = moraleData.broken?.toInt()
        unit.moraleRegeneration = moraleData.regeneration?.toInt()
        unit.unitMoraleBrakeModifiers = moraleData.unitMoraleBrakeModifiers.toMutableSet()

        unit.moveSpeed = getUnitSpeed(unitData)?.toInt()
        unit.sightRadius = getSightRadius(unitData)?.toInt()
        unit.detectRadius = getDetectRadius(unitData)?.toInt()

        val massData = getMassData(unitData)
        unit.mass = massData.mass
        unit.upTime = massData.upTime

        val reinforceCostRgdData = getReinforceCostRgdData(squadData)
        val reinforceCostData = commonParseRgdService.getBuildCost(reinforceCostRgdData)
        unit.reinforceCostRequisition = reinforceCostData.requisition
        unit.reinforceCostPower = reinforceCostData.power
        unit.reinforceCostPopulation = reinforceCostData.population
        unit.reinforceCostFaith = reinforceCostData.faith
        unit.reinforceCostSouls = reinforceCostData.souls
        unit.reinforceTime = reinforceCostData.time

        unit.mobValue = getMobValue(unitData)

        unit.unitModifiers = geUnitModifiers(unit, unitData, squadData).toMutableSet()
        unit.unitRequirements = getUnitRequirements(unit, squadData).toMutableSet()
        unit.jump = getJump(unit, squadData)
        unit.abilities = commonParseRgdService.getAbilities(unitData, abilities).toMutableSet()

        unit.deathExplosions.addAll(deathExplosionRgdExtractService.getDeathExplosions(unitData, armorTypes).toMutableSet())


        val sergeantsData = getSergeantsData(squadData)

        val sergeantsEntities: List<Pair<Sergeant, Set<Weapon>>>? = sergeantsData.first?.mapNotNull { sergeantData ->
            val sergeantFile = sergeantData.filePath.split("\\").last().replace(".lua", ".rgd")
            val sergeantRgdData = modUnitsFull[sergeantFile]
            if (sergeantRgdData != null) {
                sergeantRgdExtractService.extractToSergeantEntity(
                    sergeantFile,
                    modDictionary,
                    mod,
                    sergeantRgdData,
                    weapons,
                    abilities,
                    modFolderData,
                    sergeantData.cost,
                    armorTypes
                )
            } else null
        }

        unit.maxSergeants = sergeantsData.second


        val unitWeapons = getUnitWeapons(unitData, weapons)

        unit.modId = mod.id

        return UnitDataToSave(unit, unitWeapons, sergeantsEntities)
    }

    fun getUnitRgdFileNameFromSquadData(squadRgdData: List<RgdData>): String? {
        val baseUnitPath = squadRgdData.getRgdTableByName("squad_loadout_ext")
            ?.getRgdTableByName("trooper_base")
            ?.getStringByName("type")
        return baseUnitPath?.split("\\")?.last()?.replace(".lua", ".rgd")
    }

    private fun getBuildCost(unitData: List<RgdData>, squadData: List<RgdData>): BuildCost {

        val cost = commonParseRgdService.getBuildCost(unitData.getRgdTableByName("cost_ext")?.getRgdTableByName("time_cost"))

        val minSquadSize = squadData.getRgdTableByName("squad_loadout_ext")
            ?.getDoubleByName("unit_min")?.toInt()

        fun getCost(cost: Double?) =
            cost?.let { it * (minSquadSize ?: 1) }

        return BuildCost(
            getCost(cost.requisition),
            getCost(cost.power),
            getCost(cost.population),
            getCost(cost.faith),
            getCost(cost.souls),
            getCost(cost.time)
        )
    }


    private fun getSquadCap(squadData: List<RgdData>): Pair<Double?, Double?> {

        val squadCap = squadData.getRgdTableByName("squad_cap_ext")

        return Pair(
            squadCap?.getDoubleByName("squad_cap_usage"),
            squadCap?.getDoubleByName("support_cap_usage")
        )

    }

    private fun getSquadSize(squadData: List<RgdData>): Pair<Double?, Double?> {

        val squadSize = squadData.getRgdTableByName("squad_loadout_ext")

        val unitMin = squadSize?.getDoubleByName("unit_min")
        val unitMax = squadSize?.getDoubleByName("unit_max")

        return Pair(unitMin, unitMax)
    }

    private fun getSquadLimit(squadData: List<RgdData>): Double? {
        val requirements = squadData.getRgdTableByName("squad_requirement_ext")
            ?.getRgdTableByName("requirements")

        return requirements?.map {
            if (it.type == 100) {
                val reqirementData = it.value as List<RgdData>
                reqirementData.find { it.name == "max_squad_cap" || it.name == "max_cumulative_squad_cap" }?.value as Double?
            } else null
        }?.filterNotNull()?.firstOrNull()
    }

    private fun getHealthAndMoraleDeathPenaltyData(unitData: List<RgdData>): HealthAndMoraleDeathData {
        val healthExt = unitData.getRgdTableByName("health_ext")
        val canRepair = healthExt?.getBooleanByName("can_be_repaired")
        return HealthAndMoraleDeathData(
            healthExt?.getDoubleByName("hitpoints"),
            healthExt?.getDoubleByName("armour")?.minus(100),
            healthExt?.getDoubleByName("regeneration_rate"),
            healthExt?.getDoubleByName("morale_death"),
            canRepair?.let { if (it) healthExt.getIntByName("max_repairers") else null }
        )
    }

    private fun getRepairData(unitData: List<RgdData>): RepairData {
        val repairExt = unitData.getRgdTableByName("repair_ext")
        return RepairData(
            repairExt?.getIntByName("health_repaired_per_second"),
            repairExt?.getIntByName("percent_of_target_cost_for_full_repair"),
        )
    }


    private fun getMoraleData(unit: DowUnit, squadData: List<RgdData>): MoraleData {
        val moraleData = squadData.getRgdTableByName("squad_morale_ext")
        val max = moraleData?.getDoubleByName("max")
        val broken = moraleData?.getDoubleByName("broken_min_morale")
        val regeneration = moraleData?.getDoubleByName("rate_per_second")

        val unitMoraleBreakModifiers = moraleData?.getRgdTableByName("broken_modifiers")?.mapNotNull { m ->
            if (m.name.contains("modifier_")) {
                val mTable = m.value as List<RgdData>
                if (mTable.getStringByName("\$REF") == "modifiers\\no_modifier.lua" ||
                    mTable.getStringByName("\$REF") == "modifiers\\morale_break_event.lua") null else {
                    UnitMoraleBrakeModifiers().also {
                        it.unit = unit
                    }.fillDataFromRgdTable(mTable)
                }
            } else null
        } ?: emptyList()

        return MoraleData(max, broken, regeneration, unitMoraleBreakModifiers)
    }

    private fun getMobValue(unitData: List<RgdData>): Double? =
        unitData.getRgdTableByName("mob_ext")
            ?.getDoubleByName("mob_value")

    private fun getMassData(unitData: List<RgdData>): MassData {
        val massDataRgd = unitData
            .getRgdTableByName("special_attack_physics_ext")

        val unitMass = massDataRgd?.getIntByName("mass")
        val unitUpTime = massDataRgd?.getDoubleByName("get_up_time")

        return MassData(unitMass, unitUpTime)
    }

    private fun getUnitSpeed(unitData: List<RgdData>): Double? = unitData
        .getRgdTableByName("moving_ext")
        ?.getDoubleByName("speed_max")

    private fun getSightRadius(unitData: List<RgdData>): Double? = unitData
        .getRgdTableByName("sight_ext")
        ?.getDoubleByName("sight_radius")

    private fun getDetectRadius(unitData: List<RgdData>): Double? = unitData
        .getRgdTableByName("sight_ext")
        ?.getDoubleByName("keen_sight_radius")

    private fun getReinforceCostRgdData(squadData: List<RgdData>): List<RgdData>? = squadData
        .getRgdTableByName("squad_reinforce_ext")
        ?.getRgdTableByName("cost")

    private fun getUnitWeapons(unitData: List<RgdData>?, weapons: Set<Weapon>): List<WeaponsData>? = unitData
        ?.getRgdTableByName("combat_ext")
        ?.getRgdTableByName("hardpoints")
        ?.mapNotNull { hardpoint ->
            if (hardpoint.name.contains("hardpoint_")) {
                val hardpointValue = hardpoint.name.replace("hardpoint_", "").toInt()
                val hardpointTable = hardpoint.value as List<RgdData>



                hardpointTable.getRgdTableByName("weapon_table")?.let {
                    it.mapNotNull { weapon ->
                        (weapon.value as? List<RgdData>)?.getStringByName("weapon")?.let {
                            if (it != "") {
                                val weaponFileName = it.replace("weapon\\", "").replace(".rgd", "").replace(".lua", "")
                                val weaponEntity = weapons.find {
                                    it.filename?.replace(".rgd", "") == weaponFileName
                                }
                                if(weaponEntity == null){
                                    log.warn("Can't find weapon $weaponFileName")
                                    null
                                } else {
                                    WeaponsData(
                                        hardpointValue,
                                        weapon.name.replace("weapon_", "").toInt(),
                                        weaponEntity
                                    )
                                }
                            } else null
                        }
                    }
                }
            } else null
        }?.flatten()



    private fun getSergeantsData(squadData: List<RgdData>?): Pair<List<SergeantData>?, Int?> {

        val squadTable = squadData
            ?.getRgdTableByName("squad_leader_ext")

        val maxSergeants = squadTable?.getIntByName("max_leaders")

        val sergeantsData = squadTable?.mapNotNull { sergeantData ->
            if (sergeantData.name.contains("leader_")) {
                val sergeantRgdTable = sergeantData.value as List<RgdData>

                val sergeantLeaderFilePath = sergeantRgdTable.getRgdTableByName("leader")?.getStringByName("type")

                if (sergeantLeaderFilePath == null || sergeantLeaderFilePath == "") null else {

                    SergeantData(
                        sergeantLeaderFilePath,
                        commonParseRgdService.getBuildCost(sergeantRgdTable.getRgdTableByName("cost_time"))
                    )
                }
            } else null
        }

        return Pair(sergeantsData, maxSergeants)
    }

    private fun geUnitModifiers(unit: DowUnit, unitData: List<RgdData>, squadData: List<RgdData>): List<UnitModifiers> {

        fun getUnitModifiersFromTable(table: List<RgdData>?) : List<UnitModifiers> = table?.mapNotNull { m ->
            if (m.name.contains("modifier_")) {
                val mTable = m.value as List<RgdData>
                if (mTable.getStringByName("\$REF") == "modifiers\\no_modifier.lua") null else {
                    UnitModifiers().also {
                        it.unit = unit
                    }.fillDataFromRgdTable(mTable)
                }
            } else null
        }?.filterUseless() ?: emptyList()

        fun getSquadModifiersFromTable(table: List<RgdData>?) : List<UnitModifiers> = table?.mapNotNull { m ->
            if (m.name.contains("modifier_")) {
                val mTable = m.value as List<RgdData>
                val modifier = mTable.getRgdTableByName("modifier")
                val maxLifetime = mTable.getDoubleByName("max_lifetime")
                if (modifier?.getStringByName("\$REF") == "modifiers\\no_modifier.lua" || modifier == null) null else {
                    UnitModifiers().also {
                        it.unit = unit
                        it.maxLifeTime = maxLifetime
                    }.fillDataFromRgdTable(modifier)
                }
            } else null
        }?.filterUseless() ?: emptyList()

        val unitModifiersTable = unitData.getRgdTableByName("modifier_apply_ext")?.getRgdTableByName("modifiers")
        val squadModifiersTable = squadData.getRgdTableByName("squad_modifier_apply_ext")?.getRgdTableByName("modifiers")

        return getUnitModifiersFromTable(unitModifiersTable) + getSquadModifiersFromTable(squadModifiersTable)

    }

    private fun getUnitRequirements(unit: DowUnit, squadData: List<RgdData>): List<UnitRequirements> {
        val requirements = squadData.getRgdTableByName("squad_requirement_ext")?.getRgdTableByName("requirements")
        return requirements?.mapNotNull { r ->
            if (r.name.contains("required_")) {
                val rTable = r.value as List<RgdData>
                if (rTable.getStringByName("\$REF")?.contains(Metadata.Requirements.REFERENCE_REQUIREMENT_NONE) == true) null else {
                    UnitRequirements().also {
                        it.unit = unit
                        it.reference = rTable.getStringByName("\$REF")
                        it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, unit.filenameSquad!!, log)
                    }
                }
            } else null
        } ?: emptyList()
    }

    private fun getJump(unit: DowUnit, squadData: List<RgdData>): Jump? {
        val squadJumpExt = squadData.getRgdTableByName("squad_jump_ext") ?: return null

        return Jump().also { jump ->
            jump.unit = unit
            jump.chargeRegeneration = squadJumpExt.getDoubleByName("charge_regeneration")
            jump.goUpTime = squadJumpExt.getDoubleByName("go_up_time")
            jump.jumpDistanceMax = squadJumpExt.getDoubleByName("jump_distance_max")
            jump.breakdownTime = squadJumpExt.getDoubleByName("breakdown_time")
            jump.chargeJumpCostMin = squadJumpExt.getDoubleByName("charge_jump_cost_min")
            jump.jumpHeight = squadJumpExt.getDoubleByName("jump_height")
            jump.chargeMax = squadJumpExt.getDoubleByName("charge_max")
            jump.setupTime = squadJumpExt.getDoubleByName("setup_time")
            jump.chargeJumpCostMax = squadJumpExt.getDoubleByName("charge_jump_cost_max")
            jump.goDownTime = squadJumpExt.getDoubleByName("go_down_time")
            jump.teleport = squadJumpExt.getBooleanByName("teleport") ?: false
            jump.jumpTimeMax = squadJumpExt.getDoubleByName("jump_time_max")
            jump.jumpTimeMin = squadJumpExt.getDoubleByName("jump_time_min")
            jump.jumpMinHeight = squadJumpExt.getDoubleByName("jump_min_height")

            // Parse jump requirements
            val requirements = squadJumpExt.getRgdTableByName("requirements")
            jump.jumpRequirements = requirements?.mapNotNull { r ->
                if (r.name.contains("required_")) {
                    val rTable = r.value as List<RgdData>
                    if (rTable.getStringByName("\$REF")?.contains(Metadata.Requirements.REFERENCE_REQUIREMENT_NONE) == true) null else {
                        JumpRequirements().also {
                            it.jump = jump
                            it.reference = rTable.getStringByName("\$REF")
                            it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, unit.filenameSquad!!, log)
                        }
                    }
                } else null
            }?.toMutableSet() ?: mutableSetOf()
        }
    }
}
