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 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.MatteryCapability
import ru.dbotthepony.mc.otm.capability.item.ProxiedItemHandler
import ru.dbotthepony.mc.otm.capability.UpgradeType
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl
import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.UpgradeContainer
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.core.math.Decimal
import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode

class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) :
	MatteryPoweredBlockEntity(MBlockEntities.MATTER_BOTTLER, blockPos, blockState) {

	val upgrades = UpgradeContainer(::markDirtyFast, 3, UpgradeType.BASIC_MATTER)
	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, upgrades.transform(MachinesConfig.MatterBottler.VALUES)))
	val energyConfig = ConfigurableEnergy(energy)

	private inner class Container : MatteryContainer(3) {
		init {
			addDroppableContainer(this)
		}

		override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
			return 1
		}

		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
			markDirtyFast()
			updateBlockState()
		}
	}

	val unbottling: MatteryContainer = Container()
	val bottling: MatteryContainer = Container()

	var isBottling: Boolean = true
		set(value) {
			field = value
			initialCapacity = null
			workProgress = 0f
			this.markDirtyFast()

			if (value) {
				inputHandler.parent = bottlingHandler
				outputHandler.parent = unbottlingHandler
			} else {
				inputHandler.parent = unbottlingHandler
				outputHandler.parent = bottlingHandler
			}

			updateBlockState()
		}

	var spitItemsWhenCantWork = false
		set(value) {
			field = value
			this.markDirtyFast()
		}

	val bottlingHandler = bottling.handler(object : HandlerFilter {
		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
			return isBottling && stack.getCapability(MatteryCapability.MATTER).map { it.matterFlow.input && it.missingMatter.isPositive }.orElse(false)
		}
	})

	val unbottlingHandler = unbottling.handler(object : HandlerFilter {
		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
			return !isBottling && stack.getCapability(MatteryCapability.MATTER).map { it.matterFlow.output && it.storedMatter.isPositive }.orElse(false)
		}
	})

	private val inputHandler = ProxiedItemHandler(bottlingHandler)
	private val outputHandler = ProxiedItemHandler(unbottlingHandler)

	val itemConfig = ConfigurableItemHandler(
		input = inputHandler,
		output = outputHandler,
		battery = batteryItemHandler
	)

	val matter: ProfiledMatterStorage<MatterStorageImpl> = ProfiledMatterStorage(object : MatterStorageImpl(this::markDirtyFast, FlowDirection.BI_DIRECTIONAL, upgrades.matterCapacity(MachinesConfig.MatterBottler.VALUES::matterCapacity)) {
		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)

		savetables.bool(::isBottling)
		savetables.bool(::spitItemsWhenCantWork)
		savetables.stateful(::energy, ENERGY_KEY)
		savetables.stateful(::matter, MATTER_STORAGE_KEY)
		savetables.stateful(::bottling)
		savetables.stateful(::unbottling)
		savetables.stateful(::upgrades)
	}

	var workProgress: Float = 0f
		private set

	private var initialCapacity: Decimal? = 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)
	}

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

	private fun updateBlockState(container: MatteryContainer) {
		val level = level as? ServerLevel ?: return
		var state = blockState

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

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

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

	private fun updateBlockState() {
		if (isBottling)
			updateBlockState(bottling)
		else
			updateBlockState(unbottling)
	}

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

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

	override fun tick() {
		super.tick()

		if (redstoneControl.isBlockedByRedstone) {
			blockstateToIdle()
			return
		}

		if (isBottling) {
			var any = false
			var idle = false

			for (slot in bottling.slotIterator()) {
				val item = slot.item
				item.getCapability(MatteryCapability.MATTER).ifPresentK {
					if (!it.missingMatter.isPositive) {
						unbottling.consumeItem(item, false)
						slot.setChanged()
					} else {
						any = true
						initialCapacity = initialCapacity ?: it.storedMatter
						val rate = MachinesConfig.MatterBottler.RATE * (1.0 + upgrades.speedBonus)

						if (matter.storedMatter < rate) {
							matter.receiveMatter(matterNode.graph.extractMatter(matter.missingMatter
									.coerceAtMost(rate * 200)
									.coerceAtMost(it.missingMatter - matter.storedMatter), false), false)
						}

						if (matter.storedMatter.isPositive) {
							matter.extractMatter(it.receiveMatter(rate.coerceAtMost(matter.storedMatter), false), false)

							if (!it.missingMatter.isPositive) {
								initialCapacity = null
								workProgress = 0f
							} else {
								workProgress = ((it.storedMatter - initialCapacity!!) / it.maxStoredMatter).toFloat()
							}
						} else {
							idle = true

							if (spitItemsWhenCantWork) {
								unbottling.consumeItem(item, false)
								slot.setChanged()
							}
						}
					}
				}

				if (any) {
					break
				}
			}

			if (any && !idle) {
				blockstateToWorking()
			} else {
				matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false)
				blockstateToIdle()
			}
		} else {
			matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false)

			if (!matter.missingMatter.isPositive) {
				if (spitItemsWhenCantWork) {
					for (slot in unbottling.slotIterator()) {
						bottling.consumeItem(slot.item, false)
						slot.setChanged()
					}
				}

				blockstateToIdle()
				return
			}

			var any = false

			for (slot in unbottling.slotIterator()) {
				val item = slot.item

				item.getCapability(MatteryCapability.MATTER).ifPresentK {
					if (!it.storedMatter.isPositive) {
						bottling.consumeItem(item, false)
						slot.setChanged()
					} else {
						any = true
						initialCapacity = initialCapacity ?: it.storedMatter
						matter.receiveMatter(it.extractMatter(MachinesConfig.MatterBottler.RATE, false), false)

						if (!it.storedMatter.isPositive) {
							initialCapacity = null
							workProgress = 0f
						} else {
							workProgress = 1f - (it.storedMatter / initialCapacity!!).toFloat()
						}
					}
				}

				if (any) {
					break
				}
			}

			if (any) {
				blockstateToWorking()
			} else {
				blockstateToIdle()
			}
		}
	}
}
