package ru.dbotthepony.mc.otm.block.entity.matter

import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.ForgeConfigSpec
import ru.dbotthepony.mc.otm.config.ConciseBalanceValues
import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock
import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage
import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl
import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.graph.matter.MatterGraph
import ru.dbotthepony.mc.otm.menu.matter.MatterBottlerMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.core.util.WriteOnce
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue
import ru.dbotthepony.mc.otm.core.math.defineDecimal
import ru.dbotthepony.mc.otm.graph.matter.MatterNode
import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode

class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
	MatteryPoweredBlockEntity(MBlockEntities.MATTER_BOTTLER, p_155229_, p_155230_) {

	val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this, ENERGY_VALUES))

	var isBottling: Boolean = true
		set(value) {
			field = value
			this.setChangedLight()
		}

	private fun updateBlockState() {
		val level = level as? ServerLevel ?: return

		var state = blockState
		val initial = if (isBottling) 0 else 3

		for (i in initial .. initial + 2) {
			val desired = !container.getItem(i).isEmpty && container.getItem(i).getCapability(MatteryCapability.MATTER).isPresent

			if (state.getValue(MatterBottlerBlock.SLOT_PROPERTIES[i - initial]) != desired) {
				state = state.setValue(MatterBottlerBlock.SLOT_PROPERTIES[i - initial], desired)
			}
		}

		if (state !== blockState) {
			level.setBlock(blockPos, state, Block.UPDATE_CLIENTS)
		}
	}

	// TODO: оно должно что то делать
	// true - continue even if empty when bottling / if full while unbottling
	// false - spit into output slot
	//private var work_behavior = true

	val container: MatteryContainer = object : MatteryContainer(this::setChangedLight, 6) {
		override fun getMaxStackSize(slot: Int): Int {
			return 1
		}

		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
			super.setChanged(slot, new, old)
			updateBlockState()
		}
	}.also(::addDroppableContainer)

	val itemHandler = container.handler(object : HandlerFilter {
		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
			if (isBottling) {
				return slot < 3 && stack.getCapability(MatteryCapability.MATTER).isPresent
			}

			return slot >= 3 && stack.getCapability(MatteryCapability.MATTER).isPresent
		}

		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
			if (isBottling) {
				return slot >= 3
			}

			return slot < 3
		}
	})

	val matter: ProfiledMatterStorage<MatterStorageImpl> = ProfiledMatterStorage(object : MatterStorageImpl(this::setChangedLight, FlowDirection.BI_DIRECTIONAL, ::CAPACITY) {
		override val matterFlow: FlowDirection get() {
			return if (this@MatterBottlerBlockEntity.isBottling) FlowDirection.INPUT else FlowDirection.OUTPUT
		}
	})

	val matterNode = SimpleMatterNode(matter = matter)

	init {
		exposeGlobally(MatteryCapability.MATTER, matter)
		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
		exposeItemsGlobally(itemHandler)

		savetables.bool(::isBottling)
		savetable(::energy, ENERGY_KEY)
		savetable(::matter, MATTER_STORAGE_KEY)
		savetable(::container, INVENTORY_KEY)

		exposeEnergyGlobally(energy)
	}

	private var initialCapacity: Decimal? = null
	private var lastWorkStack: ItemStack? = null

	override fun setLevel(level: Level) {
		super.setLevel(level)
		matterNode.discover(this)
	}

	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
		return MatterBottlerMenu(containerID, inventory, this)
	}

	fun getWorkProgress(): Float {
		val lastWorkStack = lastWorkStack ?: return 0f
		val initialCapacity = initialCapacity ?: return 0f
		val cap = lastWorkStack.getCapability(MatteryCapability.MATTER).orNull() ?: return 0f

		if (this.isBottling) {
			if (cap.maxStoredMatter - initialCapacity <= Decimal.ZERO) {
				return 0f
			}

			return ((cap.storedMatter - initialCapacity) / (cap.maxStoredMatter - initialCapacity)).toFloat()
		}

		if (initialCapacity <= Decimal.ZERO) {
			return 0f
		}

		return (Decimal.ONE - cap.storedMatter / initialCapacity).toFloat()
	}

	override fun setRemoved() {
		super.setRemoved()
		matterNode.isValid = false
	}

	override fun tick() {
		super.tick()

		if (redstoneControl.isBlockedByRedstone) {
			if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE) {
				level?.setBlock(
					blockPos,
					blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE),
					Block.UPDATE_CLIENTS
				)
			}

			return
		}

		var work_stack: ItemStack? = null
		var capability: IMatterStorage? = null
		val align = if (isBottling) 0 else 3
		var work_slot = -1
		val unexpectedDirection = if (isBottling) FlowDirection.OUTPUT else FlowDirection.INPUT

		for (i in align until align + 3) {
			val itemStack = container.getItem(i)

			if (!itemStack.isEmpty) {
				val cap = itemStack.getCapability(MatteryCapability.MATTER).orNull() ?: continue

				if (cap.matterFlow != unexpectedDirection) {
					if (this.isBottling && cap.missingMatter > Decimal.ZERO || !this.isBottling && cap.storedMatter > Decimal.ZERO) {
						work_stack = itemStack
						capability = cap
						work_slot = i
						break
					}
				}
			}
		}

		if (work_stack == null) {
			lastWorkStack = null
			initialCapacity = null
		} else if (work_stack != lastWorkStack) {
			lastWorkStack = work_stack
			initialCapacity = capability!!.storedMatter
		}

		val graph = matterNode.graph as MatterGraph?

		if (capability != null) {
			if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.WORKING) {
				level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS)
			}

			if (isBottling) {
				if (matter.storedMatter < MATTER_EXCHANGE_RATE && graph != null) {
					val extracted = graph.extractMatter(
						matter.missingMatter.coerceAtMost(MATTER_EXCHANGE_RATE * EXTRACTION_TICKS).coerceAtMost(capability.missingMatter - matter.storedMatter), true
					)

					if (extracted > Decimal.ZERO) {
						val received = matter.receiveMatter(extracted, false)
						graph.extractMatter(received, false)
					}
				}

				if (matter.storedMatter > Decimal.ZERO) {
					val energyExtracted = energy.extractEnergy(ENERGY_CONSUMPTION, true)

					if (!energyExtracted.isZero) {
						val matter = capability.receiveMatter(MATTER_EXCHANGE_RATE.coerceAtMost(matter.storedMatter) * energyExtracted / ENERGY_CONSUMPTION, true)

						if (!matter.isZero) {
							energy.extractEnergy(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE, false)

							capability.receiveMatter(matter, false)
							this.matter.extractMatter(matter, false)

							if (capability.missingMatter.isZero) {
								for (i in 3..5) {
									if (container.getItem(i).isEmpty) {
										container.setItem(work_slot, ItemStack.EMPTY)
										container.setItem(i, work_stack!!)
										break
									}
								}
							}
						}
					}
				}
			} else {
				val energyExtracted = energy.extractEnergy(ENERGY_CONSUMPTION, true)

				if (!energyExtracted.isZero) {
					val matter = capability.extractMatter(MATTER_EXCHANGE_RATE.coerceAtMost(matter.missingMatter) * energyExtracted / ENERGY_CONSUMPTION, true)

					if (!matter.isZero) {
						this.energy.extractEnergy(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE,false)

						capability.extractMatter(matter, false)
						this.matter.receiveMatter(matter, false)

						if (capability.storedMatter.isZero) {
							for (i in 2 downTo 0) {
								if (container.getItem(i).isEmpty) {
									container.setItem(work_slot, ItemStack.EMPTY)
									container.setItem(i, work_stack!!)
									break
								}
							}
						}
					}
				}
			}
		} else {
			level!!.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS)
		}

		if (!isBottling && !matter.storedMatter.isZero && graph != null) {
			val diff = matter.extractMatter(matter.storedMatter, true)
			val diff2 = graph.receiveMatter(diff, true)
			matter.extractMatter(diff2, false)
			graph.receiveMatter(diff2, false)
		}
	}

	companion object {
		val MATTER_EXCHANGE_RATE get() = _MATTER_EXCHANGE_RATE.get()
		val ENERGY_CONSUMPTION get() = _ENERGY_CONSUMPTION.get()
		val EXTRACTION_TICKS get() = _EXTRACTION_TICKS.get()
		val CAPACITY get() = _CAPACITY.get()

		private var _MATTER_EXCHANGE_RATE: DecimalConfigValue by WriteOnce()
		private var _ENERGY_CONSUMPTION: DecimalConfigValue by WriteOnce()
		private var _EXTRACTION_TICKS: DecimalConfigValue by WriteOnce()
		private var _CAPACITY: DecimalConfigValue by WriteOnce()

		var ENERGY_VALUES: ConciseBalanceValues by WriteOnce()
			private set

		fun registerConfig(builder: ForgeConfigSpec.Builder) {
			builder.push(MNames.MATTER_BOTTLER)

			ENERGY_VALUES = BlockEnergyStorageImpl.makeConfigEntry(builder)

			_MATTER_EXCHANGE_RATE = builder.defineDecimal("matterExchangeRate", Decimal("0.04"), Decimal("0.0001"))
			_ENERGY_CONSUMPTION = builder.defineDecimal("energyConsumption", Decimal(20), Decimal.ONE)
			_EXTRACTION_TICKS = builder.defineDecimal("extractionTicks", Decimal(200), Decimal.ONE)
			_CAPACITY = builder.defineDecimal("matterCapacity", Decimal(4_000), Decimal.ONE_TENTH)

			builder.pop()
		}
	}
}
