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

import net.minecraft.nbt.CompoundTag
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.common.util.LazyOptional
import ru.dbotthepony.mc.otm.config.ConciseBalanceValues
import ru.dbotthepony.mc.otm.config.VerboseBalanceValues
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.set

open class MatterStorageImpl @JvmOverloads constructor(
	protected val listener: Runnable?,
	override val matterFlow: FlowDirection,
	protected val maxStoredMatterSupplier: () -> Decimal,
	protected val maxReceiveSupplier: () -> Decimal? = { null },
	protected val maxExtractSupplier: () -> Decimal? = maxReceiveSupplier
) : IMatterStorage, INBTSerializable<CompoundTag?> {
	constructor(
		listener: Runnable?,
		direction: FlowDirection,
		maxStoredMatter: Decimal,
		maxReceive: Decimal? = null,
		maxExtract: Decimal? = null,
	) : this(listener, direction, { maxStoredMatter }, { maxReceive }, { maxExtract })

	constructor(
		listener: Runnable?,
		direction: FlowDirection,
		values: ConciseBalanceValues
	) : this(listener, direction, values::capacity, values::throughput, values::throughput)

	constructor(
		listener: Runnable?,
		direction: FlowDirection,
		values: VerboseBalanceValues
	) : this(listener, direction, values::capacity, values::receive, values::extract)

	val maxReceive get() = maxReceiveSupplier.invoke()
	val maxExtract get() = maxExtractSupplier.invoke()

	override val maxStoredMatter: Decimal
		get() = maxStoredMatterSupplier.invoke()

	override var storedMatter = Decimal.ZERO

	open fun canReceiveAll(value: Decimal): Boolean {
		return maxStoredMatter >= value && storedMatter + value <= maxStoredMatter
	}

	override fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal {
		if (!howMuch.isPositive)
			return Decimal.ZERO

		val new: Decimal

		if (maxReceive == null) {
			new = (storedMatter + howMuch).coerceAtMost(maxStoredMatter)
		} else {
			new = (storedMatter + howMuch.coerceAtMost(maxReceive!!)).coerceAtMost(maxStoredMatter)
		}

		val diff = new - storedMatter

		if (!simulate && new != storedMatter) {
			storedMatter = new
			listener?.run()
		}

		return diff
	}

	override fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal {
		if (!howMuch.isPositive)
			return Decimal.ZERO

		val new: Decimal

		if (maxExtract == null) {
			new = (storedMatter - howMuch).moreThanZero()
		} else {
			new = (storedMatter - howMuch.coerceAtMost(maxExtract!!)).moreThanZero()
		}

		val diff = storedMatter - new

		if (!simulate && new != storedMatter) {
			storedMatter = new
			listener?.run()
		}

		return diff
	}

	override fun serializeNBT(): CompoundTag {
		return CompoundTag().also {
			it["stored"] = storedMatter.serializeNBT()
			//it["max_storage"] = maxStoredMatter.serializeNBT()
			//if (maxReceive != null) it["max_receive"] = maxReceive.serializeNBT()
			//if (maxExtract != null) it["max_extract"] = maxExtract.serializeNBT()
		}
	}

	override fun deserializeNBT(tag: CompoundTag?) {
		if (tag == null) return
		storedMatter = Decimal.deserializeNBT(tag["stored"])
		//maxStoredMatter = ImpreciseFraction.deserializeNBT(tag["max_storage"])
		//maxReceive = if (tag.contains("max_receive")) ImpreciseFraction.deserializeNBT(tag["max_receive"]) else null
		//maxExtract = if (tag.contains("max_extract")) ImpreciseFraction.deserializeNBT(tag["max_extract"]) else null
	}
}
