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

import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.ExperienceOrb
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.item.enchantment.Enchantment
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage.Companion.XP_TO_LIQUID_RATIO
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.tech.EssenceStorageBlock
import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid
import ru.dbotthepony.mc.otm.core.lookupOrThrow
import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.otmRandom
import ru.dbotthepony.mc.otm.core.util.countingLazy
import ru.dbotthepony.mc.otm.item.consumables.EssenceCapsuleItem
import ru.dbotthepony.mc.otm.item.EssenceServoItem
import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
import ru.dbotthepony.mc.otm.registry.game.MDataComponentTypes
import ru.dbotthepony.mc.otm.registry.game.MFluids

class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ESSENCE_STORAGE, blockPos, blockState), IFluidHandler {
	var experienceStored = 0L
		set(value) {
			require(value >= 0L) { "Negative experience: $value" }
			field = value
			markDirtyFast()
		}

	val capsuleContainer = MatteryContainer(::markDirtyFast, 1)
	val servoContainer = MatteryContainer(::markDirtyFast, 1)
	val mendingContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer)

	private var mending: Holder<Enchantment>? = null

	init {
		savetables.long(::experienceStored)
		savetables.stateful(::capsuleContainer)
		savetables.stateful(::servoContainer)
		savetables.stateful(::mendingContainer)
	}

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

	val itemConfig = ConfigurableItemHandler(
		inputOutput = CombinedItemHandler(
			capsuleContainer.handler(HandlerFilter.OnlyIn.and(object : HandlerFilter {
				override fun canInsert(slot: Int, stack: ItemStack): Boolean {
					return stack.item is EssenceCapsuleItem
				}
			})),
			mendingContainer.handler(object : HandlerFilter {
				override fun canInsert(slot: Int, stack: ItemStack): Boolean {
					return stack.isDamaged && stack.getEnchantmentLevel(mending!!) > 0
				}

				override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
					return !stack.isDamaged || experienceStored <= 0
				}
			})
		)
	)

	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
		super.loadAdditional(nbt, registry)

		mending = registry.lookupOrThrow(Enchantments.MENDING)
	}

	val fluidConfig = ConfigurableFluidHandler(this)

	override fun getTanks(): Int {
		return 1
	}

	override fun getFluidInTank(tank: Int): FluidStack {
		if (tank != 0)
			return FluidStack.EMPTY

		if ((experienceStored * XP_TO_LIQUID_RATIO) >= 2_000_000_000L)
			return FluidStack(MFluids.LIQUID_XP, 2_000_000_000)

		return FluidStack(MFluids.LIQUID_XP, (experienceStored * XP_TO_LIQUID_RATIO).toInt())
	}

	override fun getTankCapacity(tank: Int): Int {
		if (tank != 0) return 0
		return Int.MAX_VALUE
	}

	override fun isFluidValid(tank: Int, stack: FluidStack): Boolean {
		return tank == 0 && stack.fluid.fluidType == MFluids.LIQUID_XP_TYPE
	}

	override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int {
		if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE || resource.amount <= 0)
			return 0

		val actualFill = resource.amount / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO

		if (action.execute())
			experienceStored += actualFill / XP_TO_LIQUID_RATIO

		return actualFill
	}

	override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack {
		if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE)
			return FluidStack.EMPTY

		return drain(resource.amount, action)
	}

	override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack {
		val stored = experienceStored * XP_TO_LIQUID_RATIO
		val actualStored = if (stored >= Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt()
		val actualDrain = maxDrain.coerceAtMost(actualStored).let { it / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO }

		if (actualDrain <= 0)
			return FluidStack.EMPTY

		if (action.execute())
			experienceStored -= actualDrain / XP_TO_LIQUID_RATIO

		return FluidStack(MFluids.LIQUID_XP, actualDrain)
	}

	private val hasFillState by countingLazy(blockStateChangesCounter) {
		this.blockState.hasProperty(EssenceStorageBlock.FILLED)
	}

	private val isFilledState by countingLazy(blockStateChangesCounter) {
		hasFillState && this.blockState.getValue(EssenceStorageBlock.FILLED)
	}

	override fun tick() {
		super.tick()

		if (!redstoneControl.isBlockedByRedstone) {
			val capsule = capsuleContainer[0]

			if (!capsule.isEmpty && capsule.has(MDataComponentTypes.EXPERIENCE)) {
				experienceStored += capsule.get(MDataComponentTypes.EXPERIENCE)!! * capsule.count
				capsuleContainer.clearContent()
			}

			val repairStack = mendingContainer[0]

			if (!repairStack.isEmpty && repairStack.isDamaged) {
				if (experienceStored > 0) {
					val dmgPerExp = repairStack.xpRepairRatio
					var repairPoints = dmgPerExp.toInt()

					val diff = dmgPerExp - repairPoints.toFloat()
					if (diff > 0f) {
						repairPoints += if ((level?.otmRandom?.nextFloat() ?: 1f) <= diff) 1 else 0
					}

					experienceStored -= 1
					repairStack.damageValue -= repairPoints
				}
			}

			val servo = servoContainer[0]

			if (!servo.isEmpty && servo.item is EssenceServoItem && level is ServerLevel) {
				val entities = level!!.getEntitiesInEllipsoid(
					blockPos.center,
					Vector(
						MachinesConfig.EssenceStorage.RADIUS_HORIZONTAL,
						MachinesConfig.EssenceStorage.RADIUS_VERTICAL,
						MachinesConfig.EssenceStorage.RADIUS_HORIZONTAL
					)
				) { it is ExperienceOrb }

				for ((ent, _) in entities) {
					ent as ExperienceOrb

					for (i in 1 .. ent.count) {
						experienceStored += ent.value
						ent.count--

						level!!.playSound(null, ent.x, ent.y, ent.z,
							SoundEvents.EXPERIENCE_ORB_PICKUP, SoundSource.BLOCKS,
							0.1F, 0.5F + level!!.otmRandom.nextFloat() * 0.25F
						)
					}

					if (ent.count <= 0) {
						ent.discard()
					}
				}
			}
		}

		if (hasFillState) {
			val filled = experienceStored > 0

			if (isFilledState != filled) {
				level?.setBlock(blockPos, blockState.setValue(EssenceStorageBlock.FILLED, filled), Block.UPDATE_CLIENTS)
			}
		}
	}
}
