package theorycrafter.storage

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import theorycrafter.fits.FitsContext
import kotlin.math.max


/**
 * Detects whether a given fit is a duplicate of an existing fit.
 */
@Immutable
class DuplicateFitDetector(private val fitsByShipTypeIdAndName: Map<Pair<Int, String>, List<StoredFit>>) {


    /**
     * Returns a fit that is a duplicate of the given one or `null` if no such fit exists.
     */
    fun findDuplicateOf(fit: StoredFit): StoredFit? {
        val duplicateCandidates = fitsByShipTypeIdAndName[Pair(fit.shipTypeId, fit.name)] ?: emptyList()
        return duplicateCandidates.find {
            candidate -> fit.isDuplicateOf(candidate)
        }
    }


    /**
     * Returns whether the given fit has a duplicate.
     */
    fun duplicateOfExists(fit: StoredFit): Boolean = findDuplicateOf(fit) != null


    /**
     * Returns whether the given fit is a duplicate of this fit.
     */
    private fun StoredFit.isDuplicateOf(other: StoredFit?): Boolean {
        if (other == null)
            return false

        fun mutationsEqual(mutation1: StoredFit.StoredMutation?, mutation2: StoredFit.StoredMutation?): Boolean {
            if ((mutation1 == null) && (mutation2 == null))
                return true
            if ((mutation1 == null) || (mutation2 == null))
                return false
            return (mutation1.mutaplasmidId == mutation2.mutaplasmidId) &&
                    (mutation1.attributeIdsAndValues.sortedBy { it.first } == mutation2.attributeIdsAndValues.sortedBy { it.first })
        }

        fun racksEqual(rack1: List<StoredFit.StoredModule?>, rack2: List<StoredFit.StoredModule?>): Boolean {
            for (index in 0..max(rack1.lastIndex, rack2.lastIndex)) {
                val module1 = rack1.getOrNull(index)
                val module2 = rack2.getOrNull(index)
                if (module1?.itemId != module2?.itemId)
                    return false
                if (!mutationsEqual(module1?.mutation, module2?.mutation))
                    return false
            }
            return true
        }

        fun cargoholdsEqual(cargo1: List<StoredFit.StoredCargoItem>, cargo2: List<StoredFit.StoredCargoItem>): Boolean {
            val itemsById1 = cargo1.groupBy { it.itemId }
            val itemsById2 = cargo2.groupBy { it.itemId }
            if (itemsById1.size != itemsById2.size)
                return false
            val allItemIds = itemsById1.keys + itemsById2.keys
            for (itemId in allItemIds) {
                val itemAmount1 = (itemsById1[itemId] ?: emptyList()).sumOf { it.amount }
                val itemAmount2 = (itemsById2[itemId] ?: emptyList()).sumOf { it.amount }
                if (itemAmount1 != itemAmount2)
                    return false
            }
            return true
        }

        fun implantsEqual(implants1: List<StoredFit.StoredImplant>, implants2: List<StoredFit.StoredImplant>): Boolean {
            if (implants1.size != implants2.size)
                return false
            for (index in 0..implants1.lastIndex) {
                if (implants1[index].itemId != implants2[index].itemId)
                    return false
            }
            return true
        }

        fun boostersEqual(boosters1: List<StoredFit.StoredBooster>, boosters2: List<StoredFit.StoredBooster>): Boolean {
            if (boosters1.size != boosters2.size)
                return false
            for (index in 0..boosters1.lastIndex) {
                if (boosters1[index].itemId != boosters2[index].itemId)
                    return false
            }
            return true
        }

        return racksEqual(highSlotRack, other.highSlotRack) &&
                racksEqual(medSlotRack, other.medSlotRack) &&
                racksEqual(lowSlotRack, other.lowSlotRack) &&
                racksEqual(rigs, other.rigs) &&
                cargoholdsEqual(cargoItems, other.cargoItems) &&
                implantsEqual(implants, other.implants) &&
                boostersEqual(boosters, other.boosters)
    }

}


/**
 * Returns a state that provides a [DuplicateFitDetector].
 */
@Composable
fun produceDuplicateFitDetector(fitsContext: FitsContext): State<DuplicateFitDetector?> {
    return produceState(key1 = fitsContext.handlesKey, initialValue = null) {
        value = createDuplicateFitDetector(fitsContext)
    }
}


/**
 * Creates a new [DuplicateFitDetector].
 */
suspend fun createDuplicateFitDetector(fitsContext: FitsContext): DuplicateFitDetector {
    val storedFits = fitsContext.handles.mapNotNull { it.storedFit }
    return withContext(Dispatchers.Default) {
        DuplicateFitDetector(
            storedFits.groupBy { Pair(it.shipTypeId, it.name) }
        )
    }
}
