package theorycrafter.ui.tournaments

import androidx.compose.foundation.layout.*
import androidx.compose.material.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.window.WindowPosition
import compose.input.KeyShortcut
import compose.input.KeyboardModifierMatcher
import eve.data.EveData
import eve.data.typeid.isCommandBurst
import kotlinx.coroutines.launch
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.hostOs
import theorycrafter.*
import theorycrafter.fitting.Fit
import theorycrafter.tournaments.*
import theorycrafter.tournaments.Composition
import theorycrafter.ui.*
import theorycrafter.ui.fiteditor.KeyShortcutsIcon
import theorycrafter.ui.widgets.*
import java.util.*


/**
 * The tournament window.
 */
@Composable
fun TournamentWindow() {
    val windowManager = LocalTheorycrafterWindowManager.current
    val state = windowManager.tournamentWindowState
    if (state !is TournamentWindowState.Open)
        return

    val tournamentDesc = state.tournamentDescriptor
    val windowState = rememberWindowStateAndUpdateSettings(
        windowSettings = state.windowSettings,
        defaultPosition = { WindowPosition.PlatformDefault },
        defaultSize = { TheorycrafterTheme.sizes.tournamentWindowDefaultSize },
    )

    TheorycrafterWindow(
        title = tournamentDesc.name,
        onCloseRequest = windowManager::closeTournamentWindow,
        state = windowState
    ) {
        key(tournamentDesc.id) {
            TournamentWindowContent(tournamentDesc)
        }

        LaunchedEffect(state, window) {
            state.window = window
        }

        LaunchedEffect(tournamentDesc) {
            window.toFront()  // The tournament may have changed
        }
    }
}


/**
 * The state of a tournament window.
 */
sealed interface TournamentWindowState {


    /**
     * The window is closed.
     */
    @Stable
    data object Closed: TournamentWindowState


    /**
     * The window is open, showing the given tournament.
     */
    @Stable
    class Open(


        /**
         * The descriptor of the tournament being displayed.
         */
        val tournamentDescriptor: TournamentDescriptor,


        /**
         * The window settings.
         */
        val windowSettings: TheorycrafterSettings.WindowSettings


    ): TheorycrafterWindowInfo(), TournamentWindowState


}


/**
 * The content of the tournament window.
 */
@Composable
fun TournamentWindowContent(tournamentDescriptor: TournamentDescriptor) {
    val tournament by produceState<Tournament?>(initialValue = null, key1 = tournamentDescriptor) {
        value = TheorycrafterContext.tournaments.loadTournament(tournamentDescriptor)
    }
    val snackbarHost = remember { SnackbarHostState() }
    CompositionLocalProvider(LocalSnackbarHost provides snackbarHost) {
        val (comp, setComp) = remember { mutableStateOf<Composition?>(null) }
        LaunchedEffect(comp?.deleted) {
            if ((comp != null) && comp.deleted)
                setComp(null)
        }

        val compositionEditorFocusRequester = remember { FocusRequester() }

        ThreePanelScaffold(
            modifier = Modifier.fillMaxSize(),
            left = {
                TitledPanel(
                    title = tournamentDescriptor.compositionsWindowTitle,
                    modifier = Modifier.width(TheorycrafterTheme.sizes.compositionListWidth)
                ) {
                    tournament?.let {
                        CompositionsPanel(
                            tournament = it,
                            onShowComposition = setComp,
                            focusCompositionEditor = {
                                compositionEditorFocusRequester.requestFocus()
                            }
                        )
                    }
                }
            },
            middle = {
                if (comp == null)
                    return@ThreePanelScaffold
                key(comp) {
                    TitledPanel(
                        title = comp.name,
                        actionsButton = { CompositionActionButton(comp) }
                    ) {
                        Column(Modifier.fillMaxSize()) {
                            CompositionEditor(
                                composition = comp,
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .weight(1f)
                                    .focusRequester(compositionEditorFocusRequester)
                            )

                            KeyShortcutsIcon(
                                modifier = Modifier
                                    .align(Alignment.End)
                                    .padding(
                                        end = TheorycrafterTheme.spacing.horizontalEdgeMargin,
                                        bottom = TheorycrafterTheme.spacing.horizontalEdgeMargin,
                                        top = TheorycrafterTheme.spacing.small
                                    )
                            ) {
                                KeyShortcutsTooltip(COMPOSITION_KEY_SHORTCUTS_TOOLTIP_CONTENTS)
                            }
                        }
                    }
                }
            },
            snackbarHostState = snackbarHost
        )
    }
}


/**
 * The action button for the composition.
 */
@Composable
private fun CompositionActionButton(composition: Composition) {
    val snackbarState = LocalSnackbarHost.current
    val coroutineScope = rememberCoroutineScope()
    val compositionExporter = rememberCompositionExporter()

    val compLabel = composition.tournament.compositionLabel(capitalized = true)
    ActionsMenuButton(contentDescription = "$compLabel Actions") {
        menuItem(
            text = "Copy $compLabel to Clipboard",
            icon = { Icons.Copy() },
            keyShortcut = CopyCompositionToClipboardKeyShortcut
        ) {
            compositionExporter.copyToClipboard(composition)
        }

        menuItem(
            text = "Copy to Clipboard with Options…",
            icon = { Icons.CopyWithOptions() },
            keyShortcut = null
        ) {
            compositionExporter.copyToClipboardWithOptions(composition)
        }

        menuItem(
            text = "Export $compLabel to XML File…",
            icon = { Icons.Share() },
            keyShortcut = null
        ) {
            compositionExporter.exportToXml(composition)
        }

        separator()

        menuItem(
            text = "Apply Command and Repair Effects",
            enabled = composition.ships.any { it?.fitId != null },
            icon = { Icons.ApplyCrossFitFriendlyEffects() },
            keyShortcut = ApplyCrossFitFriendlyEffectsKeyShortcut
        ) {
            coroutineScope.launch {
                with(TheorycrafterContext.eveData) {
                    composition.applyCrossShipAssistiveEffects(snackbarState)
                }
            }
        }
    }
}


/**
 * Applies the relevant effects from every ship in the composition to all other ships.
 */
context(EveData)
private suspend fun Composition.applyCrossShipAssistiveEffects(snackbarState: SnackbarHostState) {
    data class ShipAndFit(val ship: Composition.Ship, val fit: Fit)
    val fitContext = TheorycrafterContext.fits
    val shipsAndFits = ships
        .mapNotNull { ship ->
            if ((ship == null) || !ship.active) return@mapNotNull null
            val fitId = ship.fitId ?: return@mapNotNull null
            val fitHandle = fitContext.handleById(fitId) ?: return@mapNotNull null
            ShipAndFit(ship, fitContext.engineFitOf(fitHandle))
        }
    val shipsWithUniqueFits = shipsAndFits.distinctBy { it.fit }

    val neededCommandEffects = mutableSetOf<Pair<ShipAndFit, ShipAndFit>>()
    val neededRepairEffects = mutableListOf<Pair<ShipAndFit, ShipAndFit>>()

    for (source in shipsAndFits) {
        val hasCommandBursts = source.fit.modules.all.any { it.type.isCommandBurst() }
        val isLogi = source.fit.ship.type.isLogistics

        for (target in shipsWithUniqueFits) {
            if (isLogi)
                neededRepairEffects.add(source to target)

            // Repair effects need to apply from ship to itself (if the amount is > 1)
            // Command effects do not need to apply from ship to itself
            if (target.ship === source.ship)
                continue

            if (hasCommandBursts)
                neededCommandEffects.add(source to target)
        }
    }

    var removedEffectCount = 0
    var addedCommandEffectCount = 0
    var addedRepairEffectCount = 0
    fitContext.modifyAndSave {
        val neededRepairEffectCountBySourceAndTargetFit = neededRepairEffects
            .groupingBy { (source, target) ->
                Pair(source.fit, target.fit)
            }.fold(0) { acc, (source, target) ->
                if (source == target)
                    acc + source.ship.amountOrOne - 1
                else
                    acc + source.ship.amountOrOne
            }

        // Clear existing assistive effects
        for (target in shipsAndFits) {
            for (commandEffect in target.fit.commandEffects.toList()) {
                val isCommandEffectNeeded = neededCommandEffects.any { (s, t) ->
                    (commandEffect.source == s.fit) && (commandEffect.target == t.fit)
                }
                if (!isCommandEffectNeeded) {
                    target.fit.removeCommandEffect(commandEffect)
                    removedEffectCount++
                }
            }

            val currentCountBySource = target.fit.friendlyEffects.groupingBy { it.source }.eachCount().toMutableMap()
            for (friendlyEffect in target.fit.friendlyEffects.toList()) {
                if (friendlyEffect.isByAuxiliaryFit) {
                    for (module in friendlyEffect.affectingModules) {
                        target.fit.removeModuleEffect(module)
                        removedEffectCount++
                    }
                    continue
                }
                val neededCount = neededRepairEffectCountBySourceAndTargetFit[friendlyEffect.source to friendlyEffect.target] ?: 0
                val currentCount = currentCountBySource[friendlyEffect.source] ?: 0
                if (currentCount > neededCount) {
                    target.fit.removeFriendlyEffect(friendlyEffect)
                    currentCountBySource[friendlyEffect.source] = currentCount - 1
                    removedEffectCount++
                }
            }
        }

        // Add command effects
        for ((source, target) in neededCommandEffects) {
            if (target.fit.commandEffects.none { it.source == source.fit }) {
                target.fit.addCommandEffect(source.fit)
                addedCommandEffectCount++
            }
        }

        // Add friendly effects
        for ((source, target) in neededRepairEffects) {
            val neededCount = neededRepairEffectCountBySourceAndTargetFit[source.fit to target.fit] ?: 0
            if (neededCount == 0) continue
            val currentCount = target.fit.friendlyEffects.count { it.source == source.fit }
            repeat(neededCount - currentCount) {
                target.fit.addFriendlyEffect(source.fit)
                addedRepairEffectCount++
            }
        }
    }

    if (addedCommandEffectCount + addedRepairEffectCount + removedEffectCount == 0)
        snackbarState.showSnackbar("No new effects were applied or removed")
    else {
        snackbarState.showSnackbar(
            """
                $addedCommandEffectCount command effects were applied
                $addedRepairEffectCount repair effects were applied
                $removedEffectCount existing effects were removed
            """.trimIndent()
        )
    }
}


/**
 * The legend of the keyboard shortcuts in the composition editor.
 */
private val COMPOSITION_KEY_SHORTCUTS_TOOLTIP_CONTENTS = listOf(
    KeyShortcutDescription(
        description = "Edit ship",
        DisplayedKeyShortcut("enter", verticalShiftMultiplier = LowercaseTextShiftMultiplier),
        DisplayedKeyShortcut("F2")
    ),
    KeyShortcutDescription(
        description = "Edit fit",
        DisplayedKeyShortcut(
            keys = listOf(if (hostOs == OS.MacOS) "⌥" else "alt", "enter"),
            verticalShiftMultiplier = LowercaseTextShiftMultiplier),
    ),
    KeyShortcutDescription(
        description = "Clear slot",
        DisplayedKeyShortcut("del", verticalShiftMultiplier = 0.1f), // 'del' is taller than regular text
        DisplayedKeyShortcut("⌫", verticalShiftMultiplier = 0.05f)
    ),
    KeyShortcutDescription(
        description = "Toggle active state",
        DisplayedKeyShortcut("space", verticalShiftMultiplier = LowercaseTextShiftMultiplier),
        DisplayedKeyShortcut("T")
    ),
)


/**
 * The key shortcut to copy the currently displayed composition to the clipboard.
 */
val CopyCompositionToClipboardKeyShortcut = FitWindowKeyShortcuts.CopyFitToClipboard


/**
 * The key shortcut to apply cross-fit effects on all fits in the composition.
 */
val ApplyCrossFitFriendlyEffectsKeyShortcut = KeyShortcut(Key.E, KeyboardModifierMatcher.Command)


/**
 * Returns the name of the "composition" in the given tournament.
 */
fun Tournament.compositionLabel(
    capitalized: Boolean = false,
    plural: Boolean = false,
    shortened: Boolean = false,
): String {
    var label = if (isDoctrines) "doctrine" else "composition"
    if (capitalized)
        label = label.replaceFirstChar { it.titlecase(Locale.ROOT) }
    if (plural)
        label += "s"
    if (shortened) {
        if (label.length > 8)
            label = label.take(4) + "."  // Comp.
    }

    return label
}