package com.dowstats.service.w40k

import com.dowstats.Metadata
import com.dowstats.data.dto.BuildCost
import com.dowstats.data.entities.Activation
import com.dowstats.data.entities.AreaEffect
import com.dowstats.data.entities.AreaFilter
import com.dowstats.data.entities.ArmorType
import com.dowstats.data.entities.Mod
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.ability.AbilityArmorPiercing
import com.dowstats.data.entities.ability.AbilityModifiers
import com.dowstats.data.entities.ability.AbilityRequirements
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
import java.math.BigDecimal

@Service
class AbilityRgdExtractService @Autowired constructor(
    private val commonParseRgdService: CommonParseRgdService,
) {

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

    fun extractToAbilityEntity(
        fileName: String,
        abilityData: List<RgdData>,
        mod: Mod,
        activations: Set<Activation>,
        areaEffects: Set<AreaEffect>,
        areaFilters: Set<AreaFilter>,
        armorTypes: Set<ArmorType>,
        modFolderData: String,
        modDictionary: Map<Int, String>
    ): Ability? {

        val ability = Ability()
        ability.filename = fileName
        ability.modId = mod.id

        val activationValue = abilityData.getRgdTableByName("activation")?.getStringByName("\$REF")?.replace("type_abilityactivation\\tp_","")?.replace(".lua","")
        ability.activation = activations.firstOrNull { it.id == activationValue }

        // UI Info & Basic Data
        val uiInfo = abilityData.getRgdTableByName("ui_info")
        val uiData = uiInfo?.let{ commonParseRgdService.getUiInfo(uiInfo, modDictionary, modFolderData, mod, log, ability.activation?.id == "ability_activation_toggled")}
        
        ability.name = uiData?.name
        ability.description = uiData?.description
        ability.icon = uiData?.iconPath
        ability.uiIndexHint = abilityData.getIntByName("ui_index_hint") ?: 0
        ability.uiHotkeyName = abilityData.getStringByName("ui_hotkey_name")

        // Costs
        val fireCost = abilityData.getRgdTableByName("fire_cost")
        ability.costRequisition = fireCost?.getDoubleByName("requisition")
        ability.costPower = fireCost?.getDoubleByName("power")
        ability.costFaith = fireCost?.getDoubleByName("faith")
        ability.costSouls = fireCost?.getDoubleByName("souls")

        // Ability Specific Params
        ability.childAbilityName = abilityData.getStringByName("child_ability_name")
        ability.initialDelayTime = abilityData.getDoubleByName("initial_delay_time")

        ability.range = abilityData.getDoubleByName("range")
        ability.rechargeTime = abilityData.getDoubleByName("recharge_time")
        ability.rechargeTimerGlobal = abilityData.getBooleanByName("recharge_timer_global")
        ability.refreshTime = abilityData.getDoubleByName("refresh_time")
        ability.durationTime = abilityData.getDoubleByName("duration_time")

        ability.spawnedEntityName = abilityData.getStringByName("spawned_entity_name")


        // Damage & Area Effect
        val areaEffectTable = abilityData.getRgdTableByName("area_effect")
        if (areaEffectTable != null) {
            val areaEffectData = commonParseRgdService.getAreaEffectData(areaEffectTable)

            val targetFilter = areaEffectTable
                .getRgdTableByName("area_effect_information")
                ?.getRgdTableByName("target_filter")
                ?.mapNotNull { m ->
                    if (m.name.contains("entry_")) {
                        val target = m.value as List<RgdData>
                        val armorType = target.getStringByName("\$REF")?.replace("type_armour\\tp_","")?.replace(".lua","")
                        armorTypes.firstOrNull { it.id == armorType }
                    } else null
                } ?: emptyList()

            ability.abilityTargetFilters = targetFilter.toMutableSet()
            
            ability.radius = areaEffectData.damageRadius
            ability.minDamage = areaEffectData.minDamage
            ability.minDamageValue = areaEffectData.minDamageValue
            ability.maxDamage = areaEffectData.maxDamage
            ability.moraleDamage = areaEffectData.moraleDamage
            ability.throwForceMin = areaEffectData.throwForceMin
            ability.throwForceMax = areaEffectData.throwForceMax

            ability.areaEffect = areaEffects.firstOrNull { it.id == areaEffectData.areaType }
            ability.areaFilter = areaFilters.firstOrNull { it.id == areaEffectData.filterType }

            // Armor Piercings
            ability.abilityPiercings = armorTypes.map { armorType ->
                AbilityArmorPiercing().also {
                    it.ability = ability
                    it.armorType = armorType
                    val piercingValue = (areaEffectData.weaponDmgMap[armorType.id] ?: areaEffectData.defaultArmorPiercing)?.toBigDecimal()
                    it.piercingValue = piercingValue?.let { pv -> if (pv <= BigDecimal(100)) pv else BigDecimal(100) }
                }
            }.toMutableSet()
        }

        // Modifiers & Requirements
        ability.abilityModifiers = getAbilityModifiers(ability, abilityData).toMutableSet()
        ability.abilityRequirements = getAbilityRequirements(ability, abilityData).toMutableSet()

        return ability
    }

    private fun getAbilityModifiers(ability: Ability, abilityData: List<RgdData>): List<AbilityModifiers> {
        return abilityData
            .getRgdTableByName("area_effect")
            ?.getRgdTableByName("weapon_damage")
            ?.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 {
                    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 {
                        AbilityModifiers().also {
                            it.ability = ability
                            it.maxLifeTime = maxLifetime
                        }.fillDataFromRgdTable(modifier)
                    }
                }
            } else null
        }?.filterUseless() ?: emptyList()
    }

    private fun getAbilityRequirements(ability: Ability, abilityData: List<RgdData>): List<AbilityRequirements> {
        return abilityData.getRgdTableByName("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 {
                    AbilityRequirements().also {
                        it.ability = ability
                        it.reference = rTable.getStringByName("\$REF")
                        it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, ability.filename!!, log)
                    }
                }
            } else null
        } ?: emptyList()
    }
}
