/**
 * Defines the formats for copy/pasting items in the fit editor.
 */
package theorycrafter.ui.fiteditor

import eve.data.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import theorycrafter.esi.DogmaApi
import theorycrafter.esi.getMutatedType
import theorycrafter.fitting.*
import theorycrafter.formats.EftMutationDescriptor
import theorycrafter.formats.toEftMutationDescriptor
import theorycrafter.fits.mutatedName
import theorycrafter.fits.mutatedWith


/**
 * Returns the text to put into the clipboard for the given booster.
 */
fun Booster.clipboardText() = type.name


/**
 * Returns the [BoosterType] corresponding to the given clipboard text.
 */
context(EveData)
fun boosterFromClipboardText(text: String): BoosterType? {
    // We allow an amount (but ignore it) in order to allow pasting from cargo
    val name = parseName(text)
    return boosterTypeOrNull(name)
}


/**
 * Returns the text to put into the clipboard for the given implant.
 */
fun Implant.clipboardText() = type.name


/**
 * Returns the [ImplantType] corresponding to the given clipboard text.
 */
context(EveData)
fun implantFromClipboardText(text: String): ImplantType? {
    // We allow an amount (but ignore it) in order to allow pasting from cargo
    val name = parseName(text)
    return implantTypes.getOrNull(name)
}


/**
 * Returns the text to put into the clipboard for the given cargo item.
 */
fun CargoItem.clipboardText() = clipboardText(type.name, amount)


/**
 * Returns the item type and amount corresponding to the given clipboard text.
 */
context(EveData)
fun cargoItemFromClipboardText(text: String): Pair<EveItemType, Int?>? {
    val (name, amount) = parseAmountAndName(text)
    val item = cargoItemTypeOrNull(name) ?: return null

    return Pair(item, amount)
}


/**
 * Returns an [EftMutationDescriptor] for the given item type.
 */
private fun EftMutationDescriptor(itemType: EveItemType): EftMutationDescriptor {
    val mutation = itemType.mutation ?: throw IllegalArgumentException("$itemType is not a mutated item")

    return EftMutationDescriptor(
        baseTypeName = mutation.baseType.name,
        mutaplasmidName = mutation.mutaplasmid.name,
        attributeNamesAndValues = mutation.mutatedAttributesAndValues().map { (attribute, value) ->
            Pair(attribute.name, value)
        }
    )
}


/**
 * Returns the text to put into the clipboard for the given drone group.
 */
fun DroneGroup.clipboardText(): String {
    val mutation = type.mutation
    if (mutation == null)
        return clipboardText(type.name, size)

    return EftMutationDescriptor(type).toString()
}


/**
 * Returns the drone type and amount corresponding to the given clipboard text.
 */
context(EveData)
fun droneGroupFromClipboardText(text: String): Pair<DroneType, Int?>? {
    if (text.trim().indexOfAny(charArrayOf('\r', '\n')) == -1) {  // Single line is regular (non-mutated) drone
        val (name, amount) = parseAmountAndName(text)
        val item = droneTypeOrNull(name) ?: return null
        return Pair(item, amount)
    }

    val mutationDescriptor = text.toEftMutationDescriptor()
    return mutationDescriptor?.let {
        val baseType = droneTypeOrNull(it.baseTypeName) ?: return null
        val mutaplasmid = mutaplasmidOrNull(it.mutaplasmidName) ?: return null
        val valueByAttribute = it.attributeNamesAndValues.toValueByAttribute()
        baseType.mutated(
            mutaplasmid = mutaplasmid,
            name = baseType.mutatedName(mutaplasmid),
            valueByAttribute = valueByAttribute
        ) to 1
    }
}


/**
 * Converts a list of attribute names and values to a map of values by attributes.
 */
context(EveData)
private fun List<Pair<String, Double>>.toValueByAttribute() =
    mapNotNull { (attrName, value) ->
        val attribute = attributes[attrName] ?: return@mapNotNull null
        Pair(attribute, value)
    }.toMap()


/**
 * Returns the text to put into the clipboard for the given module slot group.
 */
fun ModuleSlotGroup.clipboardText(): String {
    val moduleType = requireRepModule().type
    val mutation = moduleType.mutation
    if (mutation == null) {
        return if (this is MultiModuleSlot)
            clipboardText(moduleType.name, this.slotCount)
        else
            moduleType.name
    }

    return EftMutationDescriptor(moduleType).toString()
}


/**
 * Returns the module type corresponding to the given clipboard text.
 */
context(EveData)
fun moduleFromClipboardText(text: String): ModuleType? {
    if (text.trim().indexOfAny(charArrayOf('\r', '\n')) == -1) {  // Single line is regular (non-mutated) module
        // We allow an amount (but ignore it) in order to allow pasting from cargo
        val name = parseName(text)
        return moduleTypeOrNull(name)
    }

    // Mutated module
    val mutationDescriptor = text.toEftMutationDescriptor()
    return mutationDescriptor?.let {
        val baseType = moduleTypeOrNull(it.baseTypeName) ?: return null
        val mutaplasmid = mutaplasmidOrNull(it.mutaplasmidName) ?: return null
        val valueByAttribute = it.attributeNamesAndValues.toValueByAttribute()
        baseType.mutatedWith(
            mutaplasmid = mutaplasmid,
            valueByAttribute = valueByAttribute
        )
    }
}


/**
 * The regex for Eve's "showinfo" URLs, matching the type id and item id.
 */
private val DynamicItemRegex = "<url=showinfo:(\\d+)//(\\d+)>.+</url>".toRegex()


/**
 * Returns a mutated item type described by the given text in Eve's "showinfo" URL format.
 * Returns `null` if the text is not in the correct format.
 * The [Deferred]'s value is `null` if unable to retrieve the information from ESI.
 */
context(EveData)
private fun <T> dynamicItemFromClipboardText(text: String, transform: (EveItemType) -> T?): Deferred<T?>? {
    val match = DynamicItemRegex.find(text) ?: return null

    val (typeIdString, itemIdString) = match.destructured
    val typeId = typeIdString.toIntOrNull() ?: return null
    val itemId = itemIdString.toLongOrNull() ?: return null

    return CoroutineScope(Dispatchers.IO).async {
        DogmaApi.getMutatedType(typeId = typeId, itemId = itemId)?.let {
            transform(it)
        }
    }
}


/**
 * Returns a [Deferred] with a mutated module type described by the given text in Eve's "showinfo" URL format.
 * Returns `null` if the text is not in the correct format.
 * The [Deferred]'s value is `null` if unable to retrieve the information from ESI, or if the item is not a module.
 */
context(EveData)
fun dynamicModuleFromClipboardText(text: String): Deferred<ModuleType?>? {
    return dynamicItemFromClipboardText(text) { it as? ModuleType }
}


/**
 * Returns a [Deferred] with a mutated drone type described by the given text in Eve's "showinfo" URL format.
 * Returns `null` if the text is not in the correct format.
 * The [Deferred]'s value is `null` if unable to retrieve the information from ESI or if the item is not a drone.
 * The [Int] in the pair is for type compatibility with [droneGroupFromClipboardText] and is always `null`.
 */
context(EveData)
fun dynamicDroneFromClipboardText(text: String): Deferred<Pair<DroneType, Int?>?>? {
    return dynamicItemFromClipboardText(text) {
        if (it !is DroneType)
            return@dynamicItemFromClipboardText null
        Pair(it, null)
    }
}


/**
 * Returns the text to put into the clipboard for the given charge.
 */
fun ChargeType.clipboardText() = name


/**
 * Returns the charge type corresponding to the given clipboard text.
 */
context(EveData)
fun chargeFromClipboardText(text: String): ChargeType? {
    // We allow an amount (but ignore it) in order to allow pasting from cargo
    val name = parseName(text)
    return chargeTypeOrNull(name)
}


/**
 * Returns the text to put into the clipboard for the given subsystem.
 */
fun Subsystem.clipboardText() = type.name


/**
 * Returns the subsystem type corresponding to the given clipboard text.
 */
context(EveData)
fun subsystemFromClipboardText(text: String): SubsystemType? {
    // We allow an amount (but ignore it) in order to allow pasting from cargo
    val name = parseName(text)
    return subsystemTypeOrNull(name)
}


/**
 * The separator between the amount of an item and the item name.
 */
const val AmountSeparator = "x "





/**
 * Returns the text for the given amount and item.
 */
private fun clipboardText(itemName: String, amount: Int) = "$amount$AmountSeparator$itemName"


/**
 * Parses the given text into an item name and an optional amount.
 */
private fun parseAmountAndName(text: String): Pair<String, Int?> {
    val amountSeparatorIndex = text.indexOf(AmountSeparator)
    val amount =
        if (amountSeparatorIndex == -1)
            null
        else
            text.take(amountSeparatorIndex).toIntOrNull()

    val itemName =
        // Check (amount == null), not (amountSeparatorIndex == -1) because "x " can appear in the item name
        if (amount == null)
            text
        else
            text.substring(amountSeparatorIndex + AmountSeparator.length)

    return Pair(itemName, amount)
}


/**
 * Parses the given text into an item name, ignoring any amount it may specify.
 */
private fun parseName(text: String) = parseAmountAndName(text).first


/**
 * Returns the text to put into the clipboard for the given environment.
 */
fun Environment.clipboardText() = type.name


/**
 * Returns the subsystem type corresponding to the given clipboard text.
 */
context(EveData)
fun environmentFromClipboardText(text: String): EnvironmentType? {
    val name = parseName(text)
    return environmentTypeOrNull(name)
}
