package ru.dbotthepony.mc.otm.capability.energy

import net.minecraftforge.energy.IEnergyStorage
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import java.math.BigInteger
import kotlin.math.roundToInt

/**
 * Energy interface in Overdrive That Matters, which also implements backward compatible methods to implement [IEnergyStorage]
 *
 * Details of this energy system are as follows:
 * * [extractEnergy] and [receiveEnergy] are *unchecked*, which means, they will generally work regardless of energy storage type ([energyFlow]), be it tool, battery or generator.
 * What this means is that, despite making no sense to use tool as a battery in machine, [extractEnergy] will work regardless.
 * To avoid such cases, [extractEnergyChecked] and [receiveEnergyChecked] are provided
 * * Instead of [canReceive] and [canExtract] of [IEnergyStorage], this interface employs [energyFlow] enum property, which is cleaner and easier to use,
 * see [FlowDirection] for details
 * * This interface allows to set energy stored *directly*. With great power comes great responsibility though, since, sometimes it is technically
 * cumbersome or straight up impossible to set stored energy value, therefore, setter of [batteryLevel] is free to throw [UnsupportedOperationException].
 * To avoid try/catch block, check [canSetBatteryLevel] property first
 * * Due to limitations of setting [batteryLevel] directly, two methods are also provided: [fillBattery] and [drainBattery]. If you ever
 * need to only fill or drain this energy storage, call these two methods and do not use [batteryLevel] directly.
 * * Lastly, there is [missingPower]. It returns whatever it has in name. What it makes better than writing your own "[maxBatteryLevel] - [batteryLevel]" logic is that
 * implementations can use different and more precise logic to determine actually missing power. **Please use [missingPower] instead of writing your own logic to determine
 * whenever storage is missing energy, it will make players not experience silly bugs and make you avoid headaches**.
 */
interface IMatteryEnergyStorage : IEnergyStorage {
	/**
	 * Extracts up to [howMuch] energy from this storage
	 *
	 * In general, this method *does not* obey [energyFlow] and will allow to extract energy
	 * even if [energyFlow] and [canExtract] say otherwise, unlike [IEnergyStorage.extractEnergy], which, in general,
	 * obeys return value of [IEnergyStorage.canExtract].
	 *
	 * Therefore, if you want/need to obey [energyFlow], use [extractEnergyChecked]
	 *
	 * General contracts:
	 * * Negative values are not allowed, failing to comply with this contract will result in undefined behavior in worst case or nothing in best case
	 * * Returned value CAN NOT be bigger than requested [howMuch], implementations failing to obey this contract will cause undefined behavior in upstream code.
	 * Upstream code SHOULD NOT check for this contract.
	 *
	 * @return energy extracted
	 */
	fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal

	/**
	 * Extracts up to [howMuch] energy from this storage, while obeying [energyFlow]. See [extractEnergy].
	 *
	 * Interface implementations generally do not need to override this.
	 *
	 * @return energy extracted
	 */
	fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal {
		if (howMuch.isNegative || !energyFlow.output)
			return Decimal.ZERO

		return extractEnergy(howMuch, simulate)
	}

	/**
	 * Inserts up to [howMuch] energy into this storage
	 *
	 * In general, this method *does not* obey [energyFlow] and will allow to insert energy
	 * even if [energyFlow] and [canReceive] say otherwise, unlike [IEnergyStorage.receiveEnergy], which, in general,
	 * obeys return value of [IEnergyStorage.canReceive].
	 *
	 * Therefore, if you want/need to obey [energyFlow], use [receiveEnergyChecked]
	 *
	 * General contracts:
	 * * Negative values are not allowed, failing to comply with this contract will result in undefined behavior in worst case or nothing in best case
	 * * Returned value CAN NOT be bigger than requested [howMuch], implementations failing to obey this contract will cause undefined behavior in upstream code.
	 * Upstream code SHOULD NOT check for this contract.
	 *
	 * @return energy extracted
	 */
	fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal

	/**
	 * Inserts up to [howMuch] energy into this storage, while obeying [energyFlow]. See [receiveEnergy].
	 *
	 * Interface implementations generally do not need to override this.
	 *
	 * @return energy extracted
	 */
	fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal {
		if (howMuch.isNegative || !energyFlow.input)
			return Decimal.ZERO

		return receiveEnergy(howMuch, simulate)
	}

	/**
	 * If this is false, then [batteryLevel] will throw [UnsupportedOperationException] when trying to set it
	 */
	val canSetBatteryLevel: Boolean get() = true

	/**
	 * How much energy (estimated) is stored in this energy storage. Why estimated? Because some objects can be bottomless.
	 *
	 * Implementations are free to throw [UnsupportedOperationException] if setting battery level is not supported
	 * due to technical complications (in this case, [canSetBatteryLevel] MUST be false)
	 *
	 * @throws [UnsupportedOperationException]
	 */
	var batteryLevel: Decimal

	/**
	 * How much energy (estimated) can this energy storage hold. Why estimated? Because some objects can be bottomless.
	 */
	val maxBatteryLevel: Decimal

	/**
	 * How much energy (estimated) is missing in this energy storage. Why estimated? Because some objects can be bottomless.
	 *
	 * Use this to determine whenever you need an estimate on how much energy this storage can accept, **and do not implement [maxBatteryLevel] - [batteryLevel] logic by yourself**.
	 */
	val missingPower: Decimal
		get() = (maxBatteryLevel - batteryLevel).moreThanZero()

	/**
	 * Empties power of this energy storage
	 *
	 * @see batteryLevel
	 * @return whenever operation was successful
	 */
	fun drainBattery(): Boolean {
		try {
			batteryLevel = Decimal.ZERO
		} catch(err: UnsupportedOperationException) {
			return false
		}

		return true
	}

	/**
	 * Fully fills power in this energy storage
	 *
	 * @see batteryLevel
	 * @return whenever operation was successful
	 */
	fun fillBattery(): Boolean {
		try {
			batteryLevel = maxBatteryLevel
		} catch(err: UnsupportedOperationException) {
			return false
		}

		return true
	}

	/**
	 * Flow direction of energy possible for this energy storage, see [FlowDirection]
	 */
	val energyFlow: FlowDirection

	// -------- Forge Energy stuff

	override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int {
		// follow contract of IEnergyStorage
		if (!energyFlow.input)
			return 0

		val received = receiveEnergyChecked(Decimal(maxReceive), true).toInt()

		// Receiving only a fraction
		if (received == 0)
			return 0

		return receiveEnergyChecked(Decimal(received), simulate).toInt()
	}

	override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int {
		// follow contract of IEnergyStorage
		if (!energyFlow.output)
			return 0

		val extracted = extractEnergyChecked(Decimal(maxReceive), true).toInt()

		// Extracting only a fraction
		if (extracted == 0)
			return 0

		return extractEnergyChecked(Decimal(extracted), simulate).toInt()
	}

	override fun getEnergyStored(): Int {
		return batteryLevel.toInt()
	}

	override fun getMaxEnergyStored(): Int {
		return maxBatteryLevel.toInt()
	}

	override fun canExtract(): Boolean {
		return energyFlow.output
	}

	override fun canReceive(): Boolean {
		return energyFlow.input
	}

	/**
	 * Companion object of [IMatteryEnergyStorage] represents empty energy storage
	 */
	companion object : IMatteryEnergyStorage {
		override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
			return Decimal.ZERO
		}

		override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
			return Decimal.ZERO
		}

		override var batteryLevel: Decimal
			get() = Decimal.ZERO
			set(value) {}
		override val maxBatteryLevel: Decimal
			get() = Decimal.ZERO
		override val energyFlow: FlowDirection
			get() = FlowDirection.NONE

		override val canSetBatteryLevel: Boolean
			get() = false

		override val missingPower: Decimal
			get() = Decimal.ZERO

		override fun drainBattery(): Boolean {
			return false
		}

		override fun fillBattery(): Boolean {
			return false
		}

		override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int {
			return 0
		}

		override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int {
			return 0
		}
	}
}

/**
 * Performs *unchecked* extraction of energy
 *
 * @return whenever can (or we did) extract exactly [howMuch] energy from this storage
 */
fun IMatteryEnergyStorage.extractEnergyExact(howMuch: Decimal, simulate: Boolean): Boolean {
	if (extractEnergy(howMuch, true) == howMuch) {
		if (!simulate) {
			return extractEnergy(howMuch, false) == howMuch
		}

		return true
	}

	return false
}

/**
 * Performs *unchecked* insertion of energy
 *
 * @return whenever can (or we did) receive exactly [howMuch] energy into this storage
 */
fun IMatteryEnergyStorage.receiveEnergyExact(howMuch: Decimal, simulate: Boolean): Boolean {
	if (receiveEnergy(howMuch, true) == howMuch) {
		if (!simulate) {
			return receiveEnergy(howMuch, false) == howMuch
		}

		return true
	}

	return false
}

/**
 * Performs *unchecked* extraction of energy
 *
 * @return how much times can (or we did) extract exactly [howMuch] * [times] energy from this storage
 */
fun IMatteryEnergyStorage.extractEnergyExact(howMuch: Decimal, times: Int, simulate: Boolean): Int {
	if (times == 0)
		return 0

	require(times >= 0) { "times $times >= 0" }
	require(howMuch >= Decimal.ZERO) { "howMuch $howMuch >= 0" }

	@Suppress("name_shadowing")
	val times = (extractEnergy(howMuch * times, true) / times).toInt()

	if (simulate)
		return times

	return (extractEnergy(howMuch * times, false) / times).toInt()
}

/**
 * Performs *unchecked* extraction of energy
 *
 * @return how much times can (or we did) extract exactly [howMuch] * [times] energy from this storage
 */
fun IMatteryEnergyStorage.extractEnergyExact(howMuch: Decimal, multiplier: BigInteger, simulate: Boolean): BigInteger {
	return (extractEnergy(howMuch * multiplier, simulate) / howMuch).whole
}

/**
 * @see [IMatteryEnergyStorage.extractEnergy]
 */
fun IMatteryEnergyStorage.extractEnergy(howMuch: Long, simulate: Boolean): Decimal {
	return extractEnergy(Decimal(howMuch), simulate)
}

/**
 * @see [IMatteryEnergyStorage.receiveEnergy]
 */
fun IMatteryEnergyStorage.receiveEnergy(howMuch: Long, simulate: Boolean): Decimal {
	return receiveEnergy(Decimal(howMuch), simulate)
}

/**
 * Performs *unchecked* energy transfer from this storage to [other]
 *
 * @see IMatteryEnergyStorage.extractEnergy
 * @see IMatteryEnergyStorage.receiveEnergy
 */
fun IMatteryEnergyStorage.transfer(other: IMatteryEnergyStorage, amount: Decimal, simulate: Boolean): Decimal {
	if (!amount.isPositive)
		return Decimal.ZERO

	val extracted = extractEnergy(amount, true)
	val received = other.receiveEnergy(extracted, simulate)

	if (!simulate)
		return extractEnergy(received, false)

	return received
}

/**
 * Performs checked energy transfer from this storage to [other]
 *
 * @see IMatteryEnergyStorage.extractEnergy
 * @see IMatteryEnergyStorage.receiveEnergy
 */
fun IMatteryEnergyStorage.transferChecked(other: IMatteryEnergyStorage, amount: Decimal, simulate: Boolean): Decimal {
	if (!amount.isPositive)
		return Decimal.ZERO

	val extracted = extractEnergy(amount, true)
	val received = other.receiveEnergyChecked(extracted, simulate)

	if (!simulate)
		return extractEnergy(received, false)

	return received
}

fun IMatteryEnergyStorage.getBarWidth(): Int {
	if (!maxBatteryLevel.isPositive)
		return 0

	return ((batteryLevel / maxBatteryLevel).toFloat().coerceIn(0f, 1f) * 13f).roundToInt()
}

fun IMatteryEnergyStorage.getBarColor(): Int {
	if (!maxBatteryLevel.isPositive)
		return 0

	return RGBAColor.LOW_POWER.linearInterpolation((batteryLevel / maxBatteryLevel).toFloat(), RGBAColor.FULL_POWER).toRGB()
}
