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

import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.capabilities.Capabilities
import ru.dbotthepony.kommons.collect.toList
import ru.dbotthepony.kommons.util.DelegateSetter
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kommons.util.value
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.MatterHatchBlockEntity
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.CombinedProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.matter.CombinedProfiledMatterStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.config.ServerConfig
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblock
import ru.dbotthepony.mc.otm.core.getBlockStateNow
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.core.otmRandom
import ru.dbotthepony.mc.otm.core.util.InvalidableLazy
import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MBlockTags
import ru.dbotthepony.mc.otm.registry.game.MBlocks
import java.io.Closeable

class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(
	MBlockEntities.BLACK_HOLE_GENERATOR, blockPos, blockState) {
	var multiblock: ShapedMultiblock? = null
		private set

	private var multiblockSyncher: Closeable? = null

	var lastRange by syncher.int(-1, setter = DelegateSetter { field, value ->
		if (value == -1) {
			multiblockSyncher?.close()
			multiblockSyncher = null
			multiblock?.close()
			energyTarget.invalidate()
			matterTarget.invalidate()
			multiblock = null
			field.accept(value)
		} else if (value != field.value) {
			multiblockSyncher?.close()
			multiblock?.close()
			multiblock = CONFIGURATIONS[value - 5].value.create(blockPos)

			multiblock!!.onGenerationChanges(Runnable {
				energyTarget.invalidate()
				matterTarget.invalidate()
			})

			multiblockSyncher = syncher.add0(multiblock!!)
			energyTarget.invalidate()
			matterTarget.invalidate()
			field.accept(value)
		}
	})
		private set

	var drawBuildingGuide by syncher.boolean()

	private val energyTarget = InvalidableLazy.Impl {
		multiblock?.blockEntities(EnergyHatchBlockEntity.OUTPUT_TAG)?.iterator()?.map { it.energy }?.toList() ?: listOf()
	}

	private val matterTarget = InvalidableLazy.Impl {
		multiblock?.blockEntities(MatterHatchBlockEntity.INPUT_TAG)?.iterator()?.map { it.matter }?.toList() ?: listOf()
	}

	val energy = CombinedProfiledEnergyStorage(FlowDirection.NONE, energyTarget::value, { level?.otmRandom })
	val matter = CombinedProfiledMatterStorage(FlowDirection.NONE, matterTarget::value, { level?.otmRandom })

	enum class Mode(val label: Component, val tooltip: Component) {
		TARGET_MASS(TranslatableComponent("otm.gui.black_hole_generator.sustain.mode"), TranslatableComponent("otm.gui.black_hole_generator.sustain.desc")),
		MATTER_ONLY(TranslatableComponent("otm.gui.black_hole_generator.matter_only.mode"), TranslatableComponent("otm.gui.black_hole_generator.matter_only.desc")),
		ANTIMATTER_ONLY(TranslatableComponent("otm.gui.black_hole_generator.antimatter_only.mode"), TranslatableComponent("otm.gui.black_hole_generator.antimatter_only.desc"));
	}

	var mode = Mode.TARGET_MASS
		set(value) {
			field = value
			markDirtyFast()
		}

	var injectionRate = Decimal.ONE_TENTH
		set(value) {
			field = maxOf(Decimal.ONE_TENTH, value)
			markDirtyFast()
		}

	var targetMass = ServerConfig.Blackhole.BASELINE_MASS
		set(value) {
			field = maxOf(Decimal.ONE, value)
			markDirtyFast()
		}

	init {
		exposeSideless(MatteryCapability.MATTER_BLOCK, matter)
		exposeSideless(MatteryCapability.BLOCK_ENERGY, energy)
		exposeSideless(Capabilities.EnergyStorage.BLOCK, energy)

		savetablesConfig.enum(::mode, map = Mode::valueOf)
		savetablesConfig.decimal(::injectionRate)
		savetablesConfig.decimal(::targetMass)
	}

	private fun findBlackHoleRange(): Int {
		val normal = blockRotation.normal
		val level = level!!

		if (lastRange != -1) {
			val pos = blockPos + normal * lastRange

			if (level.getBlockStateNow(pos).block == MBlocks.BLACK_HOLE) {
				return lastRange
			}
		}

		for (i in 5 until 64) {
			val pos = blockPos + normal * i

			if (level.getBlockStateNow(pos).block == MBlocks.BLACK_HOLE) {
				return i
			}
		}

		return -1
	}

	override fun setRemoved() {
		super.setRemoved()

		multiblock?.close()
		multiblock = null
	}

	override fun tick() {
		super.tick()

		energy.tick()
		matter.tick()

		if (redstoneControl.isBlockedByRedstone)
			return

		val level = level!!

		if (lastRange == -1) {
			this.lastRange = findBlackHoleRange()
		}

		val multiblock = multiblock

		if (multiblock != null) {
			val status = multiblock.update(level, blockRotation.back)

			if (status == MultiblockStatus.INVALID) {
				this.lastRange = findBlackHoleRange()
			} else if (status == MultiblockStatus.VALID) {
				val blackHole = multiblock.blockEntities(BLACK_HOLE).firstOrNull() ?: return

				val matterExtracted = matter.extractMatter(if (mode == Mode.TARGET_MASS && blackHole.mass != targetMass) minOf(injectionRate, (blackHole.mass - targetMass).absoluteValue) else injectionRate, true)
				if (!matterExtracted.isPositive) return

				val targetEnergy = when (mode) {
					Mode.TARGET_MASS -> {
						if (targetMass > blackHole.mass) {
							MachinesConfig.BlackHoleGenerator.GENERATION_RATIO_FEED * matterExtracted * blackHole.gravitationStrength
						} else if (targetMass < blackHole.mass) {
							MachinesConfig.BlackHoleGenerator.GENERATION_RATIO_DRAIN * matterExtracted * blackHole.gravitationStrength
						} else {
							MachinesConfig.BlackHoleGenerator.GENERATION_RATIO_SUSTAIN * matterExtracted * blackHole.gravitationStrength
						}
					}

					Mode.MATTER_ONLY -> MachinesConfig.BlackHoleGenerator.GENERATION_RATIO_FEED * matterExtracted
					Mode.ANTIMATTER_ONLY -> MachinesConfig.BlackHoleGenerator.GENERATION_RATIO_DRAIN * matterExtracted
				}

				val energyInserted = energy.receiveEnergy(targetEnergy, true)
				if (!energyInserted.isPositive) return
				val actualMatterRatio = energyInserted / targetEnergy * matterExtracted

				matter.extractMatter(actualMatterRatio, false)
				energy.receiveEnergy(energyInserted, false)

				when (mode) {
					Mode.TARGET_MASS -> {
						if (targetMass > blackHole.mass) {
							blackHole.mass += actualMatterRatio * MachinesConfig.BlackHoleGenerator.FEEDING_EFFICIENCY
						} else if (targetMass < blackHole.mass) {
							blackHole.mass -= actualMatterRatio * MachinesConfig.BlackHoleGenerator.DRAINING_EFFICIENCY
						}
					}

					Mode.MATTER_ONLY -> {
						blackHole.mass += actualMatterRatio * MachinesConfig.BlackHoleGenerator.FEEDING_EFFICIENCY
					}

					Mode.ANTIMATTER_ONLY -> {
						blackHole.mass -= actualMatterRatio * MachinesConfig.BlackHoleGenerator.DRAINING_EFFICIENCY
					}
				}
			}
		}
	}

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

	companion object {
		val BLACK_HOLE = BlockEntityTag(BlackHoleBlockEntity::class)

		private fun points(x: Int, z: Int, result: ObjectArraySet<BlockPos>) {
			if (x == 0) {
				result.add(BlockPos(0, 0, z))
				result.add(BlockPos(0, 0, -z))
				result.add(BlockPos(z, 0, 0))
				result.add(BlockPos(-z, 0, 0))
			} else if (x == z) {
				result.add(BlockPos(x, 0, z))
				result.add(BlockPos(-x, 0, z))
				result.add(BlockPos(x, 0, -z))
				result.add(BlockPos(-x, 0, -z))
			} else if (x < z) {
				result.add(BlockPos(x, 0, z))
				result.add(BlockPos(-x, 0, z))
				result.add(BlockPos(x, 0, -z))
				result.add(BlockPos(-x, 0, -z))

				result.add(BlockPos(z, 0, x))
				result.add(BlockPos(-z, 0, x))
				result.add(BlockPos(z, 0, -x))
				result.add(BlockPos(-z, 0, -x))
			}
		}

		private fun ring(size: Int): ObjectArrayList<BlockPos> {
			val positions = ObjectArraySet<BlockPos>()
			var x = 0
			var y = size
			var p = (5 - size * 4) / 4
			points(x, y, positions)

			while (x < y) {
				x++

				if (p < 0) {
					p += x * 2 + 1
				} else {
					y--
					p += (x - y) * 2 + 1
				}

				points(x, y, positions)
			}

			val result = ObjectArrayList<BlockPos>(positions.size)

			for (pos in positions) {
				if (pos.x == 0 && pos.z + size == 0) continue
				result.add(BlockPos(pos.x, 0, pos.z + size))
			}

			return result
		}

		val CONFIGURATIONS = immutableList {
			for (i2 in 0 until 59) {
				val i = i2 + 5

				accept(lazy {
					shapedMultiblock {
						builder.customCheck {
							val blocks = it.blocks()

							blocks.getInt(MBlocks.MATTER_INJECTOR) == 1 &&
							blocks.getInt(MBlocks.ANTIMATTER_INJECTOR) == 1 &&
							blocks.getInt(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR) == 1
						}

						block(MBlocks.BLACK_HOLE_GENERATOR)

						relative(BlockPos(0, 0, i)) {
							block(MBlocks.BLACK_HOLE)
							tag(BLACK_HOLE)
						}

						for (p in i until i * 2) {
							relative(BlockPos(0, 0, p)) {
								air()
							}
						}

						for (p in 0 until i) {
							relative(BlockPos(p, 0, i)) {
								air()
								block(MBlocks.GRAVITATION_STABILIZER)
								block(MBlocks.GRAVITATION_STABILIZER_LENS)
							}

							relative(BlockPos(-p, 0, i)) {
								air()
								block(MBlocks.GRAVITATION_STABILIZER)
								block(MBlocks.GRAVITATION_STABILIZER_LENS)
							}
						}

						val ring = ring(i)

						for (pos in ring) {
							val node = relative(pos)

							if ((pos.x == i || pos.x == -i) && pos.z == i) {
								node.block(MBlocks.MATTER_INJECTOR)
								node.block(MBlocks.ANTIMATTER_INJECTOR)
								node.tagBlock()
							} else if (pos.x == 0 && pos.z == i * 2) {
								node.block(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR)
								node.tagBlock()
							} else if (pos.x == i || pos.x == -i || pos.z == i * 2 || pos.z == 0) {
								node.or {
									block(MBlocks.ENERGY_OUTPUT_HATCH)
									tag(EnergyHatchBlockEntity.OUTPUT_TAG)
								}

								node.or {
									block(MBlocks.MATTER_INPUT_HATCH)
									tag(MatterHatchBlockEntity.INPUT_TAG)
								}

								node.block(MBlockTags.MULTIBLOCK_STRUCTURE)
							} else {
								node.block(MBlockTags.MULTIBLOCK_HARD_STRUCTURE)
							}
						}
					}
				})
			}
		}
	}
}
