package theorycrafter.fitting

import eve.data.AttributeModifier.Operation
import eve.data.Effect
import eve.data.EffectFlags
import eve.data.ModuleFlags
import eve.data.PROJECTED
import kotlin.test.Test


/**
 * Tests fitting functionality related to remote effects.
 */
class RemoteEffectsTest {


    /**
     * Tests a hostile effect on a ship.
     */
    @Test
    fun testHostileEffectOnShip() = runFittingTest {
        val defaultAttributeValue = 100.0
        val effectPercentValue = -40.0
        val attenuationFactor = 0.75
        val targetAttenuatedValue = 70.0
        val targetUnattenuatedValue = 60.0
        val targetTwiceAttenuatedValue = 49.0

        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()
        val attenuatingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(modifiedAttribute, defaultAttributeValue)
            attributeValue(attenuatingAttribute, attenuationFactor)
        }

        val moduleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(modifyingAttribute, effectPercentValue)
            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    flags = EffectFlags(isOffensive = true),
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    attenuatingAttribute = attenuatingAttribute,
                    operation = Operation.ADD_PERCENT
                )
            )
        }

        // Create affecting fit
        val (affectingFit, module) = fit(shipType, moduleType) { _, module: Module ->
            module.setState(Module.State.ACTIVE)
        }

        // Add affected fit and set up the hostile effect
        val (affectedFit, _) = fit(shipType)
        val remoteEffect = modify {
            affectedFit.addHostileEffect(affectingFit)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile effect on ship by a module applied incorrectly"
        )

        // Disable the effect and check the property value
        modify {
            remoteEffect.setEnabled(false)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile effect on ship by a module applied even when disabled"
        )

        // Enable the effect and check the property value
        modify {
            remoteEffect.setEnabled(true)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile effect on ship by a module applied incorrectly after being re-enabled"
        )

        // Change the attenuating property value and check the property value
        modify {
            affectedFit.ship.setPinnedPropertyValue(attenuatingAttribute, 1.0)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetUnattenuatedValue,
            message = "Hostile effect on ship by a module did not change correctly when attenuation was changed"
        )

        // Deactivate the module and check the property value
        modify {
            module.setState(Module.State.ONLINE)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile effect on ship by a module applied incorrectly when inactive"
        )

        // Reactivate the module and check the property value
        modify {
            module.setState(Module.State.ACTIVE)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetUnattenuatedValue,
            message = "Hostile effect on ship by a module applied incorrectly after reactivation"
        )

        // Reset attenuation
        modify {
            affectedFit.ship.setPinnedPropertyValue(attenuatingAttribute, attenuationFactor)
        }

        // Add another effect from same fit
        val anotherEffect = modify {
            affectedFit.addHostileEffect(affectingFit)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetTwiceAttenuatedValue,
            message = "Hostile effect on ship by a module applied twice incorrectly"
        )

        // Remove the 2nd effect
        modify {
            affectedFit.removeHostileEffect(anotherEffect)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile effect on ship by a module applied incorrectly after 2nd effect removed"
        )

        // Remove the effect and check the property value
        modify {
            affectedFit.removeHostileEffect(remoteEffect)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile effect on ship by a module still applied after being removed"
        )
    }


    /**
     * Tests a hostile drone effect on a ship.
     */
    @Test
    fun testHostileDroneEffectOnShip() = runFittingTest {
        val defaultAttributeValue = 100.0
        val effectAddedValue = 60.0
        val attenuationFactor = 0.75
        val attenuatedAddedValue = effectAddedValue * attenuationFactor

        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()
        val attenuatingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(modifiedAttribute, defaultAttributeValue)
            attributeValue(attenuatingAttribute, attenuationFactor)
        }

        val droneType = testDroneType {
            attributeValue(modifyingAttribute, effectAddedValue)
            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    flags = EffectFlags(isOffensive = true),
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    attenuatingAttribute = attenuatingAttribute,
                    operation = Operation.ADD
                )
            )
        }

        // Create affecting fit
        val (affectingFit, _) = fit(shipType)
        val droneGroup = modify {
            affectingFit.addDroneGroup(droneType, 1)
                .also {
                    it.setActive(true)
                }
        }

        // Add affected fit and set up the hostile effect
        val (affectedFit, _) = fit(shipType)
        val remoteEffect = modify {
            affectedFit.addHostileEffect(affectingFit)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + attenuatedAddedValue,
            message = "Hostile effect on ship by a drone applied incorrectly"
        )

        // Add another drone to the group and check the property value
        modify {
            droneGroup.setSize(2)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 2*attenuatedAddedValue,
            message = "Hostile effect on ship by a 2 drones applied incorrectly"
        )

        // Add another drone to the group and check the property value
        modify {
            droneGroup.setSize(3)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 3*attenuatedAddedValue,
            message = "Hostile effect on ship by a 3 drones applied incorrectly"
        )

        // Deactivate the drone group and check the property value
        modify {
            droneGroup.setActive(false)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile effect on ship by a drone group applied even when inactive"
        )

        // Reactivate the drone group and check the property value
        modify {
            droneGroup.setActive(true)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 3*attenuatedAddedValue,
            message = "Hostile effect on ship by a 3 drones applied incorrectly after re-activation"
        )

        // Disable the effect and check the property value
        modify {
            remoteEffect.setEnabled(false)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile effect on ship by a drone applied even when the effect is disabled"
        )

        // Re-enable the effect and check the property value
        modify {
            remoteEffect.setEnabled(true)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 3*attenuatedAddedValue,
            message = "Hostile effect on ship by a 3 drones applied incorrectly after re-enabling"
        )

        // Remove a drone and check the property value
        modify {
            droneGroup.setSize(2)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 2*attenuatedAddedValue,
            message = "Hostile effect on ship by a 2 drones applied incorrectly the 2nd time"
        )

        // Change the attenuating property value and check the property value
        modify {
            affectedFit.ship.setPinnedPropertyValue(attenuatingAttribute, 1.0)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 2*effectAddedValue,
            message = "Hostile effect on ship by 2 drones did not change correctly when attenuation was changed"
        )

        // Remove the effect and check the property value
        modify {
            affectedFit.removeHostileEffect(remoteEffect)
        }
        affectedFit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile effect on ship by a drone group still applied after being removed"
        )
    }


    /**
     * Tests removing a fit that has remote effects on it.
     */
    @Test
    fun testRemoveFitAffectedByRemoteFit() = runFittingTest {
        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(modifiedAttribute, 1.0)
        }

        val hostileModuleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(modifyingAttribute, 20.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    flags = EffectFlags(isOffensive = true),
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    operation = Operation.ADD_PERCENT
                )
            )
        }

        val friendlyModuleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(modifyingAttribute, -20.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    flags = EffectFlags(isAssistive = true),
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    operation = Operation.ADD_PERCENT
                )
            )
        }

        // Create affecting fit
        val (affectingFit, _) = fit(shipType, hostileModuleType, friendlyModuleType) { _, modules ->
            modules.forEach { it.setState(Module.State.ACTIVE) }
        }

        // Add affected fit and set up the effects
        val (affectedFit, _) = fit(shipType)
        modify {
            affectedFit.addHostileEffect(affectingFit)
            affectedFit.addFriendlyEffect(affectingFit)
        }

        modify {
            affectedFit.remove()
        }
    }


    /**
     * Tests a hostile module (with no source fit) affecting a ship.
     */
    @Test
    fun testHostileModuleOnShip() = runFittingTest {
        val defaultAttributeValue = 100.0
        val effectPercentValue = -40.0
        val attenuationFactor = 0.75
        val targetAttenuatedValue = 70.0
        val targetUnattenuatedValue = 60.0

        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()
        val attenuatingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(modifiedAttribute, defaultAttributeValue)
            attributeValue(attenuatingAttribute, attenuationFactor)
        }

        val moduleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(modifyingAttribute, effectPercentValue)
            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    flags = EffectFlags(isOffensive = true),
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    attenuatingAttribute = attenuatingAttribute,
                    operation = Operation.ADD_PERCENT
                )
            )
        }

        // Add affected fit and set up the hostile effect
        val (fit, _) = fit(shipType)
        val module = modify {
            fit.addModuleEffect(moduleType)!!.also {
                it.setState(Module.State.ACTIVE)
            }
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile module effect on ship applied incorrectly"
        )

        // Change the attenuating property value and check the property value
        modify {
            fit.ship.setPinnedPropertyValue(attenuatingAttribute, 1.0)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetUnattenuatedValue,
            message = "Hostile module effect on ship did not change correctly when attenuation was changed"
        )

        // Deactivate the module and check the property value
        modify {
            module.setState(Module.State.ONLINE)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile module effect on ship applied incorrectly when inactive"
        )

        // Remove the module and check the property value
        modify {
            fit.removeModuleEffect(module)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile module effect on ship applied after being removed"
        )
    }


    /**
     * Tests a hostile module (with no source fit) affecting a module.
     */
    @Test
    fun testHostileModuleOnModule() = runFittingTest {
        val defaultAttributeValue = 100.0
        val effectPercentValue = -40.0
        val attenuationFactor = 0.75
        val targetAttenuatedValue = 70.0
        val targetUnattenuatedValue = 60.0

        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()
        val attenuatingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(attenuatingAttribute, attenuationFactor)
        }
        val affectedModuleType = moduleType {
            attributeValue(modifiedAttribute, defaultAttributeValue)
        }

        val affectingModuleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(modifyingAttribute, effectPercentValue)
            effectReference(
                effectOnModules(
                    category = Effect.Category.PROJECTED,
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    attenuatingAttribute = attenuatingAttribute,
                    operation = Operation.ADD_PERCENT,
                    flags = EffectFlags(isOffensive = true),
                )
            )
        }

        // Add affected fit and set up the hostile effect
        val (fit, affectedModule) = fit(shipType, affectedModuleType)
        val affectingModule = modify {
            fit.addModuleEffect(affectingModuleType)!!
        }
        affectedModule.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile module effect on module applied incorrectly"
        )

        val affectedModuleFitAfterAffectingModule = modify {
            fit.fitModule(affectedModuleType, 1)
        }
        affectedModuleFitAfterAffectingModule.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile module effect on module fitted after it applied incorrectly"
        )

        // Change the attenuating property value and check the property value
        modify {
            fit.ship.setPinnedPropertyValue(attenuatingAttribute, 1.0)
        }
        affectedModule.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetUnattenuatedValue,
            message = "Hostile module effect on module did not change correctly when attenuation was changed"
        )

        // Deactivate the module and check the property value
        modify {
            affectingModule.setState(Module.State.ONLINE)
        }
        affectedModule.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile module effect on module applied incorrectly when inactive"
        )

        // Remove the module and check the property value
        modify {
            fit.removeModuleEffect(affectingModule)
        }
        affectedModule.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile module effect on module applied after being removed"
        )
    }


    /**
     * Tests a hostile module (with no source fit) affecting a loaded charge.
     */
    @Test
    fun testHostileModuleOnCharge() = runFittingTest {
        val defaultAttributeValue = 100.0
        val effectPercentValue = -40.0
        val attenuationFactor = 0.75
        val targetAttenuatedValue = 70.0
        val targetUnattenuatedValue = 60.0

        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()
        val attenuatingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(attenuatingAttribute, attenuationFactor)
        }
        val chargeGroup = newTestGroup()
        val affectedChargeType =  chargeType(group = chargeGroup) {
            attributeValue(modifiedAttribute, defaultAttributeValue)
        }
        val affectedModuleType = moduleTypeLoadableWithCharges(
            chargeGroupId = chargeGroup.id
        )

        val affectingModuleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(modifyingAttribute, effectPercentValue)
            effectReference(
                effectOnCharges(
                    category = Effect.Category.PROJECTED,
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    attenuatingAttribute = attenuatingAttribute,
                    operation = Operation.ADD_PERCENT,
                    flags = EffectFlags(isOffensive = true),
                )
            )
        }

        // Add affected fit and set up the hostile effect
        val (fit, _) = fit(shipType)
        val affectedCharge = modify {
            val module = fit.fitModule(affectedModuleType, 0)
            module.setCharge(affectedChargeType)
        }
        val affectingModule = modify {
            fit.addModuleEffect(affectingModuleType)!!
        }
        affectedCharge.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile module effect on charge applied incorrectly"
        )

        val affectedChargeFittedAfterAffectingModule = modify {
            val module = fit.fitModule(affectedModuleType, 1)
            module.setCharge(affectedChargeType)
        }
        affectedChargeFittedAfterAffectingModule.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetAttenuatedValue,
            message = "Hostile module effect on charge fitted after it applied incorrectly"
        )

        // Change the attenuating property value and check the property value
        modify {
            fit.ship.setPinnedPropertyValue(attenuatingAttribute, 1.0)
        }
        affectedCharge.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = targetUnattenuatedValue,
            message = "Hostile module effect on charge did not change correctly when attenuation was changed"
        )

        // Deactivate the module and check the property value
        modify {
            affectingModule.setState(Module.State.ONLINE)
        }
        affectedCharge.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile module effect on charge applied incorrectly when inactive"
        )

        // Remove the module and check the property value
        modify {
            fit.removeModuleEffect(affectingModule)
        }
        affectedCharge.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile module effect on charge applied after being removed"
        )
    }


    /**
     * Tests a hostile drone affecting a ship (with no source fit).
     */
    @Test
    fun testHostileDroneOnShip() = runFittingTest {
        val defaultAttributeValue = 100.0
        val effectAddedValue = 60.0
        val attenuationFactor = 0.75
        val attenuatedAddedValue = effectAddedValue * attenuationFactor

        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()
        val attentuatingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(modifiedAttribute, defaultAttributeValue)
            attributeValue(attentuatingAttribute, attenuationFactor)
        }

        val droneType = testDroneType {
            attributeValue(modifyingAttribute, effectAddedValue)
            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    flags = EffectFlags(isOffensive = true),
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    attenuatingAttribute = attentuatingAttribute,
                    operation = Operation.ADD
                )
            )
        }

        // Add affected fit and set up the hostile effect
        val (fit, _) = fit(shipType)
        val droneGroup = modify {
            fit.addDroneEffect(droneType, 1).also {
                it.setActive(true)
            }
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + attenuatedAddedValue,
            message = "Hostile drone on ship applied incorrectly"
        )

        // Add another drone to the group and check the property value
        modify {
            droneGroup.setSize(2)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 2*attenuatedAddedValue,
            message = "Hostile 2 drones on a ship applied incorrectly"
        )

        // Add another drone to the group and check the property value
        modify {
            droneGroup.setSize(3)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 3*attenuatedAddedValue,
            message = "Hostile 3 drones on a ship applied incorrectly"
        )

        // Deactivate the drone group and check the property value
        modify {
            droneGroup.setActive(false)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile drone affected a ship even when inactive"
        )

        // Reactivate the drone group and check the property value
        modify {
            droneGroup.setActive(true)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 3*attenuatedAddedValue,
            message = "Hostile 3 drones affected a ship incorrectly after re-activation"
        )

        // Remove a drone and check the property value
        // Add another drone to the group and check the property value
        modify {
            droneGroup.setSize(2)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue + 2*attenuatedAddedValue,
            message = "Hostile 2 drones affected a ship incorrectly the 2nd time"
        )


        // Remove the drone and check the property value
        modify {
            fit.removeDroneEffect(droneGroup)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile drone affected ship after being removed"
        )
    }


    /**
     * Test adding and then removing two module effects on a ship.
     *
     * Test needed to validate a fix to a crash when the 2nd module is removed (attenuating property was incorrectly
     * added to the removal info of the affecting module, rather than of the ship).
     */
    @Test
    fun testRemovingTwoAttenuatedHostileEffects() = runFittingTest {
        val defaultAttributeValue = 100.0

        val modifiedAttribute = attribute()
        val modifyingAttribute = attribute()
        val attenuatingAttribute = attribute()

        val shipType = testShipType {
            attributeValue(modifiedAttribute, defaultAttributeValue)
            attributeValue(attenuatingAttribute, 0.75)
        }

        val moduleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(modifyingAttribute, -40.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    flags = EffectFlags(isOffensive = true),
                    modifyingAttribute = modifyingAttribute,
                    modifiedAttribute = modifiedAttribute,
                    attenuatingAttribute = attenuatingAttribute,
                    operation = Operation.ADD_PERCENT
                )
            )
        }

        // Add affected fit and set up the hostile effects
        val (fit, _) = fit(shipType)
        val hostileModule1 = modify {
            fit.addModuleEffect(moduleType)!!.also {
                it.setState(Module.State.ACTIVE)
            }
        }
        val hostileModule2 = modify {
            fit.addModuleEffect(moduleType)!!.also {
                it.setState(Module.State.ACTIVE)
            }
        }

        // Remove the effects
        modify {
            fit.removeModuleEffect(hostileModule1)
        }
        modify {
            fit.removeModuleEffect(hostileModule2)
        }
        fit.ship.assertPropertyEquals(
            attribute = modifiedAttribute,
            expected = defaultAttributeValue,
            message = "Hostile module effect on ship applied after being removed"
        )
    }


    /**
     * Test that projecting more modules than is allowed by their group onto a fit is allowed.
     */
    @Test
    fun testAboveMaxGroupProjectedModules() = runFittingTest {
        val shipAttribute = attribute()
        val shipType = testShipType {
            attributeValue(shipAttribute, 20.0)
        }

        val group = newTestGroup()
        val moduleAttribute = attribute()
        fun moduleTypeInGroup() = moduleType(group = group){
            attributeValue(attributes.maxGroupFitted, 2)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ALWAYS,
                    modifiedAttribute = shipAttribute,
                    modifyingAttribute = moduleAttribute,
                    operation = Operation.ADD,
                    flags = EffectFlags(isOffensive = true)
                )
            )
        }

        val moduleType1 = moduleTypeInGroup()
        val moduleType2 = moduleTypeInGroup()

        val (fit, _) = fit(shipType)

        modify {
            fit.addModuleEffect(moduleType1, 0)
            fit.addModuleEffect(moduleType2, 0)
            fit.addModuleEffect(moduleType2, 0)
        }
    }


}
