package theorycrafter.fits

import androidx.compose.runtime.*
import eve.data.*
import eve.data.utils.ValueByEnum
import eve.data.utils.forEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import theorycrafter.SkillSetHandle
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.*
import theorycrafter.formats.FitsImportResult
import theorycrafter.formats.ImportedRemoteFitEffect
import theorycrafter.storage.*
import theorycrafter.storage.StoredFit.Companion.withUpdatesFrom
import theorycrafter.storage.StoredFit.StoredRemoteEffect
import theorycrafter.utils.bfsSequence
import theorycrafter.utils.forEachNonNullIndexed
import theorycrafter.utils.runCatchingExceptCancellation


/**
 * The part of [theorycrafter.TheorycrafterContext] responsible for fit management.
 */
class FitsContext(


    /**
     * The underlying repository.
     */
    private val repo: FittingRepository,


    /**
     * The fitting engine.
     */
    private val fittingEngine: FittingEngine,


    /**
     * The callbacks to invoke when fits change.
     */
    private val changeListener: ChangeListener


) {


    /**
     * The list of fit handles, as a Compose state object.
     */
    private val mutableHandles = repo.fits
        .sortedBy { it.id!! }
        .map { FitHandleImpl(it) }
        .toMutableStateList()


    /**
     * Maps fit handles by their ids.
     */
    private val handleById: MutableMap<Int, FitHandle> =
        mutableHandles
            .associateBy { it.fitId }
            .toMutableMap()


    /**
     * The list of fits handles, as an unmodifiable [List], a Compose state object reflecting the actual list of fits
     * in the repository.
     */
    val handles: List<FitHandle>
        get() = mutableHandles


    /**
     * The mutex that protects loading engine fits.
     */
    private val engineFitLoadingMutex = Mutex()


    /**
     * An int state we bump whenever the fit handles change.
     */
    private var _handlesKey by mutableIntStateOf(0)


    /**
     * A Compose [State] value that changes whenever the fit handles change.
     *
     * This can be observed or used as a key in [remember].
     */
    val handlesKey: Any
        get() = _handlesKey


    /**
     * Maps [FitHandle]s by their [Fit]s.
     */
    private val handleByEngineFit: MutableMap<Fit, FitHandle> = mutableMapOf()


    /**
     * Rewrites and replaces the [FitHandle.storedFit].
     */
    private suspend fun FitHandle.replaceStoredFit(updated: (StoredFit) -> StoredFit) {
        val currentStoredFit = impl.requireStoredFit()
        val updatedStoredFit = updated(currentStoredFit)
        repo.updateFit(currentStoredFit, updatedStoredFit)
        impl.storedFit = updatedStoredFit
    }


    /**
     * Creates a new fit with the given name, for the given ship type, and returns its [FitHandle].
     */
    suspend fun addEmpty(
        shipType: ShipType,
        name: String,
        tags: List<String> = emptyList(),
    ): FitHandle {
        val eveData = fittingEngine.eveData
        return add(
            StoredFit.newEmpty(
                name = name,
                tags = tags,
                skillSetId = null,
                shipType = shipType,
                tacticalMode = with(eveData) { shipType.defaultTacticalMode() },
                subsystems = with(eveData) { shipType.defaultSubsystemsOrNull() }
            )
        )
    }


    /**
     * Adds the given fit and returns its [FitHandle].
     */
    suspend fun add(storedFit: StoredFit): FitHandle {
        val fitId = repo.addFit(storedFit)
        return FitHandleImpl(storedFit).also {
            mutableHandles.add(it)
            handleById[fitId] = it
            _handlesKey += 1
            changeListener.onFitAdded(it)
        }
    }


    /**
     * Adds imported [fits] in bulk.
     *
     * Unlike [add], this method also allows adding remote fit effects between the imported fits, which are taken from
     * [importResult] (it's not possible to create a [StoredRemoteEffect] before adding the affecting fit, because it
     * requires its id).
     */
    suspend fun addImportedFits(importResult: FitsImportResult, fits: Collection<StoredFit>) {
        // First, assign ids to all the fits
        val fitById = mutableMapOf<Int, StoredFit>()
        for (addedFit in fits) {
            val id = repo.assignFitId(addedFit)
            fitById[id] = addedFit
        }

        // Then add the remote effects to the fits
        fun buildRemoteEffects(importedEffects: List<ImportedRemoteFitEffect>) =
            buildMap {
                for (commandEffect in importedEffects) {
                    val affectingFit = fitById[commandEffect.affectingFit.id] ?: continue
                    val affectedFit = fitById[commandEffect.affectedFit.id] ?: continue
                    getOrPut(affectedFit, ::mutableListOf).add(
                        StoredRemoteEffect(affectingFit.id!!, commandEffect.enabled)
                    )
                }
            }
        val commandEffectsByAffectedFit = buildRemoteEffects(importResult.commandFitEffects)
        val friendlyEffectsByAffectedFit = buildRemoteEffects(importResult.friendlyFitEffects)
        val hostileEffectsByAffectedFit = buildRemoteEffects(importResult.hostileFitEffects)
        for (fit in fits) {
            fitById[fit.id!!] = fit.copy(
                fitTimes = StoredFit.FitTimes.Unchanged,
                commandEffects = commandEffectsByAffectedFit[fit] ?: emptyList(),
                friendlyEffects = friendlyEffectsByAffectedFit[fit] ?: emptyList(),
                hostileEffects = hostileEffectsByAffectedFit[fit] ?: emptyList(),
            )
        }

        // Now add the fits to the repo
        for (addedFit in fitById.values) {
            val fitId = repo.addFit(addedFit)
            val handle = FitHandleImpl(addedFit)
            mutableHandles.add(handle)
            handleById[fitId] = handle
            changeListener.onFitAdded(handle)
        }

        _handlesKey += 1
    }


    /**
     * Duplicates the given fit, assigning the new fit the given name, and tags.
     *
     * Returns the handle to the new fit.
     */
    suspend fun duplicate(
        fitHandle: FitHandle,
        name: String,
        tags: List<String> = fitHandle.requireStoredFit().tags
    ): FitHandle {
        val newStoredFit = fitHandle.requireStoredFit().copy(
            fitTimes = StoredFit.FitTimes.NewFit,
            id = null,
            name = name,
            tags = tags,
        )
        return add(newStoredFit)
    }


    /**
     * Changes the given fit's name.
     */
    suspend fun setName(handle: FitHandle, name: String) {
        handle.replaceStoredFit {
            it.copy(
                name = name,
                fitTimes = StoredFit.FitTimes.Unchanged
            )
        }

        _handlesKey += 1
        changeListener.onFitNameChanged(handle)
    }


    /**
     * Changes the given fit's tags.
     */
    suspend fun setTags(handle: FitHandle, tags: List<String>) {
        handle.replaceStoredFit {
            it.copy(
                tags = tags,
                fitTimes = StoredFit.FitTimes.Unchanged
            )
        }

        _handlesKey += 1
        changeListener.onFitTagsChanged(handle)
    }


    /**
     * Sets the skill set of the given fits.
     *
     * A `null` [skillSetHandle] indicates the fits should use the default skill set.
     */
    suspend fun setSkillSet(handles: Collection<FitHandle>, skillSetHandle: SkillSetHandle?) {
        // Update the engine fits, if any
        val engineFits = handles.mapNotNull { it.impl.engineFit }
        if (engineFits.isNotEmpty()) {
            val engineSkillSet = TheorycrafterContext.skillSets.engineSkillSetOf(skillSetHandle)
            fittingEngine.modify {
                for (engineFit in engineFits)
                    engineFit.setSkillSet(engineSkillSet)
            }
        }

        // Update the stored fits
        for (handle in handles) {
            handle.replaceStoredFit {
                it.copy(fitTimes = StoredFit.FitTimes.Unchanged, skillSetId = skillSetHandle?.skillSetId)
            }
        }
    }


    /**
     * Sets the skill set of the given fit.
     *
     * A `null` [skillSetHandle] indicates the fit should use the default skill set.
     */
    suspend fun setSkillSet(handle: FitHandle, skillSetHandle: SkillSetHandle?) {
        setSkillSet(listOf(handle), skillSetHandle)
    }


    /**
     * Sets the flagship status of the given fit.
     */
    suspend fun setFlagship(fit: Fit, value: Boolean) {
        fit.isFlagship = value
        save(fit)
    }


    /**
     * Saves the given fit to disk.
     */
    private suspend fun save(fit: Fit) {
        handleOf(fit).replaceStoredFit {
            it.withUpdatesFrom(
                fit = fit,
                fitTimes = StoredFit.FitTimes.ModifiedFit,
                fitIdByFit = { f -> handleByEngineFit[f]!!.fitId }
            )
        }
    }


    /**
     * Deletes the given fits.
     */
    suspend fun delete(handles: Collection<FitHandle>) {
        repo.deleteFits(handles.mapNotNull { it.storedFit })

        mutableHandles.removeAll(handles.toSet())
        for (fitHandle in handles)
            handleById.remove(fitHandle.fitId)

        val deletedEngineFits = handles.mapNotNull{ it.impl.engineFit }.toSet()
        handleByEngineFit.keys.removeAll(deletedEngineFits)

        val affectedFits = mutableSetOf<Fit>()
        fittingEngine.modify {
            for (fit in deletedEngineFits) {
                fit.remove().also {
                    affectedFits.addAll(it)
                }
            }
        }

        // Call the listener before clearing storedFit, so it can access the fit id
        changeListener.onFitsDeleted(handles, affectedFits)

        for (fitHandle in handles) {
            fitHandle.impl.storedFit = null
            fitHandle.impl.engineFit = null
        }

        _handlesKey += 1
    }


    /**
     * Returns the engine [Fit] corresponding to the given [FitHandle], loading it into the engine if needed.
     */
    suspend fun engineFitOf(handle: FitHandle): Fit {
        return engineFitOfOrNull(handle) ?: error("Fit has been deleted or ship type no longer exists")
    }


    /**
     * Returns the engine [Fit] corresponding to the given [FitHandle], loading it into the engine if needed.
     * Returns `null` if the fit has been deleted or can't be loaded (maybe the ship type no longer exists).
     */
    suspend fun engineFitOfOrNull(handle: FitHandle): Fit? {
        // Avoid locking the mutex if it's already been loaded
        loadedEngineFitOf(handle)?.let { return it }

        engineFitLoadingMutex.withLock(owner = null) {
            loadedEngineFitOf(handle)?.let { return it }  // Return if it's already been loaded
            return handle.impl.storedFit?.loadInto(fittingEngine)
        }
    }


    /**
     * Returns the already-loaded [Fit] corresponding to the given [FitHandle], or `null` if it hasn't yet been loaded.
     */
    fun loadedEngineFitOf(handle: FitHandle): Fit? {
        return handle.impl.engineFit
    }


    /**
     * Loads the fit of the given [FitHandle] and returns it as a state.
     */
    @Composable
    fun rememberEngineFitOf(
        handle: FitHandle?,
        onError: (Throwable) -> Unit = { throw it }
    ): Fit? {
        if (handle == null)
            return null

        // If the fit is already loaded, we want to return it immediately, and not even call engineFitOf.
        // If the fit hasn't yet been loaded, we don't want to change the value to `null`, because that
        // would make the fit editor display a `null` value. What we want is to have it display the previous
        // fit until the new one has been loaded.

        var rememberedFit: Fit? by remember { mutableStateOf(null) }

        val loadedFit = loadedEngineFitOf(handle)
        if (loadedFit != null) {
            rememberedFit = loadedFit
            return loadedFit
        }

        val currentOnError by rememberUpdatedState(onError)
        LaunchedEffect(handle) {
            rememberedFit = runCatchingExceptCancellation {
                engineFitOf(handle)
            }.onFailure(currentOnError).getOrNull()
        }

        return rememberedFit
    }



    /**
     * Returns the [StoredFit] of the given [FitHandle].
     */
    fun storedFitOf(handle: FitHandle): StoredFit {
        return handle.impl.requireStoredFit()
    }


    /**
     * Returns the [FitHandle] of the given engine [Fit].
     */
    fun handleOf(fit: Fit): FitHandle = handleOfOrNull(fit) ?: error("No fit handle exists for $fit")


    /**
     * Returns the [FitHandle] of the given engine [Fit], or `null` if it has been deleted, or is not a stored fit.
     */
    fun handleOfOrNull(fit: Fit): FitHandle? = handleByEngineFit[fit]


    /**
     * Returns the [FitHandle] with the given id, or `null` if no such fit exists.
     */
    fun handleById(id: Int): FitHandle? {
        return handleById[id]
    }


    /**
     * Loads the [StoredFit] into the engine and returns it.
     */
    private suspend fun StoredFit.loadInto(fittingEngine: FittingEngine): Fit? {
        val fitId = this.id!!

        val dependencyFitHandles = handleById[fitId]!!.bfsSequence(
            childrenOf = {
                it.impl.requireStoredFit().getDependencyFitIds().mapNotNull { dependencyFitId ->
                    handleById[dependencyFitId]
                }
            },
            initialQueueCapacity = 1
        ).toSet()

        val fitById = mutableMapOf<Int, Fit>()

        // Get the fits that need to be loaded; the rest put into fitById immediately
        val fitHandlesToLoad = mutableListOf<FitHandle>()
        for (fitHandle in dependencyFitHandles) {
            val engineFit = fitHandle.impl.engineFit
            if (engineFit == null)
                fitHandlesToLoad.add(fitHandle)
            else
                fitById[fitHandle.fitId] = engineFit
        }

        // Load all the new dependency fits, but only the self effects
        for (fitHandle in fitHandlesToLoad) {
            val fit = fitHandle.impl.requireStoredFit().loadSelfEffects(fittingEngine) ?: continue
            fitById[fitHandle.fitId] = fit
        }

        // Load all the cross-fit effects
        for (fitHandle in fitHandlesToLoad) {
            val engineFit = fitById[fitHandle.fitId] ?: continue
            fitHandle.impl.requireStoredFit().loadEffectsFromDependencyFits(engineFit, fittingEngine, fitById)
        }

        // Remember all the new fits we loaded
        for (fitHandle in fitHandlesToLoad) {
            val engineFit = fitById[fitHandle.fitId] ?: continue
            fitHandle.impl.engineFit = engineFit
            handleByEngineFit[engineFit] = fitHandle
        }

        return fitById[fitId]
    }


    /**
     * Makes modifications to fits and saves them.
     *
     * Only modifications are allowed. Creating or deleting fits is not allowed via this function.
     */
    suspend fun <T> modifyAndSave(block: FittingEngine.ModificationScope.() -> T): T {
        var modificationScope: FitSavingModificationScope? = null
        try {
            return fittingEngine.modify {
                val scope = FitSavingModificationScope(fittingEngine, this)
                modificationScope = scope
                scope.block()
            }
        } finally {
            modificationScope?.let {
                for (fit in it.modifiedFits)
                    save(fit)
            }
        }
    }


    /**
     * Makes temporary fittings.
     *
     * Modifying any existing fits is not allowed within [block], and all fits created by it are automatically removed
     * when it returns.
     * This function can be used to determine theoretical properties of eve items. For example, it can be used to find
     * the ammo with the highest damage to fit into a weapon.
     */
    suspend fun <T> withTemporaryFit(

        /**
         * A ship to create a temporary fit for, which can be accessed with [block] via [TempFittingScope.tempFit].
         */
        shipType: ShipType,

        /**
         * Code to execute within the temporary fitting scope.
         */
        block: suspend TempFittingScope.() -> T

    ): T {
        val tempFittingScope = makeTempFittingScope(shipType)
        return try {
            tempFittingScope.block()
        } finally {
            tempFittingScope.dispose()
        }
    }


    /**
     * Returns a remembered context for temporary fitting.
     *
     * The given ship is fitted into [TempFittingScope.tempFit].
     */
    @Composable
    fun rememberTempFittingScope(key: Any?, shipType: ShipType): TempFittingScope {
        val context = remember(key, shipType) { makeTempFittingScope(shipType) }
        DisposableEffect(context) {
            onDispose {
                runBlocking {
                    context.dispose()
                }
            }
        }

        return context
    }


    /**
     * Returns a context for temporary fitting. It is the caller's responsibility to dispose of it when done.
     */
    fun makeTempFittingScope(shipType: ShipType): TempFittingScope {
        return TempFittingScope(fittingEngine, shipType)
    }


    /**
     * The interface for listening to fit changes.
     */
    interface ChangeListener {


        /**
         * Invoked when a new fit has been added.
         */
        fun onFitAdded(fitHandle: FitHandle)


        /**
         * Invoked when a fit's name has been changed.
         */
        fun onFitNameChanged(fitHandle: FitHandle)


        /**
         * Invoked when a fit's tags have been changed.
         */
        fun onFitTagsChanged(fitHandle: FitHandle)


        /**
         * Invoked when fits have been deleted.
         *
         * [affectedFits] holds the fits that had been affected by the removal of the deleted fits (because they were
         * the target of a remote effect by a deleted fit).
         */
        suspend fun onFitsDeleted(
            deletedFitHandles: Collection<FitHandle>,
            affectedFits: Collection<Fit>
        )


    }


}


/**
 * A handle to a fit. To obtain the [Fit] this handle is for, use [FitsContext.engineFitOf].
 */
@Stable
sealed interface FitHandle {


    /**
     * The id of the fit.
     */
    val fitId: Int


    /**
     * The name of the fit.
     */
    val name: String


    /**
     * The [StoredFit]; `null` if it has been deleted.
     */
    val storedFit: StoredFit?


    /**
     * Returns the stored fit, throwing an exception if it has been deleted.
     */
    fun requireStoredFit(): StoredFit = storedFit ?: error("Fit has been deleted")


    /**
     * The type of the ship of this fit.
     */
    val shipTypeId: Int
        get() = requireStoredFit().shipTypeId


    /**
     * Whether the fit of this handle has been deleted.
     */
    val isDeleted: Boolean


}


/**
 * The actual implementation of [FitHandle], which allows us to:
 * 1. Hold the underlying [StoredFit], and to replace it when saving the fit on disk (see [FitsContext.save]).
 * 2. Hold the underlying engine [Fit], without a need for an extra map in [FitsContext].
 */
private class FitHandleImpl(storedFit: StoredFit): FitHandle {


    /**
     * The [StoredFit]; `null` when it has been deleted.
     */
    override var storedFit: StoredFit? by mutableStateOf(storedFit)


    /**
     * The engine [Fit].
     */
    var engineFit: Fit? = null


    override val fitId: Int
        get() = requireStoredFit().id!!


    override val name: String
        get() = requireStoredFit().name


    override val isDeleted: Boolean by derivedStateOf(structuralEqualityPolicy()) {
        this.storedFit == null
    }


    override fun toString(): String {
        return storedFit?.let { "$name [id=$fitId]" } ?: "Deleted fit"
    }


}


/**
 * A shortcut for casting [FitHandle] into [FitHandleImpl].
 */
private inline val FitHandle.impl: FitHandleImpl
    get() = this as FitHandleImpl



/**
 * Loads an item type.
 */
private inline fun <T: EveItemType> obtainItemType(itemTypeId: Int, itemTypeName: String, itemById: (Int) -> T?): T? {
    val itemType = itemById(itemTypeId)
    if (itemType == null) {
        System.err.println("Unknown $itemTypeName type id: $itemTypeId")
        return null
    }
    return itemType
}


/**
 * Loads this [StoredFit] into the engine, except for any effects from other fits; returns the resulting engine [Fit].
 */
@Suppress("SuspiciousCollectionReassignment")
private suspend fun StoredFit.loadSelfEffects(fittingEngine: FittingEngine): Fit? {
    // This handles correctly with both default and unknown skill set id
    val skillSetHandle = skillSetId?.let { TheorycrafterContext.skillSets.handleByIdOrNull(it) }
    val skillSet = TheorycrafterContext.skillSets.engineSkillSetOf(skillSetHandle)

    val eveData = fittingEngine.eveData
    with(eveData) {
        return fittingEngine.modify {

            fun Fit.setTacticalMode(storedTacticalMode: StoredFit.StoredTacticalMode) {
                val tacticalModeType = obtainItemType(storedTacticalMode.itemId, "tactical mode", ::tacticalModeTypeOrNull) ?: return
                setTacticalMode(tacticalModeType)
            }

            fun Fit.setSubsystems(subsystems: ValueByEnum<SubsystemType.Kind, StoredFit.StoredSubsystem>) {
                subsystems.forEach { _, storedSubsystem ->
                    val subsystemType = obtainItemType(storedSubsystem.itemId, "subsystem", ::subsystemTypeOrNull) ?: return
                    setSubsystem(subsystemType)
                }
            }

            fun <T: EveItemType> T.mutatedWith(
                mutation: StoredFit.StoredMutation?,
                mutated: context(EveData) T.(Mutaplasmid, Map<Attribute<*>, Double>) -> T
            ): T {
                if (mutation == null)
                    return this
                val mutaplasmid = mutaplasmid(mutation.mutaplasmidId)
                val valueByAttribute = mutation.attributeIdsAndValues.associate { (attrId, value) ->
                    attributes[attrId] to value
                }
                return mutated(mutaplasmid, valueByAttribute)
            }

            fun StoredFit.StoredModule.moduleType(): ModuleType? {
                val baseType = obtainItemType(itemId, "module", ::moduleTypeOrNull) ?: return null
                return baseType.mutatedWith(mutation, ModuleType::mutatedWith)
            }

            fun Module.configureFrom(module: StoredFit.StoredModule) {
                val chargeType = module.chargeId?.let {
                    chargeTypeOrNull(it).also { chargeType ->
                        if (chargeType == null) {
                            System.err.println("Unknown charge type id: $it")
                        }
                    }
                }
                setEnabled(module.enabled)
                setState(module.state)
                if (chargeType != null)
                    setCharge(chargeType)

                // Add default extra attributes
                var extraAttributes = module.extraAttributes
                if ((spoolupCycles != null) && extraAttributes.none { it is SpoolupCyclesExtraAttribute })
                    extraAttributes += SpoolupCyclesExtraAttribute.MAX
                if ((adaptationCycles != null) && extraAttributes.none { it is AdaptationCyclesExtraAttribute })
                    extraAttributes += AdaptationCyclesExtraAttribute.MAX
                for (extraAttribute in extraAttributes)
                    extraAttribute.applyTo(this)
            }

            fun Fit.addRack(modules: List<StoredFit.StoredModule?>) {
                modules.forEachNonNullIndexed { index, module ->
                    val moduleType = module.moduleType() ?: return@forEachNonNullIndexed
                    fitModule(moduleType, slotIndex = index).also {
                        it.configureFrom(module)
                    }
                }
            }

            fun StoredFit.StoredDroneGroup.droneType(): DroneType? {
                val baseType = obtainItemType(itemId, "drone", ::droneTypeOrNull) ?: return null
                return baseType.mutatedWith(mutation, DroneType::mutatedWith)
            }

            fun Fit.addDroneGroup(droneGroup: StoredFit.StoredDroneGroup) {
                val droneType = droneGroup.droneType() ?: return
                addDroneGroup(droneType, size = droneGroup.size).also {
                    it.setActive(droneGroup.active)
                }
            }

            fun Fit.addCargoItem(cargoItem: StoredFit.StoredCargoItem) {
                val itemType = obtainItemType(cargoItem.itemId, "cargo item", ::cargoItemTypeOrNull) ?: return
                addCargoItem(itemType, amount = cargoItem.amount)
            }

            fun Fit.addImplant(implant: StoredFit.StoredImplant) {
                val implantType = obtainItemType(implant.itemId, "implant", implantTypes::getOrNull) ?: return
                fitImplant(implantType).also {
                    it.setEnabled(implant.enabled)
                }
            }

            fun Fit.addBooster(booster: StoredFit.StoredBooster) {
                val boosterType = obtainItemType(booster.itemId, "booster", ::boosterTypeOrNull) ?: return
                fitBooster(boosterType).also {
                    it.setEnabled(booster.enabled)
                    for (sideEffect in it.type.sideEffects) {
                        val active = sideEffect.penalizedAttribute.id in booster.activeSideEffectPenalizedAttributeIds
                        it.setSideEffectActive(sideEffect, active = active)
                    }
                }
            }

            fun Fit.addEnvironment(environment: StoredFit.StoredEnvironment) {
                val envType = obtainItemType(environment.itemId, "environment", ::environmentTypeOrNull) ?: return
                addEnvironment(envType).also {
                    it.setEnabled(environment.enabled)
                }
            }

            fun Fit.addModuleEffect(module: StoredFit.StoredModule) {
                val moduleType = module.moduleType() ?: return
                addModuleEffect(moduleType)?.also {
                    it.module.configureFrom(module)
                }
            }

            fun Fit.addDroneEffect(droneGroup: StoredFit.StoredDroneGroup) {
                val droneType = droneGroup.droneType() ?: return
                addDroneEffect(droneType, size = droneGroup.size).also {
                    it.droneGroup.setActive(droneGroup.active)
                }
            }

            fun Fit.addNonFitIncomingDamageEffect(effect: StoredFit.StoredDamageEffect) {
                when (effect) {
                    is StoredFit.StoredDamageEffect.PureDamage -> addDamageEffect(
                        damageType = effect.damageType,
                        damage = effect.damage
                    )
                    is StoredFit.StoredDamageEffect.Ammo -> {
                        val chargeType = obtainItemType(effect.chargeItemId, "charge", ::chargeTypeOrNull) ?: return
                        addDamageEffect(
                            amount = effect.amount,
                            chargeType = chargeType
                        )
                    }
                    is StoredFit.StoredDamageEffect.DroneGroup -> {
                        val droneType = obtainItemType(effect.droneItemId, "drone", ::droneTypeOrNull) ?: return
                        addDamageEffect(
                            amount = effect.amount,
                            droneType = droneType
                        )
                    }
                    is StoredFit.StoredDamageEffect.Fit -> {}  // Do nothing; these effects are loaded in loadEffectsFromDependencyFits
                }
            }

            val shipType = obtainItemType(shipTypeId, "ship", ::shipTypeOrNull) ?: return@modify null
            newFit(shipType, skillSet).also { fit ->
                if (fit.ship.pilotSecurityStatus != null)
                    fit.ship.setPilotSecurityStatus(securityStatus?.toDouble() ?: 0.0)
                fit.isFlagship = isFlagship
                if (shipType.hasTacticalModes && (tacticalMode != null))
                    fit.setTacticalMode(tacticalMode)
                if (shipType.usesSubsystems && (subsystems != null))
                    fit.setSubsystems(subsystems)
                fit.addRack(highSlotRack)
                fit.addRack(medSlotRack)
                fit.addRack(lowSlotRack)
                fit.addRack(rigs)
                for (droneGroup in droneGroups)
                    fit.addDroneGroup(droneGroup)
                for (cargoItem in cargoItems)
                    fit.addCargoItem(cargoItem)
                for (implant in implants)
                    fit.addImplant(implant)
                for (booster in boosters)
                    fit.addBooster(booster)
                for (environment in environments)
                    fit.addEnvironment(environment)
                for (module in moduleEffects)
                    fit.addModuleEffect(module)
                for (droneGroup in droneEffects)
                    fit.addDroneEffect(droneGroup)
                for (effect in incomingDamageEffects)
                    fit.addNonFitIncomingDamageEffect(effect)
            }
        }
    }
}


/**
 * Loads effects other fits have on the fit.
 */
private suspend fun StoredFit.loadEffectsFromDependencyFits(
    fit: Fit,
    fittingEngine: FittingEngine,
    fitById: Map<Int, Fit>
) {
    // In the most common case don't bother opening a modification scope
    if (commandEffects.isEmpty() && hostileEffects.isEmpty() && friendlyEffects.isEmpty() && incomingDamageEffects.isEmpty())
        return

    fittingEngine.modify {
        for (storedCommandEffect in commandEffects) {
            val commandingFit = fitById[storedCommandEffect.fitId] ?: continue  // Ignore stale dependency fits
            val commandEffect = fit.addCommandEffect(commandingFit)
            commandEffect.setEnabled(storedCommandEffect.enabled)
        }
        for (storedHostileEffect in hostileEffects) {
            val hostileFit = fitById[storedHostileEffect.fitId] ?: continue  // Ignore stale dependency fits
            val hostileEffect = fit.addHostileEffect(hostileFit)
            hostileEffect.setEnabled(storedHostileEffect.enabled)
        }
        for (storedFriendlyEffect in friendlyEffects) {
            val friendlyFit = fitById[storedFriendlyEffect.fitId] ?: continue  // Ignore stale dependency fits
            val friendlyEffect = fit.addFriendlyEffect(friendlyFit)
            friendlyEffect.setEnabled(storedFriendlyEffect.enabled)
        }
        for ((index, storedIncomingDamageEffect) in incomingDamageEffects.withIndex()) {
            if (storedIncomingDamageEffect !is StoredFit.StoredDamageEffect.Fit) continue
            val damagingFit = fitById[storedIncomingDamageEffect.sourceFitId] ?: continue  // Ignore stale dependency fits
            val damageEffect = fit.addDamageEffect(
                amount = storedIncomingDamageEffect.amount,
                damagingFit,
                index = index
            )
            damageEffect.setEnabled(storedIncomingDamageEffect.enabled)
        }
    }
}


/**
 * A [FittingEngine.ModificationScope] that remembers all the fits that were modified, so that they can be saved later.
 */
private class FitSavingModificationScope(
    private val fittingEngine: FittingEngine,
    private val engineModificationScope: FittingEngine.ModificationScope
) : FittingEngine.ModificationScope {


    /**
     * The fits that have been modified in this scope.
     */
    val modifiedFits = mutableSetOf<Fit>()


    override fun newSkillSet(levelOfSkill: (SkillType) -> Int): SkillSet {
        error("New skill sets should be created via SkillSetsContext")
    }


    override fun SkillSet.setLevels(skillAndLevelList: Collection<Pair<SkillType, Int>>) {
        error("Skill sets should be edited via SkillSetsContext")
    }


    override fun SkillSet.remove() {
        error("Skill sets should be removed via SkillSetsContext")
    }


    override fun newFit(shipType: ShipType, skillSet: SkillSet?): Fit {
        error("New fits should be created via FitsContext.addEmpty")
    }


    override fun Fit.remove(): Collection<Fit> {
        error("Fits should be removed via FitsContext.delete")
    }


    private fun <T> modify(fit: Fit, block: FittingEngine.ModificationScope.() -> T): T {
        modifiedFits.add(fit.auxiliaryFitOf ?: fit)

        return engineModificationScope.block()
    }


    /**
     * Verify that the size of a drone group is in the valid range.
     */
    private fun verifyDroneGroupSize(size: Int) {
        // Not more than 127 because of how we store it on disk
        if (size !in 1..127)
            throw IllegalArgumentException("DroneGroup size must be between 1 and 127")
    }


    override fun Fit.setSkillSet(skillSet: SkillSet) {
        return modify(this) {
            setSkillSet(skillSet)
        }
    }


    override fun Fit.setTacticalMode(tacticalModeType: TacticalModeType): TacticalMode {
        return modify(this) {
            setTacticalMode(tacticalModeType)
        }
    }


    override fun Fit.setSubsystem(subsystemType: SubsystemType): Subsystem {
        return modify(this) {
            setSubsystem(subsystemType)
        }
    }


    override fun Fit.fitModule(moduleType: ModuleType, slotIndex: Int): Module {
        return modify(this) {
            fitModule(moduleType, slotIndex)
        }
    }


    override fun Fit.removeModule(slotType: ModuleSlotType, slotIndex: Int) {
        return modify(this) {
            removeModule(slotType, slotIndex)
        }
    }


    override fun Fit.removeModule(module: Module) {
        return modify(this) {
            removeModule(module)
        }
    }


    override fun Fit.reorderModules(slotType: ModuleSlotType, newOrder: (Int) -> Int) {
        return modify(this) {
            reorderModules(slotType, newOrder)
        }
    }


    override fun Module.setState(newState: Module.State) {
        return modify(fit) {
            setState(newState)
        }
    }


    override fun Module.setCharge(charge: ChargeType): Charge {
        return modify(fit) {
            setCharge(charge)
        }
    }


    override fun Module.removeCharge() {
        return modify(fit) {
            removeCharge()
        }
    }


    override fun Fit.addDroneGroup(droneType: DroneType, size: Int, index: Int?): DroneGroup {
        verifyDroneGroupSize(size)
        return modify(this) {
            addDroneGroup(droneType, size = size, index = index)
        }
    }


    override fun Fit.removeDroneGroup(droneGroup: DroneGroup) {
        return modify(this) {
            removeDroneGroup(droneGroup)
        }
    }


    override fun DroneGroup.setActive(isActive: Boolean) {
        return modify(fit) {
            setActive(isActive)
        }
    }


    override fun DroneGroup.setSize(size: Int) {
        verifyDroneGroupSize(size)
        return modify(fit) {
            setSize(size)
        }
    }


    override fun Fit.addCargoItem(itemType: EveItemType, amount: Int, index: Int?): CargoItem {
        return modify(this) {
            addCargoItem(itemType, amount = amount, index = index)
        }
    }


    override fun Fit.removeCargoItem(cargoItem: CargoItem) {
        return modify(this) {
            removeCargoItem(cargoItem)
        }
    }


    override fun CargoItem.setAmount(amount: Int) {
        return modify(fit) {
            setAmount(amount)
        }
    }


    override fun CargoItem.moveTo(index: Int) {
        return modify(fit) {
            moveTo(index)
        }
    }


    override fun Fit.fitImplant(implantType: ImplantType): Implant {
        return modify(this) {
            fitImplant(implantType)
        }
    }


    override fun Fit.removeImplant(slotIndex: Int) {
        return modify(this) {
            removeImplant(slotIndex)
        }
    }


    override fun Fit.fitBooster(boosterType: BoosterType): Booster {
        return modify(this) {
            fitBooster(boosterType)
        }
    }


    override fun Booster.setSideEffectActive(sideEffect: BoosterType.SideEffect, active: Boolean) {
        return modify(fit) {
            setSideEffectActive(sideEffect, active)
        }
    }


    override fun Fit.removeBooster(slotIndex: Int) {
        return modify(this) {
            removeBooster(slotIndex)
        }
    }


    override fun Fit.addEnvironment(environmentType: EnvironmentType, index: Int?): Environment {
        return modify(this) {
            addEnvironment(environmentType, index)
        }
    }


    override fun Fit.removeEnvironment(environment: Environment) {
        return modify(this) {
            removeEnvironment(environment)
        }
    }


    override fun Fit.addCommandEffect(source: Fit, index: Int?): RemoteEffect {
        return modify(this) {
            addCommandEffect(source, index)
        }
    }


    override fun Fit.removeCommandEffect(effect: RemoteEffect) {
        modify(this) {
            removeCommandEffect(effect)
        }
    }


    override fun Fit.addHostileEffect(source: Fit, index: Int?): RemoteEffect {
        return modify(this) {
            addHostileEffect(source, index)
        }
    }


    override fun Fit.removeHostileEffect(effect: RemoteEffect) {
        modify(this) {
            removeHostileEffect(effect)
        }
    }


    override fun Fit.addFriendlyEffect(source: Fit, index: Int?): RemoteEffect {
        return modify(this) {
            addFriendlyEffect(source, index)
        }
    }


    override fun Fit.removeFriendlyEffect(effect: RemoteEffect) {
        modify(this) {
            removeFriendlyEffect(effect)
        }
    }


    override fun Fit.addModuleEffect(moduleType: ModuleType, index: Int?): ModuleEffect? {
        return modify(this) {
            addModuleEffect(moduleType, index)
        }
    }


    override fun Fit.removeModuleEffect(effect: ModuleEffect) {
        modify(this) {
            removeModuleEffect(effect)
        }
    }


    override fun Fit.addDroneEffect(droneType: DroneType, size: Int, index: Int?): DroneEffect {
        return modify(this) {
            verifyDroneGroupSize(size)
            addDroneEffect(droneType, size = size, index = index)
        }
    }


    override fun Fit.removeDroneEffect(effect: DroneEffect) {
        modify(this) {
            removeDroneEffect(effect)
        }
    }


    override fun Fit.addDamageEffect(amount: Int, damagingFit: Fit, index: Int?): DamageEffect.Fit {
        return modify(this) {
            addDamageEffect(amount = amount, damagingFit, index = index)
        }
    }


    override fun Fit.addDamageEffect(amount: Int, chargeType: ChargeType, index: Int?): DamageEffect.Ammo {
        return modify(this) {
            addDamageEffect(amount = amount, chargeType = chargeType, index = index)
        }
    }

    override fun Fit.addDamageEffect(amount: Int, droneType: DroneType, index: Int?): DamageEffect.Drone {
        return modify(this) {
            addDamageEffect(amount = amount, droneType = droneType, index = index)
        }
    }


    override fun Fit.addDamageEffect(damageType: DamageType, damage: Double, index: Int?): DamageEffect.ExactType {
        return modify(this) {
            addDamageEffect(damageType = damageType, damage = damage, index = index)
        }
    }


    override fun Fit.removeDamageEffect(effect: DamageEffect) {
        modify(this) {
            removeDamageEffect(effect)
        }
    }


    override fun <T : Any> Ship.setPinnedPropertyValue(attribute: Attribute<T>, value: T?) {
        return modify(fit) {
            setPinnedPropertyValue(attribute, value)
        }
    }


    override fun <T : Any> Module.setPinnedPropertyValue(attribute: Attribute<T>, value: T?) {
        return modify(fit) {
            setPinnedPropertyValue(attribute, value)
        }
    }


    override fun FitItemWithEnabledState.setEnabled(isEnabled: Boolean) {
        modify(fit) {
            setEnabled(isEnabled)
        }
    }


    override fun EveItemType.setMutatedAttributeValue(attribute: Attribute<*>, value: Double) {
        with(engineModificationScope) {
            setMutatedAttributeValue(attribute, value)
        }

        for (item in fittingEngine.mutatedItemsOf(this)) {
            if (item is FitItem)
                modifiedFits.add(item.fit)
        }
    }


    override fun recomputePropertyValues() {
        with(engineModificationScope) {
            recomputePropertyValues()
        }
    }


}


/**
 * A scope where temporary fittings can be made.
 */
class TempFittingScope(


    /**
     * The fitting engine.
     */
    private val fittingEngine: FittingEngine,


    /**
     * The ship to fit into [tempFit].
     */
    shipType: ShipType


) {


    /**
     * The fits created in this scope.
     */
    private val fits = mutableSetOf<Fit>()


    /**
     * The "main" fit on which temporary fittings will be done.
     */
    val tempFit: Fit


    init {
        tempFit = runBlocking {
            modify {
                newFit(shipType)
            }
        }
    }


    /**
     * A mutex which can be locked if several modifications need to be done atomically (such as fitting and then
     * unfitting a module).
     *
     * Note that this mutex is local to *this* [TempFittingScope], and that it is *not* locked automatically by
     * [modify]. It's completely optional.
     */
    val mutex = Mutex()


    /**
     * Makes modifications to the engine.
     */
    suspend fun <T> modify(block: FittingEngine.ModificationScope.() -> T): T {
        return fittingEngine.modify(silent = true) {
            ModificationScope(this).block()
        }
    }


    /**
     * Removes the fits created in this scope.
     */
    suspend fun dispose() {
        fittingEngine.modify(silent = true) {
            for (fit in fits)
                fit.remove()
        }
    }


    /**
     * Makes [target] a copy of [source] by fitting it with the same modules, drones etc.
     */
    fun FittingEngine.ModificationScope.copyFit(source: Fit, target: Fit) {
        source.tacticalMode?.let {
            target.setTacticalMode(it.type)
        }
        source.subsystemByKind?.values?.let { subsystems ->
            for (subsystem in subsystems) {
                if (subsystem != null) {
                    target.setSubsystem(subsystem.type)
                }
            }
        }
        target.modules.all.toList().forEach { target.removeModule(it) }
        target.drones.all.toList().forEach { target.removeDroneGroup(it) }
        target.implants.fitted.toList().forEach { target.removeImplant(it) }
        target.boosters.fitted.toList().forEach { target.removeBooster(it) }
        target.remoteEffects.command.allExcludingAuxiliary.forEach { target.removeCommandEffect(it) }
        target.remoteEffects.friendly.allExcludingAuxiliary.forEach {
            target.removeFriendlyEffect(it)
        }
        target.remoteEffects.hostile.allExcludingAuxiliary.forEach {
            target.removeHostileEffect(it)
        }
        target.remoteEffects.allModule.toList().forEach { target.removeModuleEffect(it) }
        target.remoteEffects.allDrone.toList().forEach { target.removeDroneEffect(it) }
        target.incomingDamage.effects.toList().forEach { target.removeDamageEffect(it) }
        target.environments.toList().forEach { target.removeEnvironment(it) }
        target.cargohold.contents.toList().forEach { target.removeCargoItem(it) }

        val countByRack = ModuleSlotType.entries.associateWithTo(mutableMapOf()) { 0 }
        source.modules.all.forEach {
            val count = countByRack[it.type.slotType]!!
            val module = target.fitModule(it.type, count)
            it.loadedCharge?.let { charge ->
                module.setCharge(charge.type)
            }
            module.setState(it.state)
            it.spoolupCycles?.let { spoolupCyclesAttr ->
                module.setSpoolupCycles(spoolupCyclesAttr.doubleValue)
            }
            it.adaptationCycles?.let { adaptationCyclesAttr ->
                module.setAdaptationCycles(adaptationCyclesAttr.value)
            }
            countByRack[it.type.slotType] = count+1
        }
        source.drones.all.forEach {
            val droneGroup = target.addDroneGroup(it.type, it.size)
            droneGroup.setActive(it.active)
        }
        source.implants.fitted.forEach {
            val implant = target.fitImplant(it.type)
            implant.setEnabled(it.enabled)
        }
        source.boosters.fitted.forEach {
            val booster = target.fitBooster(it.type)
            booster.setEnabled(it.enabled)
            for (sideEffect in it.type.sideEffects) {
                booster.setSideEffectActive(sideEffect, it.isSideEffectActive(sideEffect))
            }
        }
        source.remoteEffects.command.allExcludingAuxiliary.forEach {
            val effect = target.addCommandEffect(it.source)
            effect.setEnabled(it.enabled)
        }
        source.remoteEffects.friendly.allExcludingAuxiliary.forEach {
            val effect = target.addFriendlyEffect(it.source)
            effect.setEnabled(it.enabled)
        }
        source.remoteEffects.hostile.allExcludingAuxiliary.forEach {
            val effect = target.addHostileEffect(it.source)
            effect.setEnabled(it.enabled)
        }
        source.remoteEffects.allModule.forEach {
            val module = it.module
            val newEffect = target.addModuleEffect(module.type)!!
            val newModule = newEffect.module
            module.loadedCharge?.let { charge ->
                newModule.setCharge(charge.type)
            }
            newModule.setState(module.state)
        }
        source.remoteEffects.allDrone.forEach {
            val drone = it.droneGroup
            val newEffect = target.addDroneEffect(drone.type, drone.size)
            newEffect.droneGroup.setActive(drone.active)
        }
        source.incomingDamage.effects.forEach {
            val effect = when (it) {
                is DamageEffect.Fit -> target.addDamageEffect(it.amount, it.source)
                is DamageEffect.Ammo -> target.addDamageEffect(it.amount, it.chargeType)
                is DamageEffect.ExactType -> target.addDamageEffect(it.damageType, it.damage)
                is DamageEffect.Drone -> target.addDamageEffect(amount = it.droneGroup.size, droneType = it.droneGroup.type)
            }
            effect.setEnabled(it.enabled)
        }
        source.environments.forEach {
            val env = target.addEnvironment(it.type)
            env.setEnabled(it.enabled)
        }
        source.cargohold.contents.forEach {
            target.addCargoItem(it.type, it.amount)
        }
    }


    /**
     * Throws an exception if the given fit was not created in this scope.
     */
    private fun checkFit(fit: Fit) {
        if ((fit !in fits) && fits.none { fit.auxiliaryFitOf == it })
            throw IllegalArgumentException("Attempting to modify an unrelated fit in a temporary fitting scope")
    }


    /**
     * The [FittingEngine.ModificationScope] passed to the action block in [modify].
     */
    private inner class ModificationScope(
        private val engineModificationScope: FittingEngine.ModificationScope
    ) : FittingEngine.ModificationScope {


        override fun newSkillSet(levelOfSkill: (SkillType) -> Int): SkillSet {
            error("Custom skill sets are not supported in temporary modifications")
        }


        override fun SkillSet.setLevels(skillAndLevelList: Collection<Pair<SkillType, Int>>) {
            error("Custom skill sets are not supported in temporary modifications")
        }


        override fun SkillSet.remove() {
            error("Custom skill sets are not supported in temporary modifications")
        }


        override fun newFit(shipType: ShipType, skillSet: SkillSet?): Fit {
            return with(engineModificationScope) {
                newFit(shipType)
            }.also {
                fits.add(it)
            }
        }


        override fun Fit.remove(): Collection<Fit> {
            val fit = this
            checkFit(fit)

            return with(engineModificationScope) {
                fit.remove()
            }.also {
                this@TempFittingScope.fits.remove(fit)
            }
        }


        override fun Fit.setSkillSet(skillSet: SkillSet) {
            checkFit(this)

            return with(engineModificationScope) {
                setSkillSet(skillSet)
            }
        }


        override fun Fit.setTacticalMode(tacticalModeType: TacticalModeType): TacticalMode {
            checkFit(this)

            return with(engineModificationScope) {
                setTacticalMode(tacticalModeType)
            }
        }


        override fun Fit.setSubsystem(subsystemType: SubsystemType): Subsystem {
            checkFit(this)

            return with(engineModificationScope) {
                setSubsystem(subsystemType)
            }
        }


        override fun Fit.fitModule(moduleType: ModuleType, slotIndex: Int): Module {
            checkFit(this)

            return with(engineModificationScope) {
                fitModule(moduleType, slotIndex)
            }
        }


        override fun Fit.removeModule(slotType: ModuleSlotType, slotIndex: Int) {
            checkFit(this)

            return with(engineModificationScope) {
                removeModule(slotType, slotIndex)
            }
        }


        override fun Fit.removeModule(module: Module) {
            checkFit(this)

            return with(engineModificationScope) {
                removeModule(module)
            }
        }


        override fun Fit.reorderModules(slotType: ModuleSlotType, newOrder: (Int) -> Int) {
            checkFit(this)

            return with(engineModificationScope) {
                reorderModules(slotType, newOrder)
            }
        }


        override fun Module.setState(newState: Module.State) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setState(newState)
            }
        }


        override fun Module.setCharge(charge: ChargeType): Charge {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setCharge(charge)
            }
        }


        override fun Module.removeCharge() {
            checkFit(this.fit)

            return with(engineModificationScope) {
                removeCharge()
            }
        }


        override fun Fit.addDroneGroup(droneType: DroneType, size: Int, index: Int?): DroneGroup {
            checkFit(this)

            return with(engineModificationScope) {
                addDroneGroup(droneType, size = size, index = index)
            }
        }


        override fun Fit.removeDroneGroup(droneGroup: DroneGroup) {
            checkFit(this)

            return with(engineModificationScope) {
                removeDroneGroup(droneGroup)
            }
        }


        override fun DroneGroup.setActive(isActive: Boolean) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setActive(isActive)
            }
        }


        override fun DroneGroup.setSize(size: Int) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setSize(size)
            }
        }


        override fun Fit.addCargoItem(itemType: EveItemType, amount: Int, index: Int?): CargoItem {
            checkFit(this)

            return with(engineModificationScope) {
                addCargoItem(itemType, amount = amount, index = index)
            }
        }


        override fun Fit.removeCargoItem(cargoItem: CargoItem) {
            checkFit(this)

            return with(engineModificationScope) {
                removeCargoItem(cargoItem)
            }
        }


        override fun CargoItem.setAmount(amount: Int) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setAmount(amount)
            }
        }


        override fun CargoItem.moveTo(index: Int) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                moveTo(index)
            }
        }

        override fun Fit.fitImplant(implantType: ImplantType): Implant {
            checkFit(this)

            return with(engineModificationScope) {
                fitImplant(implantType)
            }
        }


        override fun Fit.removeImplant(slotIndex: Int) {
            checkFit(this)

            return with(engineModificationScope) {
                removeImplant(slotIndex)
            }
        }


        override fun Fit.fitBooster(boosterType: BoosterType): Booster {
            checkFit(this)

            return with(engineModificationScope) {
                fitBooster(boosterType)
            }
        }


        override fun Booster.setSideEffectActive(sideEffect: BoosterType.SideEffect, active: Boolean) {
            checkFit(fit)

            return with(engineModificationScope) {
                setSideEffectActive(sideEffect, active)
            }
        }

        override fun Fit.removeBooster(slotIndex: Int) {
            checkFit(this)

            return with(engineModificationScope) {
                removeBooster(slotIndex)
            }
        }


        override fun Fit.addEnvironment(environmentType: EnvironmentType, index: Int?): Environment {
            checkFit(this)

            return with(engineModificationScope) {
                addEnvironment(environmentType, index)
            }
        }


        override fun Fit.removeEnvironment(environment: Environment) {
            checkFit(this)

            return with(engineModificationScope) {
                removeEnvironment(environment)
            }
        }


        override fun Fit.addCommandEffect(source: Fit, index: Int?): RemoteEffect {
            checkFit(this)

            return with(engineModificationScope) {
                addCommandEffect(source, index)
            }
        }


        override fun Fit.removeCommandEffect(effect: RemoteEffect) {
            checkFit(this)

            with(engineModificationScope) {
                removeCommandEffect(effect)
            }
        }


        override fun Fit.addHostileEffect(source: Fit, index: Int?): RemoteEffect {
            checkFit(this)

            return with(engineModificationScope) {
                addHostileEffect(source, index)
            }
        }


        override fun Fit.removeHostileEffect(effect: RemoteEffect) {
            checkFit(this)

            with(engineModificationScope) {
                removeHostileEffect(effect)
            }
        }


        override fun Fit.addFriendlyEffect(source: Fit, index: Int?): RemoteEffect {
            checkFit(this)

            return with(engineModificationScope) {
                addFriendlyEffect(source, index)
            }
        }


        override fun Fit.removeFriendlyEffect(effect: RemoteEffect) {
            checkFit(this)

            with(engineModificationScope) {
                removeFriendlyEffect(effect)
            }
        }


        override fun Fit.addModuleEffect(moduleType: ModuleType, index: Int?): ModuleEffect? {
            checkFit(this)

            return with(engineModificationScope) {
                addModuleEffect(moduleType, index)
            }
        }


        override fun Fit.removeModuleEffect(effect: ModuleEffect) {
            checkFit(this)

            with(engineModificationScope) {
                removeModuleEffect(effect)
            }
        }


        override fun Fit.addDroneEffect(droneType: DroneType, size: Int, index: Int?): DroneEffect {
            checkFit(this)

            return with(engineModificationScope) {
                addDroneEffect(droneType, size = size, index = index)
            }
        }


        override fun Fit.removeDroneEffect(effect: DroneEffect) {
            checkFit(this)

            with(engineModificationScope) {
                removeDroneEffect(effect)
            }
        }

        override fun Fit.addDamageEffect(amount: Int, damagingFit: Fit, index: Int?): DamageEffect.Fit {
            checkFit(this)

            return with(engineModificationScope) {
                addDamageEffect(amount = amount, damagingFit, index = index)
            }
        }

        override fun Fit.addDamageEffect(amount: Int, chargeType: ChargeType, index: Int?): DamageEffect.Ammo {
            checkFit(this)

            return with(engineModificationScope) {
                addDamageEffect(amount = amount, chargeType = chargeType, index = index)
            }
        }

        override fun Fit.addDamageEffect(amount: Int, droneType: DroneType, index: Int?): DamageEffect.Drone {
            checkFit(this)

            return with(engineModificationScope) {
                addDamageEffect(amount = amount, droneType = droneType, index = index)
            }
        }


        override fun Fit.addDamageEffect(damageType: DamageType, damage: Double, index: Int?): DamageEffect.ExactType {
            checkFit(this)

            return with(engineModificationScope) {
                addDamageEffect(damageType = damageType, damage = damage, index = index)
            }
        }


        override fun Fit.removeDamageEffect(effect: DamageEffect) {
            checkFit(this)

            with(engineModificationScope) {
                removeDamageEffect(effect)
            }
        }

        override fun <T : Any> Ship.setPinnedPropertyValue(attribute: Attribute<T>, value: T?) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setPinnedPropertyValue(attribute, value)
            }
        }


        override fun <T : Any> Module.setPinnedPropertyValue(attribute: Attribute<T>, value: T?) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setPinnedPropertyValue(attribute, value)
            }
        }


        override fun FitItemWithEnabledState.setEnabled(isEnabled: Boolean) {
            checkFit(this.fit)

            return with(engineModificationScope) {
                setEnabled(isEnabled)
            }
        }


        override fun EveItemType.setMutatedAttributeValue(attribute: Attribute<*>, value: Double) {
            with(engineModificationScope) {
                setMutatedAttributeValue(attribute, value)
            }
        }


        override fun recomputePropertyValues() {
            with(engineModificationScope) {
                recomputePropertyValues()
            }
        }


    }


}

