package ru.dbotthepony.mc.otm.datagen.blocks

import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.RotatedPillarBlock
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.client.model.generators.BlockStateProvider
import net.neoforged.neoforge.client.model.generators.ConfiguredModel
import net.neoforged.neoforge.data.event.GatherDataEvent
import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.core.getValueNullable
import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
import ru.dbotthepony.mc.otm.core.math.xRotationBlockstateNorth
import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateNorth
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.datagen.DataGen
import ru.dbotthepony.mc.otm.datagen.modLocation
import java.util.*

typealias BlockStateTransform = (state: BlockState, builder: ConfiguredModel.Builder<*>, path: String) -> String?
private val EMPTY: BlockStateTransform = { _, _, _ -> null }

private fun initialTransform(it: BlockState, modelPath: String, builder: ConfiguredModel.Builder<*>): String {
	@Suppress("NAME_SHADOWING") var modelPath = modelPath

	it.getValueNullable(BlockRotationFreedom.HORIZONTAL.property)?.let {
		builder.rotationY(it.front.yRotationBlockstateNorth())
	}

	it.getValueNullable(BlockRotationFreedom.DIRECTIONAL.property)?.let {
		builder.rotationY(it.front.yRotationBlockstateNorth())
		builder.rotationX(it.front.xRotationBlockstateNorth())
	}

	it.getValueNullable(BlockRotationFreedom.DIRECTIONAL_WITH_ROTATION.property)?.let {
		builder.rotationY(it.front.yRotationBlockstateNorth() + it.top.yRotationBlockstateNorth())
		builder.rotationX(it.front.xRotationBlockstateNorth())
	}

	it.getValueNullable(WorkerState.WORKER_STATE)?.let {
		modelPath += "_" + it.name.lowercase()
	}

	it.getValueNullable(WorkerState.SEMI_WORKER_STATE)?.let {
		modelPath += "_" + it.name.lowercase()
	}

	return modelPath
}

class MatteryBlockStateProvider(event: GatherDataEvent) : BlockStateProvider(event.generator.packOutput, DataGen.MOD_ID, event.existingFileHelper) {
	private val callbacks = LinkedList<() -> Unit>()

	fun exec(lambda: () -> Unit): MatteryBlockStateProvider {
		callbacks.add(lambda)
		return this
	}

	fun block(block: Block, model: String = "block/${block.registryName!!.path}", func: BlockStateTransform = EMPTY) = exec {
		getVariantBuilder(block).forAllStates {
			val builder = ConfiguredModel.builder()
			var modelPath = initialTransform(it, model, builder)
			modelPath = func(it, builder, modelPath) ?: modelPath

			builder.modelFile(models().getExistingFile(modLocation(modelPath)))

			return@forAllStates builder.build()
		}
	}

	fun block(blocks: Collection<Block>): MatteryBlockStateProvider {
		for (block in blocks) {
			this.block(block)
		}

		return this
	}

	fun block(vararg blocks: Block): MatteryBlockStateProvider {
		for (block in blocks) {
			block(block)
		}

		return this
	}

	fun simpleBlockM(vararg blocks: Block): MatteryBlockStateProvider {
		for (block in blocks) {
			simpleBlockM(block)
		}

		return this
	}

	fun simpleBlockM(block: Block, model: ResourceLocation = checkNotNull(block.registryName) {"$block registry name is null!"}): MatteryBlockStateProvider {
		exec {
			getVariantBuilder(block).forAllStates {
				return@forAllStates arrayOf(ConfiguredModel(models().getExistingFile(model)))
			}
		}

		return this
	}

	fun simplePillar(vararg blocks: Block): MatteryBlockStateProvider {
		for (block in blocks) {
			exec {
				getVariantBuilder(block)
					.partialState().with(RotatedPillarBlock.AXIS, Direction.Axis.Y)
					.modelForState().modelFile(models().getExistingFile(block.registryName)).addModel()
					.partialState().with(RotatedPillarBlock.AXIS, Direction.Axis.Z)
					.modelForState().modelFile(models().getExistingFile(block.registryName)).rotationX(90).addModel()
					.partialState().with(RotatedPillarBlock.AXIS, Direction.Axis.X)
					.modelForState().modelFile(models().getExistingFile(block.registryName)).rotationX(90).rotationY(90).addModel()
			}
		}

		return this
	}

	fun simpleBlockM(blocks: Collection<Block>): MatteryBlockStateProvider {
		blocks.forEach(this::simpleBlockM)
		return this
	}

	fun ore(blocks: Collection<Block>): MatteryBlockStateProvider {
		blocks.forEach(this::ore)
		return this
	}

	fun ore(vararg blocks: Block): MatteryBlockStateProvider {
		for (block in blocks) {
			exec {
				var first = true

				getVariantBuilder(block).forAllStates {
					check(first) { "$block has more than one BlockState" }
					first = false

					val localModel = models().getExistingFile(block.registryName ?: throw NullPointerException("$block has no registry name"))

					val generated = ArrayList<ConfiguredModel>()

					for (x in intArrayOf(0, 90, 180)) {
						for (y in intArrayOf(0, 90, 180, 270)) {
							generated.add(with(ConfiguredModel.builder()) {
								rotationX(x)
								rotationY(y)
								modelFile(localModel)
								return@with buildLast()
							})
						}
					}

					return@forAllStates generated.toTypedArray()
				}
			}
		}

		return this
	}

	override fun registerStatesAndModels() {
		for (callback in callbacks) {
			callback.invoke()
		}
	}
}
