/**
 * A collection of small utilities that don't have a better home.
 */

package theorycrafter.utils

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import compose.utils.EasyTooltipPlacement
import compose.utils.TooltipDefaults
import eve.data.*
import eve.data.typeid.*
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.Module
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.ui.tooltip
import java.awt.Frame
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.InputStream
import java.io.OutputStream
import kotlin.math.PI


/**
 * Returns the initial state to put the given module in when fitting it.
 */
fun ModuleType.defaultInitialState(): Module.State {
    if (!isActivable)
        return Module.State.ONLINE

    return with(TheorycrafterContext.eveData) {
        when {
            isAssaultDamageControl() ||
            isCapitalEmergencyHullEnergizer() ||
            isMicroJumpDrive() ||
            isMicroJumpFieldGenerator() ||
            isSignatureRadiusSuppressor() ||
            isCynosuralFieldGenerator() ||
            isJumpPortalGenerator() ||
            isCovertJumpPortalGenerator() ||
            isInterdictionNullifier() ||
            isWarpCoreStabilizer() ||
            isCargoScanner() ||
            isCloakingDevice() ||
            isPassiveTargetingSystem() ||
            isAutoTargetingSystem() ||
            isSurveyScanner() ||
            isPanic()
                -> Module.State.ONLINE
            else
                -> Module.State.ACTIVE
        }
    }
}


/**
 * The UI displayed in the auto-suggest dropdown menu when there are no matches.
 */
@Composable
fun NoMatches(
    text: String = "No matches",
    modifier: Modifier = Modifier
) {
    Box(
        modifier = Modifier
            .padding(
                horizontal = TheorycrafterTheme.spacing.horizontalEdgeMargin,
                vertical = TheorycrafterTheme.spacing.large
            )
            .then(modifier)
        ,
        contentAlignment = Alignment.TopCenter,
    ) {
        Text(text)
    }
}


/**
 * Returns the [AnnotatedString] to display for the given [Traits].
 */
fun Traits.displayString() = buildAnnotatedString {

    fun BonusValue.displayText() = when (unit) {
        BonusUnit.METERS -> withUnitsAndSign(number = amount, units = "m", withSign = true, withUnits = true)
        BonusUnit.KILOGRAMS -> withUnitsAndSign(number = amount, units = "kg", withSign = true, withUnits = true)
        BonusUnit.MULTIPLY -> "x$amount"
        BonusUnit.PERCENT -> "${amount}%"
        BonusUnit.NUMBER -> withUnitsAndSign(number = amount, units = "", withSign = true, withUnits = false)
    }

    fun AnnotatedString.Builder.appendBonus(bonus: Bonus) {
        bonus.value.let { value ->
            val textAnnotatedString = bonus.text.simpleStyleTagsToAnnotatedString()
            if (value == null)
                append(textAnnotatedString)
            else {
                append(value.displayText())
                append(" ")
                append(textAnnotatedString)
            }
        }
    }

    val eveData = TheorycrafterContext.eveData
    val newParagraph = ParagraphStyle()
    val sectionTitleStyle = TheorycrafterTheme.textStyles.tooltipHeading.toSpanStyle()
    val sectionStyle = ParagraphStyle()

    // Per-skill section
    for ((skillId, bonuses) in perSkill) {
        val skill = eveData.skillType(skillId)
        withStyle(newParagraph) {
            if (length != 0)
                appendLine()
            withStyle(sectionTitleStyle) {
                append("${skill.name} bonuses (per skill level)")
            }
        }
        for (bonus in bonuses) {
            withStyle(sectionStyle) {
                appendBonus(bonus)
            }
        }
    }

    if (role.isNotEmpty()) {
        withStyle(newParagraph) {
            if (length != 0)
                appendLine()
            withStyle(sectionTitleStyle) {
                append("Role bonuses")
            }
        }
        for (bonus in role) {
            withStyle(sectionStyle) {
                appendBonus(bonus)
            }
        }
    }

    if (misc.isNotEmpty()) {
        withStyle(newParagraph) {
            if (length != 0)
                appendLine()
            withStyle(sectionTitleStyle) {
                append("Additional bonuses")
            }
        }
        for (bonus in misc) {
            withStyle(sectionStyle) {
                appendBonus(bonus)
            }
        }
    }
}.trimNewlinesFromEnd()


/**
 * A tooltip [Modifier] for displaying a ship's traits.
 */
fun Modifier.shipTypeTraitsTooltip(
    shipType: ShipType,
    delayMillis: Int = TooltipDefaults.ShowDelayMillis,
    tooltipPlacement: EasyTooltipPlacement = TooltipDefaults.Placement,
) = tooltip(
    annotatedText = shipType.traits?.let {
        { it.displayString() }
    },
    delayMillis = delayMillis,
    placement = tooltipPlacement,
    modifier = Modifier.widthIn(max = 400.dp)
)


/**
 * The ordering of metagroups.
 * A mapping from meta group id to its index in the ordering.
 */
val MetaGroupOrder: Map<Int, Int> by lazy {
    with(TheorycrafterContext.eveData.metaGroups) {
        listOf(tech1, tech2, storyline, faction, deadspace, officer)
            .mapIndexed { index, metaGroup ->
                metaGroup.id to index
            }
            .toMap()
    }
}


/**
 * Converts radians to degrees.
 */
fun Double.rad2deg() = this * 180.0 / PI


/**
 * Converts degrees to radians.
 */
fun Double.deg2rad() = this * PI / 180.0


/**
 * The non-breaking space character.
 */
const val NBSP = ' '


/**
 * Returns the weighted average of two values.
 */
fun weightedAverage(a: Double, b: Double, weight: Double = 0.5) = weight*a + (1-weight)*b


/**
 * Wraps an input stream in a [DataInputStream].
 */
fun InputStream.data(): DataInputStream =
    if (this is DataInputStream) this else DataInputStream(this)


/**
 * Wraps an output stream in a [DataOutputStream].
 */
fun OutputStream.data(): DataOutputStream =
    if (this is DataOutputStream) this else DataOutputStream(this)


/**
 * De-iconifies the frame, if needed, and brings it to the front.
 */
fun Frame.deiconifyAndBringToFront() {
    extendedState = Frame.NORMAL  // De-iconifies the window. On Windows, toFront() doesn't work on iconified windows
    toFront()
}
