package ru.dbotthepony.mc.otm.capability.matter

import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.core.readItemType
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.writeItemType
import java.util.UUID

sealed interface IReplicationTask<S : IReplicationTask<S>> {
	val id: UUID
	val patternId: UUID?
	val item: Item
	val inProgress: Int
	val finished: Int
	val required: Int

	fun asMutable(): MutableReplicationTask
	fun asImmutable(): ReplicationTask

	val total get() = inProgress + finished + required

	fun copyAsMutable(
		id: UUID = this.id,
		patternId: UUID? = this.patternId,
		item: Item = this.item,
		inProgress: Int = this.inProgress,
		finished: Int = this.finished,
		required: Int = this.required,
	): MutableReplicationTask {
		return MutableReplicationTask(id, patternId, item, inProgress, finished, required)
	}

	fun copyAsImmutable(
		id: UUID = this.id,
		patternId: UUID? = this.patternId,
		item: Item = this.item,
		inProgress: Int = this.inProgress,
		finished: Int = this.finished,
		required: Int = this.required,
	): ReplicationTask {
		return ReplicationTask(id, patternId, item, inProgress, finished, required)
	}

	fun matchId(other: IReplicationTask<*>): Boolean {
		return other.id == id
	}

	fun stack(count: Int = 1): ItemStack {
		return ItemStack(item, count)
	}

	fun allocate(amount: Int = 1): S
	fun finish(amount: Int = 1): S

	fun serializeNBT(): CompoundTag {
		return CompoundTag().also {
			it["id"] = id

			if (patternId != null)
				it["patternId"] = patternId!!

			it["item"] = item.registryName!!.toString()
			it["inProgress"] = inProgress
			it["finished"] = finished
			it["required"] = required
		}
	}

	fun write(buff: FriendlyByteBuf) {
		buff.writeUUID(id)

		buff.writeBoolean(patternId != null)
		patternId?.let(buff::writeUUID)

		buff.writeItemType(item)

		buff.writeInt(inProgress)
		buff.writeInt(finished)
		buff.writeInt(required)
	}
}

private fun deserializeNBT(nbt: Tag?, mutable: Boolean): IReplicationTask<*>? {
	if (nbt !is CompoundTag)
		return null

	if (!nbt.contains("id") || !nbt.contains("inProgress") || !nbt.contains("finished") || !nbt.contains("required") || !nbt.contains("item"))
		return null

	val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(nbt.getString("item"))) ?: return null

	if (item == Items.AIR)
		return null

	val id = nbt.getUUID("id")
	val patternId = if (nbt.contains("patternId")) nbt.getUUID("patternId") else null

	val inProgress = nbt.getInt("inProgress")
	val finished = nbt.getInt("finished")
	val required = nbt.getInt("required")

	if (mutable) {
		return MutableReplicationTask(id, patternId, item, inProgress, finished, required)
	} else {
		return ReplicationTask(id, patternId, item, inProgress, finished, required)
	}
}

private fun read(buff: FriendlyByteBuf, mutable: Boolean): IReplicationTask<*>? {
	val id = buff.readUUID()
	val patternId: UUID? = if (buff.readBoolean()) buff.readUUID() else null
	val item = buff.readItemType()
	val inProgress = buff.readInt()
	val finished = buff.readInt()
	val required = buff.readInt()

	item ?: return null

	if (mutable) {
		return MutableReplicationTask(id, patternId, item, inProgress, finished, required)
	} else {
		return ReplicationTask(id, patternId, item, inProgress, finished, required)
	}
}

data class ReplicationTask(
	override val id: UUID,
	override val patternId: UUID?,
	override val item: Item,
	override val inProgress: Int,
	override val finished: Int,
	override val required: Int
) : IReplicationTask<ReplicationTask> {
	override fun asMutable(): MutableReplicationTask {
		return MutableReplicationTask(id, patternId, item, inProgress, finished, required)
	}

	override fun asImmutable(): ReplicationTask {
		return this
	}

	override fun allocate(amount: Int): ReplicationTask {
		return ReplicationTask(id, patternId, item, inProgress + amount, finished, required - amount)
	}

	override fun finish(amount: Int): ReplicationTask {
		return ReplicationTask(id, patternId, item, inProgress - amount, finished + amount, required)
	}

	companion object {
		fun deserializeNBT(nbt: Tag?): ReplicationTask? {
			return deserializeNBT(nbt, false) as ReplicationTask?
		}

		fun read(buff: FriendlyByteBuf): ReplicationTask? {
			return read(buff, false) as ReplicationTask?
		}
	}
}

data class MutableReplicationTask(
	override val id: UUID,
	override val patternId: UUID?,
	override val item: Item,
	override var inProgress: Int,
	override var finished: Int,
	override var required: Int
) : IReplicationTask<MutableReplicationTask> {
	override fun asMutable(): MutableReplicationTask {
		return this
	}

	override fun asImmutable(): ReplicationTask {
		return ReplicationTask(id, patternId, item, inProgress, finished, required)
	}

	override fun allocate(amount: Int): MutableReplicationTask {
		inProgress += amount
		finished -= amount
		return this
	}

	override fun finish(amount: Int): MutableReplicationTask {
		finished += amount
		inProgress -= amount
		return this
	}

	companion object {
		fun deserializeNBT(nbt: Tag?): MutableReplicationTask? {
			return deserializeNBT(nbt, true) as MutableReplicationTask?
		}

		fun read(buff: FriendlyByteBuf): MutableReplicationTask? {
			return read(buff, true) as MutableReplicationTask?
		}
	}
}

fun FriendlyByteBuf.writeReplicationTask(task: IReplicationTask<*>) = task.write(this)
fun FriendlyByteBuf.readReplicationTask() = ReplicationTask.read(this)
fun FriendlyByteBuf.readMutableReplicationTask() = MutableReplicationTask.read(this)
