package ru.dbotthepony.mc.otm.container

import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.UpgradeType
import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
import ru.dbotthepony.mc.otm.config.VerboseEnergyBalanceValues
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.collect.reduce
import ru.dbotthepony.mc.otm.core.math.Decimal
import java.util.function.BooleanSupplier
import kotlin.math.pow

class UpgradeContainer(
	slotCount: Int,
	val allowedUpgrades: Set<UpgradeType> = UpgradeType.ALL,
	val shouldLockUpgradeSlots: BooleanSupplier = BooleanSupplier { false },
	listener: Runnable = Runnable {}
) : MatteryContainer(listener, slotCount), IMatteryUpgrade {
	override val upgradeTypes: Set<UpgradeType>
		get() = setOf()

	private fun positiveDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal {
		if (isEmpty)
			return Decimal.ZERO

		return iterator()
			.map { (it.getCapability(MatteryCapability.UPGRADE)?.let(fn) ?: Decimal.ZERO).moreThanZero() * it.count }
			.reduce(Decimal.ZERO, reducer)
	}

	private fun anyDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal {
		if (isEmpty)
			return Decimal.ZERO

		return iterator()
			.map { (it.getCapability(MatteryCapability.UPGRADE)?.let(fn) ?: Decimal.ZERO) * it.count }
			.reduce(Decimal.ZERO, reducer)
	}

	override val speedBonus: Double
		get() = if (isEmpty) 0.0 else iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.speedBonus ?: 0.0) * it.count }.reduce(0.0) { a, b -> a + b }
	override val processingItems: Int
		get() = if (isEmpty) 0 else iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.processingItems ?: 0).coerceAtLeast(0) * it.count }.reduce(0) { a, b -> a + b }
	override val energyStorageFlat: Decimal
		get() = positiveDecimals(IMatteryUpgrade::energyStorageFlat, Decimal::plus)
	override val energyStorage: Decimal
		get() = positiveDecimals(IMatteryUpgrade::energyStorage, Decimal::plus)
	override val matterStorageFlat: Decimal
		get() = positiveDecimals(IMatteryUpgrade::matterStorageFlat, Decimal::plus)
	override val matterStorage: Decimal
		get() = positiveDecimals(IMatteryUpgrade::matterStorage, Decimal::plus)
	override val energyConsumed: Decimal
		get() = anyDecimals(IMatteryUpgrade::energyConsumed, Decimal::plus)
	override val failureMultiplier: Double
		get() = if (isEmpty) 1.0 else iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.failureMultiplier ?: 1.0).coerceAtLeast(0.0).pow(it.count.toDouble()) }.reduce(1.0) { a, b -> a * b }
	override val energyThroughputFlat: Decimal
		get() = positiveDecimals(IMatteryUpgrade::energyThroughputFlat, Decimal::plus)
	override val energyThroughput: Decimal
		get() = positiveDecimals(IMatteryUpgrade::energyThroughput, Decimal::plus)

	fun transform(values: EnergyBalanceValues): EnergyBalanceValues {
		return object : EnergyBalanceValues {
			override val energyCapacity: Decimal
				get() = if (isEmpty) values.energyCapacity else values.energyCapacity * (energyStorage + Decimal.ONE) + energyStorageFlat
			override val energyThroughput: Decimal
				get() = if (isEmpty) values.energyThroughput else values.energyThroughput * (this@UpgradeContainer.energyThroughput + Decimal.ONE) + energyThroughputFlat
		}
	}

	fun transform(values: VerboseEnergyBalanceValues): VerboseEnergyBalanceValues {
		return object : VerboseEnergyBalanceValues {
			override val energyCapacity: Decimal
				get() = if (isEmpty) values.energyCapacity else values.energyCapacity * (energyStorage + Decimal.ONE) + energyStorageFlat
			override val maxEnergyReceive: Decimal
				get() = if (isEmpty) values.maxEnergyReceive else values.maxEnergyReceive * (energyThroughput + Decimal.ONE) + energyThroughputFlat
			override val maxEnergyExtract: Decimal
				get() =if (isEmpty) values.maxEnergyExtract else  values.maxEnergyExtract * (energyThroughput + Decimal.ONE) + energyThroughputFlat
		}
	}

	fun matterCapacity(value: () -> Decimal): () -> Decimal {
		return { if (isEmpty) value.invoke() else value.invoke() * (matterStorage + Decimal.ONE) + matterStorageFlat }
	}
}
