package com.dowstats.service.w40k

import com.dowstats.Metadata
import com.dowstats.data.dto.BuildCost
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.sergant.SergeantModifiers
import com.dowstats.data.entities.sergant.SergeantRequirements
import com.dowstats.data.entities.weapon.Weapon
import com.dowstats.data.rgd.RgdData
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 SergeantRgdExtractService @Autowired constructor(
    private val commonParseRgdService: CommonParseRgdService,
    private val deathExplosionRgdExtractService: DeathExplosionRgdExtractService,
) {

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

    data class HealthAndMoraleDeathData(

        val hitpoints: Double?,
        val armour: Double?,
        val regeneration: Double?,
        val moraleDeathPenalty: Double?,
    )

    data class WeaponsData(
        val hardpoint: Int,
        val hardpointOrder: Int,
        val weaponFilename: String,
    )

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

    fun extractToSergeantEntity(
        fileName: String,
        modDictionary: Map<Int, String>,
        mod: Mod,
        sergeantData: List<RgdData>,
        weapons: Set<Weapon>,
        abilities: Set<Ability>,
        modFolderData: String,
        buildCost: BuildCost,
        armorTypes: Set<ArmorType>,
    ): Pair<Sergeant, Set<Weapon>> {

        val sergeant = Sergeant()

        sergeant.buildCostRequisition = buildCost.requisition
        sergeant.buildCostPower = buildCost.power
        sergeant.buildCostPopulation = buildCost.population
        sergeant.buildCostFaith = buildCost.faith
        sergeant.buildCostSouls = buildCost.souls
        sergeant.buildCostTime = buildCost.time

        sergeant.armorType = commonParseRgdService.getArmorType(sergeantData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
        sergeant.armorType2 = commonParseRgdService.getArmorType(sergeantData, armorTypes, "type_armour_2")
        sergeant.filename = fileName

        val uiExt = sergeantData.getRgdTableByName("ui_ext") ?: throw Exception("Could not find ui_ext at $fileName")
        val sergeantUiRgd = uiExt.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileName")
        val sergeantUiInfo = commonParseRgdService.getUiInfo(sergeantUiRgd, modDictionary, modFolderData, mod, log)
        sergeant.name = sergeantUiInfo.name
        sergeant.description = sergeantUiInfo.description
        sergeant.icon = sergeantUiInfo.iconPath

        sergeant.uiHotkeyName = uiExt.getStringByName("ui_hotkey_name")

        val healthData = getHealthAndMoraleDeathPenaltyData(sergeantData)
        sergeant.health = healthData.hitpoints?.toInt()
        sergeant.armour = healthData.armour
        sergeant.healthRegeneration = healthData.regeneration
        sergeant.moraleDeathPenalty = healthData.moraleDeathPenalty?.toInt()

        val moraleInc = sergeantData.getRgdTableByName("morale_add_ext")
        sergeant.moraleIncomeMax = moraleInc?.getDoubleByName("inc_morale_max")
        sergeant.moraleIncomeRate = moraleInc?.getDoubleByName("inc_morale_rate")

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

        sergeant.mobValue = getMobValue(sergeantData)

        sergeant.sightRadius = getSightRadius(sergeantData)?.toInt()
        sergeant.detectRadius = getDetectRadius(sergeantData)?.toInt()

        val massData = getMassData(sergeantData)
        sergeant.mass = massData.mass
        sergeant.upTime = massData.upTime

        sergeant.modId = mod.id!!

        sergeant.sergeantModifiers = getSergeantModifiers(sergeant, sergeantData).toMutableSet()
        sergeant.sergeantRequirements = getSergeantRequirements(sergeant, sergeantData).toMutableSet()

        sergeant.abilities = commonParseRgdService.getAbilities(sergeantData, abilities).toMutableSet()

        sergeant.deathExplosions.addAll(deathExplosionRgdExtractService.getDeathExplosions(sergeantData, armorTypes).toMutableSet())

        val sergeantWeapons = getSergeantWeapons(sergeantData)?.mapNotNull { weaponData ->
            weapons.find {
                it.filename == weaponData.weaponFilename + ".rgd"
            }?.also {wp ->
                sergeant.weaponHardpoints.add(Weapon.HardpointPosition(wp.id!!, weaponData.hardpoint, weaponData.hardpointOrder))
            }
        }.orEmpty().toSet()

        return Pair(sergeant, sergeantWeapons)
    }

    private fun getHealthAndMoraleDeathPenaltyData(unitData: List<RgdData>): HealthAndMoraleDeathData {
        val healthExt = unitData.getRgdTableByName("health_ext")
        return HealthAndMoraleDeathData(
            healthExt?.getDoubleByName("hitpoints"),
            healthExt?.getDoubleByName("armour")?.minus(100),
            healthExt?.getDoubleByName("regeneration_rate"),
            healthExt?.getDoubleByName("morale_death")
        )
    }

    private fun getMobValue(squadData: List<RgdData>): Double? =
        squadData.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 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 getSergeantWeapons(reinforceData: List<RgdData>?): List<WeaponsData>? = reinforceData
        ?.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 != "") {
                                WeaponsData(hardpointValue,
                                weapon.name.replace("weapon_", "").toInt(),
                                it.replace("weapon\\", "").replace(".lua", ""))
                            } else null
                        }
                    }
                }
            } else null
        }?.flatten()

    private fun getSergeantModifiers(sergeant: Sergeant, sergeantData: List<RgdData>): List<SergeantModifiers> =
        sergeantData.getRgdTableByName("modifier_apply_ext")?.getRgdTableByName("modifiers")?.mapNotNull { m ->
            if (m.name.contains("modifier_")) {
                val mTable = m.value as List<RgdData>
                if (mTable.getStringByName("\$REF") == "modifiers\\no_modifier.lua") null else {
                    SergeantModifiers().also {
                        it.sergeant = sergeant
                    }.fillDataFromRgdTable(mTable)
                }
            } else null
        }?.filterUseless() ?: emptyList()

    private fun getSergeantRequirements(sergeant: Sergeant, sergeantData: List<RgdData>): List<SergeantRequirements> {
        val requirements = sergeantData.getRgdTableByName("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 {
                    SergeantRequirements().also {
                        it.sergeant = sergeant
                        it.reference = rTable.getStringByName("\$REF")
                        it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, sergeant.filename, log)
                    }
                }
            } else null
        } ?: emptyList()
    }

}
