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

import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.client.model.generators.BlockStateProvider
import net.minecraftforge.client.model.generators.ConfiguredModel
import net.minecraftforge.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.datagen.DataGen
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.datagen.modLocation
import java.util.LinkedList

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.ONE.property)?.let {
		builder.rotationY(it.front.yRotationBlockstateNorth())
	}

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

	it.getValueNullable(BlockRotationFreedom.THREE.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, func: BlockStateTransform) = exec {
		getVariantBuilder(block).forAllStates {
			val builder = ConfiguredModel.builder()
			var modelPath = initialTransform(it, "block/${block.registryName!!.path}", 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, EMPTY)
		}

		return this
	}

	fun block(vararg blocks: Block): MatteryBlockStateProvider {
		for (block in blocks) {
			this.block(block, EMPTY)
		}

		return this
	}

	fun simpleBlockM(vararg blocks: Block): MatteryBlockStateProvider {
		for (block in blocks) {
			exec {
				getVariantBuilder(block).forAllStates {
					check(block.registryName != null) {"$block registry name is null!"}
					return@forAllStates arrayOf(ConfiguredModel(models().getExistingFile(block.registryName)))
				}
			}
		}

		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()
		}
	}
}
