package theorycrafter.fitting

import eve.data.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails


/**
 * Tests fitting functionality related to tactical modes.
 */
class TacticalModeTest {


    /**
     * Tests that tactical modes can be set on a ship.
     */
    @Test
    fun tacticalModesCanBeSet() = runFittingTest {
        val shipType = testShipWithTacticalModes()

        val tacticalModeTypes = tacticalModeTypes(
            shipType = shipType,
        )

        val (fit, _) = fit(shipType)

        for (tacticalModeType in tacticalModeTypes.values){
            modify {
                fit.setTacticalMode(tacticalModeType)
            }
            assertEquals(fit.tacticalMode?.type, tacticalModeType)
        }
    }


    /**
     * Tests that tactical modes can't be set on a ship that doesn't have the [Attributes.hasTacticalModes] attribute.
     */
    @Test
    fun tacticalModesCanNotBeSetOnShipWithoutHasTacticalModesAttribute() = runFittingTest {
        val shipType = testShipType()

        val tacticalModeTypes = tacticalModeTypes(
            shipType = shipType,
        )

        val (fit, _) = fit(shipType)

        assertFails("Able to set tactical mode on a ship that doesn't accept tactical modes") {
            modify {
                fit.setTacticalMode(tacticalModeTypes[TacticalModeType.Kind.DEFENSE])
            }
        }
    }


    /**
     * Tests the effect of a tactical mode on a ship's attribute.
     */
    @Test
    fun testTacticalModeEffectOnShip() = runFittingTest {
        val shipAttribute = attribute()

        val shipType = testShipWithTacticalModes {
            attributeValue(shipAttribute, 100.0)
        }

        val tacticalModeAttribute = attribute()
        val tacticalModeTypes = tacticalModeTypes(
            shipType = shipType,
            defenseModeBuilder = {
                attributeValue(tacticalModeAttribute, 10.0)
                effectReference(
                    effectOnShip(
                        category = Effect.Category.ALWAYS,
                        modifiedAttribute = shipAttribute,
                        modifyingAttribute = tacticalModeAttribute,
                        operation = AttributeModifier.Operation.ADD
                    )
                )
            }
        )
        val affectingMode = tacticalModeTypes[TacticalModeType.Kind.DEFENSE]
        val nonAffectingMode = tacticalModeTypes[TacticalModeType.Kind.SHARPSHOOTER]

        val (fit, _) = fit(shipType)

        // Validate initial value
        fit.ship.assertPropertyEquals(shipAttribute, 100.0, "Wrong initial ship property value")

        // Validate that an irrelevant mode doesn't do anything
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        fit.ship.assertPropertyEquals(shipAttribute, 100.0, "Wrong ship property value with irrelevant tactical mode")

        // Validate that the affecting mode applies its effect
        modify {
            fit.setTacticalMode(affectingMode)
        }
        fit.ship.assertPropertyEquals(shipAttribute, 110.0, "Wrong application of tactical mode on ship property")

        // Validate that (re)setting the irrelevant mode re-sets the property
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        fit.ship.assertPropertyEquals(shipAttribute, 100.0, "Ship property not re-set after tactical mode is re-set")
    }


    /**
     * Tests the effect of a tactical mode on the character's attribute.
     */
    @Test
    fun testTacticalModeEffectOnCharacter() = runFittingTest {
        val charAttribute = attribute()
        characterType {
            attributeValue(charAttribute, 100.0)
        }

        val shipType = testShipWithTacticalModes()

        val tacticalModeAttribute = attribute()
        val tacticalModeTypes = tacticalModeTypes(
            shipType = shipType,
            defenseModeBuilder = {
                attributeValue(tacticalModeAttribute, 10.0)
                effectReference(
                    effectOnCharacter(
                        category = Effect.Category.ALWAYS,
                        modifiedAttribute = charAttribute,
                        modifyingAttribute = tacticalModeAttribute,
                        operation = AttributeModifier.Operation.ADD
                    )
                )
            }
        )
        val affectingMode = tacticalModeTypes[TacticalModeType.Kind.DEFENSE]
        val nonAffectingMode = tacticalModeTypes[TacticalModeType.Kind.SHARPSHOOTER]

        val (fit, _) = fit(shipType)
        val character = fit.character

        // Validate initial value
        character.assertPropertyEquals(charAttribute, 100.0, "Wrong initial character property value")

        // Validate that an irrelevant mode doesn't do anything
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        character.assertPropertyEquals(charAttribute, 100.0, "Wrong character property value with irrelevant tactical mode")

        // Validate that the affecting mode applies its effect
        modify {
            fit.setTacticalMode(affectingMode)
        }
        character.assertPropertyEquals(charAttribute, 110.0, "Wrong application of tactical mode on character property")

        // Validate that (re)setting the irrelevant mode re-sets the property
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        character.assertPropertyEquals(charAttribute, 100.0, "Character property not re-set after tactical mode is re-set")
    }


    /**
     * Tests the effect of a tactical mode on module attributes.
     */
    @Test
    fun testTacticalModeEffectOnModule() = runFittingTest {
        val moduleAttribute = attribute()
        val moduleType = moduleType {
            attributeValue(moduleAttribute, 100.0)
        }

        val shipType = testShipWithTacticalModes()

        val tacticalModeAttribute = attribute()
        val tacticalModeTypes = tacticalModeTypes(
            shipType = shipType,
            defenseModeBuilder = {
                attributeValue(tacticalModeAttribute, 10.0)
                effectReference(
                    effectOnModules(
                        category = Effect.Category.ALWAYS,
                        modifiedAttribute = moduleAttribute,
                        modifyingAttribute = tacticalModeAttribute,
                        operation = AttributeModifier.Operation.ADD
                    )
                )
            }
        )
        val affectingMode = tacticalModeTypes[TacticalModeType.Kind.DEFENSE]
        val nonAffectingMode = tacticalModeTypes[TacticalModeType.Kind.SHARPSHOOTER]

        val (fit, module) = fit(shipType, moduleType)

        // Validate initial value
        module.assertPropertyEquals(moduleAttribute, 100.0, "Wrong initial module property value")

        // Validate that an irrelevant mode doesn't do anything
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        module.assertPropertyEquals(moduleAttribute, 100.0, "Wrong module property value with irrelevant tactical mode")

        // Validate that the affecting mode applies its effect
        modify {
            fit.setTacticalMode(affectingMode)
        }
        module.assertPropertyEquals(moduleAttribute, 110.0, "Wrong application of tactical mode on module property")

        // Validate that (re)setting the irrelevant mode re-sets the property
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        module.assertPropertyEquals(moduleAttribute, 100.0, "Module property not re-set after tactical mode is re-set")

        // Now check that if we first set the tactical mode and then add the module, that also works
        modify {
            fit.removeModule(module)
            fit.setTacticalMode(affectingMode)
        }
        val anotherModule = modify { fit.fitModule(moduleType, 0) }
        anotherModule.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 110.0,
            message = "Wrong application of tactical mode on module (fitted afterwards) property"
        )
    }


    /**
     * Tests the effect of a tactical mode on charge attributes.
     */
    @Test
    fun testTacticalModeEffectOnCharge() = runFittingTest {
        val chargeAttribute = attribute()
        val chargeType = chargeType {
            attributeValue(chargeAttribute, 100.0)
        }

        val moduleType = moduleTypeLoadableWithCharges(chargeGroupId = chargeType.groupId)

        val shipType = testShipWithTacticalModes()

        val tacticalModeAttribute = attribute()
        val tacticalModeTypes = tacticalModeTypes(
            shipType = shipType,
            defenseModeBuilder = {
                attributeValue(tacticalModeAttribute, 10.0)
                effectReference(
                    effectOnCharges(
                        category = Effect.Category.ALWAYS,
                        modifiedAttribute = chargeAttribute,
                        modifyingAttribute = tacticalModeAttribute,
                        operation = AttributeModifier.Operation.ADD
                    )
                )
            }
        )
        val affectingMode = tacticalModeTypes[TacticalModeType.Kind.DEFENSE]
        val nonAffectingMode = tacticalModeTypes[TacticalModeType.Kind.SHARPSHOOTER]

        val (fit, module) = fit(shipType, moduleType)
        val charge = modify {
            module.setCharge(chargeType)
        }

        // Validate initial value
        charge.assertPropertyEquals(chargeAttribute, 100.0, "Wrong initial charge property value")

        // Validate that an irrelevant mode doesn't do anything
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        charge.assertPropertyEquals(chargeAttribute, 100.0, "Wrong charge property value with irrelevant tactical mode")

        // Validate that the affecting mode applies its effect
        modify {
            fit.setTacticalMode(affectingMode)
        }
        charge.assertPropertyEquals(chargeAttribute, 110.0, "Wrong application of tactical mode on charge property")

        // Validate that (re)setting the irrelevant mode re-sets the property
        modify {
            fit.setTacticalMode(nonAffectingMode)
        }
        charge.assertPropertyEquals(chargeAttribute, 100.0, "Charge property not re-set after tactical mode is re-set")

        // Now check that if we first set the tactical mode and then the charge, that also works
        modify {
            module.removeCharge()
            fit.setTacticalMode(affectingMode)
        }
        val anotherCharge = modify { module.setCharge(chargeType) }
        anotherCharge.assertPropertyEquals(
            attribute = chargeAttribute,
            expected = 110.0,
            message = "Wrong application of tactical mode on charge property if charge set after tactical mode is set"
        )

        // Now check that if we first set the tactical mode and then add the module with the charge, that also works
        modify {
            fit.setTacticalMode(nonAffectingMode)
            fit.removeModule(module)
        }
        val anotherChargeOnAnotherModule = modify {
            fit.setTacticalMode(affectingMode)
            val anotherModule = fit.fitModule(moduleType, 0)
            anotherModule.setCharge(chargeType)
        }
        anotherChargeOnAnotherModule.assertPropertyEquals(
            attribute = chargeAttribute,
            expected = 110.0,
            message = "Wrong application of tactical mode on charge property if module and charge fit after tactical" +
                    " mode is set"
        )
    }


}


/**
 * Creates a test ship that has tactical modes.
 */
private fun FittingEngineTest.testShipWithTacticalModes(
    builder: (CustomEveDataBuilder.ShipTypeBuilder.() -> Unit)? = null
): ShipType {
    return testShipType(hasTacticalModes = true, builder = builder)
}
