package theorycrafter.fitting

import eve.data.AttributeModifier
import eve.data.Effect
import kotlin.test.Test
import kotlin.test.assertFails


/**
 * Tests implant-related fitting functionality.
 */
class ImplantTest {


    /**
     * Tests an implant's effect on the character.
     */
    @Test
    fun testImplantEffectOnCharacter() = runFittingTest {
        val charAttribute = attribute()
        characterType {
            attributeValue(charAttribute, 100.0)
        }

        val implantAttribute = attribute()
        val implantType = implantType {
            attributeValue(implantAttribute, 2.0)
            effectReference(
                effectOnCharacter(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = charAttribute,
                    modifyingAttribute = implantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val shipType = testShipType()

        val (fit, _) = fit(shipType)
        modify {
            fit.fitImplant(implantType)
        }

        val character = fit.character
        character.assertPropertyEquals(
            attribute = charAttribute,
            expected = 200.0,
            message = "Incorrect implant on character effect application"
        )
    }


    /**
     * Tests an implant's effect on the ship.
     */
    @Test
    fun testImplantEffectOnShip() = runFittingTest {
        val shipAttribute = attribute()
        val shipType = testShipType {
            attributeValue(shipAttribute, 100.0)
        }

        val implantAttribute = attribute()
        val implantType = implantType {
            attributeValue(implantAttribute, 2.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = implantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val (fit, _) = fit(shipType)
        modify {
            fit.fitImplant(implantType)
        }

        val ship = fit.ship
        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 200.0,
            message = "Incorrect implant on ship effect application"
        )
    }


    /**
     * Tests an implant's effect on a module.
     */
    @Test
    fun testImplantEffectOnModule() = runFittingTest {
        val moduleAttribute = attribute()
        val moduleType = moduleType {
            attributeValue(moduleAttribute, 50.0)
        }

        val shipType = testShipType()

        val implantAttribute = attribute()
        val implantType = implantType {
            attributeValue(implantAttribute, 2.0)
            effectReference(
                effectOnModules(
                    modifiedAttribute = moduleAttribute,
                    modifyingAttribute = implantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val (fit, module1) = fit(shipType, moduleType)
        modify {
            fit.fitImplant(implantType)
        }

        module1.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 100.0,
            message = "Incorrect implant on module effect application"
        )

        val module2 = modify {
            fit.fitModule(moduleType, 1)
        }
        module2.assertPropertyEquals(
            attribute = moduleAttribute,
            expected = 100.0,
            message = "Incorrect implant on module (fitted afterwards) effect application"
        )

    }


    /**
     * Tests an implant's effect on drones.
     */
    @Test
    fun testImplantEffectOnDrones() = runFittingTest {
        val droneAttribute = attribute()
        val droneType = testDroneType {
            attributeValue(droneAttribute, 100.0)
        }

        val implantAttribute = attribute()
        val implantType = implantType {
            attributeValue(implantAttribute, 2.0)
            effectReference(
                effectOnDrones(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = droneAttribute,
                    modifyingAttribute = implantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val shipType = testShipType()

        val (fit, _) = fit(shipType)
        val droneGroup1 = modify {
            fit.fitImplant(implantType)
            fit.addDroneGroup(droneType, 5)
        }

        droneGroup1.assertPropertyEquals(
            attribute = droneAttribute,
            expected = 200.0,
            message = "Incorrect implant on drones effect application"
        )

        val droneGroup2 = modify {
            fit.addDroneGroup(droneType, 5)
        }
        droneGroup2.assertPropertyEquals(
            attribute = droneAttribute,
            expected = 200.0,
            message = "Incorrect implant on drones (fitted afterwards) effect application"
        )
    }


    /**
     * Tests an implant's effect on charges.
     */
    @Test
    fun testImplantEffectOnCharge() = runFittingTest {
        val chargeGroup = newTestGroup()
        val chargeAttribute = attribute()

        val chargeType = chargeType(group = chargeGroup) {
            attributeValue(chargeAttribute, 100.0)
        }

        val moduleType = moduleTypeLoadableWithCharges(
            chargeGroupId = chargeGroup.id
        )

        val implantAttribute = attribute()
        val implantType = implantType {
            attributeValue(implantAttribute, 2.0)
            effectReference(
                effectOnCharges(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = chargeAttribute,
                    modifyingAttribute = implantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val shipType = testShipType()

        val (fit, _) = fit(shipType)
        val (module, charge1) = modify {
            fit.fitImplant(implantType)
            val module = fit.fitModule(moduleType, 0)
            val charge = module.setCharge(chargeType)
            module to charge
        }

        charge1.assertPropertyEquals(
            attribute = chargeAttribute,
            expected = 200.0,
            message = "Incorrect implant on charge effect application"
        )

        val charge2 = modify {
            module.removeCharge()
            module.setCharge(chargeType)
        }
        charge2.assertPropertyEquals(
            attribute = chargeAttribute,
            expected = 200.0,
            message = "Incorrect implant on charge (fitted afterwards) effect application"
        )
    }


    /**
     * Tests an implant's effect on another implant.
     */
    @Test
    fun testImplantEffectOnImplant() = runFittingTest {
        val modifiedImplantAttribute = attribute()
        val modifiedImplantType = implantType(slot = 1) {
            attributeValue(modifiedImplantAttribute, 10.0)
        }

        val modifiedImplantRequiringSkillAttribute = attribute()
        val implantSkillType = testSkillType(skillLevel = 1)
        val modifiedImplantTypeRequiringSkill = implantType(slot = 2){
            attributeValue(modifiedImplantRequiringSkillAttribute, 3.0)
            requiresSkill(skillRequirementIndex = 0, skillId = implantSkillType.itemId, skillLevel = 1)
        }


        val modifyingImplantAttribute = attribute()
        val modifyingImplantType = implantType(slot = 3) {
            attributeValue(modifyingImplantAttribute, 2.0)
            effectReference(
                effectOnImplantsAndBoosters(
                    modifiedAttribute = modifiedImplantAttribute,
                    modifyingAttribute = modifyingImplantAttribute,
                    operation = AttributeModifier.Operation.ADD
                )
            )
            effectReference(
                effectOnImplantsAndBoosters(
                    modifiedAttribute = modifiedImplantRequiringSkillAttribute,
                    modifyingAttribute = modifyingImplantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY,
                    skillTypeId = implantSkillType.itemId
                )
            )
        }

        val shipType = testShipType()

        val (fit, _) = fit(shipType)
        val modifiedImplant = modify {
            fit.fitImplant(modifyingImplantType)
            fit.fitImplant(modifiedImplantType)
        }

        modifiedImplant.assertPropertyEquals(
            attribute = modifiedImplantAttribute,
            expected = 12.0,
            message = "Incorrect implant on implant effect application"
        )

        val modifiedImplantRequirisingSkill = modify{
            fit.fitImplant(modifiedImplantTypeRequiringSkill)
        }
        modifiedImplantRequirisingSkill.assertPropertyEquals(
            attribute = modifiedImplantRequiringSkillAttribute,
            expected = 6.0,
            message = "Incorrect implant on implant effect application"
        )
    }


    /**
     * Tests an implant's effect on itself (pirate set implants have this, for example).
     */
    @Test
    fun testImplantEffectItself() = runFittingTest {
        val implantGroup = newTestGroup()
        val modifiedImplantAttribute = attribute()
        val modifyingImplantAttribute = attribute()

        val implantType = implantType(slot = 1, group = implantGroup) {
            attributeValue(modifiedImplantAttribute, 10.0)
            attributeValue(modifyingImplantAttribute, 2.0)

            effectReference(
                effectOnImplantsAndBoosters(
                    modifiedAttribute = modifiedImplantAttribute,
                    modifyingAttribute = modifyingImplantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY,
                    groupId = implantGroup.id
                )
            )
        }


        val shipType = testShipType()

        val (fit, _) = fit(shipType)
        val implant = modify {
            fit.fitImplant(implantType)
        }

        implant.assertPropertyEquals(
            attribute = modifiedImplantAttribute,
            expected = 20.0,
            message = "Incorrect implant on implant effect application"
        )
    }


    /**
     * Tests fitting an implant into an already taken slot.
     */
    @Test
    fun testFittingImplantIntoTakenSlot() = runFittingTest {
        val shipType = testShipType()

        val implantType = implantType(slot = 1)

        val (fit, _) = fit(shipType)
        modify {
            fit.fitImplant(implantType)
        }

        assertFails {
            modify {
                fit.fitImplant(implantType)
            }
        }
    }


    /**
     * Tests a disabled implant's affects.
     */
    @Test
    fun testDisabledImplant() = runFittingTest {
        val shipAttribute = attribute()
        val shipType = testShipType {
            attributeValue(shipAttribute, 100.0)
        }

        val implantAttribute = attribute()
        val implantType = implantType {
            attributeValue(implantAttribute, 2.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = implantAttribute,
                    operation = AttributeModifier.Operation.POST_MULTIPLY
                )
            )
        }

        val (fit, _) = fit(shipType)
        val implant = modify {
            fit.fitImplant(implantType).also {
                it.setEnabled(false)
            }
        }

        val ship = fit.ship
        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = shipType.attributeValue(shipAttribute),
            message = "Disabled implant has effect on ship"
        )

        modify {
            implant.setEnabled(true)
        }

        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = 200.0,
            message = "(Re)enabled implant has wrong on ship effect"
        )

        modify {
            implant.setEnabled(false)
        }
        ship.assertPropertyEquals(
            attribute = shipAttribute,
            expected = shipType.attributeValue(shipAttribute),
            message = "Disabled implant has effect on ship"
        )
    }


}