package ru.dbotthepony.mc.otm.capability.matter

import net.minecraftforge.common.capabilities.ICapabilityProvider
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import kotlin.math.roundToInt

/**
 * Matter interface in Overdrive That Matters
 *
 * Contracts of this interface follow the ones of [IMatteryEnergyStorage]
 */
interface IMatterStorage {
	/**
	 * If this is false, then [storedMatter] will throw [UnsupportedOperationException] when trying to set it
	 */
	val canSetMatterLevel: Boolean get() = true

	/**
	 * How much matter (estimated) is stored in this matter 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, [canSetMatterLevel] MUST be false)
	 *
	 * If you need to fully fill matter value, DO call [fillMatter], or, in case if you want to fully drain, call [drainMatter]
	 *
	 * @throws [UnsupportedOperationException]
	 */
	var storedMatter: Decimal

	/**
	 * How much matter (estimated) can this matter storage hold. Why estimated? Because some objects can be bottomless.
	 *
	 * **DO NOT use this to determine "how much stuff is missing", use [missingMatter] instead!**
	 */
	val maxStoredMatter: Decimal

	/**
	 * Empties matter stored of this matter storage, if possible
	 *
	 * @throws [UnsupportedOperationException]
	 * @see storedMatter
	 */
	fun drainMatter() {
		storedMatter = Decimal.ZERO
	}

	/**
	 * Fully fills matter stored, if possible
	 *
	 * @throws [UnsupportedOperationException]
	 * @see storedMatter
	 */
	fun fillMatter() {
		storedMatter = maxStoredMatter
	}

	/**
	 * Fill matter into this object, ignoring [matterFlow]
	 *
	 * @return matter accepted
	 */
	fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal

	/**
	 * Extract matter from this matter storage, ignoring [matterFlow]
	 *
	 * @return matter extracted
	 */
	fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal

	/**
	 * Fill matter into this object, accounting for [matterFlow]
	 *
	 * @return matter accepted
	 */
	fun receiveMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal {
		if (matterFlow.input) {
			return receiveMatter(howMuch, simulate)
		}

		return Decimal.ZERO
	}

	/**
	 * Extract matter from this matter storage, accounting for [matterFlow]
	 *
	 * @return matter extracted
	 */
	fun extractMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal {
		if (matterFlow.output) {
			return extractMatter(howMuch, simulate)
		}

		return Decimal.ZERO
	}

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

	/**
	 * Which direction does matter flows
	 */
	val matterFlow: FlowDirection
}

inline val IMatterStorage.canExtractMatter: Boolean
	get() = matterFlow.output
inline val IMatterStorage.canReceiveMatter: Boolean
	get() = matterFlow.input

fun IMatterStorage.getBarWidth(): Int {
	return ((storedMatter / maxStoredMatter).toFloat().coerceAtLeast(0f).coerceAtMost(1f) * 13f).roundToInt()
}

fun IMatterStorage.getBarColor(): Int {
	return RGBAColor.LOW_MATTER.linearInterpolation((storedMatter / maxStoredMatter).toFloat(), RGBAColor.FULL_MATTER).toRGB()
}

val ICapabilityProvider.matter: IMatterStorage? get() = getCapability(MatteryCapability.MATTER).orNull()
