package ru.dbotthepony.mc.otm.network

import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.item.ItemStack
import net.minecraftforge.network.NetworkDirection
import net.minecraftforge.network.NetworkEvent
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket
import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.menu.matter.CancelTaskPacket
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.matter.PatternsChangePacket
import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket
import ru.dbotthepony.mc.otm.menu.matter.TasksChangePacket
import ru.dbotthepony.mc.otm.menu.data.ClearItemViewPacket
import ru.dbotthepony.mc.otm.menu.data.ItemViewInteractPacket
import ru.dbotthepony.mc.otm.menu.data.StackAddPacket
import ru.dbotthepony.mc.otm.menu.data.StackChangePacket
import ru.dbotthepony.mc.otm.menu.data.StackRemovePacket
import java.io.ByteArrayInputStream
import java.util.function.Supplier

class MenuFieldPacket(val containerId: Int, val bytes: ByteArray, val length: Int) : MatteryPacket {
	constructor(containerId: Int, stream: FastByteArrayOutputStream) : this(containerId, stream.array, stream.length)

	override fun write(buff: FriendlyByteBuf) {
		buff.writeVarInt(containerId)
		buff.writeBytes(bytes, 0, length)
	}

	override fun play(context: Supplier<NetworkEvent.Context>) {
		context.packetHandled = true

		context.enqueueWork {
			if (containerId == ExoPackInventoryMenu.CONTAINER_ID) {
				minecraft.player?.matteryPlayer?.exoPackMenu?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length))
			} else {
				val menu = minecraft.player?.containerMenu as? MatteryMenu ?: return@enqueueWork

				if (menu.containerId == containerId)
					menu.mSynchronizer.read(ByteArrayInputStream(bytes, 0, length))
			}
		}
	}

	companion object {
		fun read(buff: FriendlyByteBuf): MenuFieldPacket {
			val containerId = buff.readVarInt()
			val readable = buff.readableBytes()
			return MenuFieldPacket(containerId, ByteArray(readable).also(buff::readBytes), readable)
		}
	}
}

class SetCarriedPacket(val item: ItemStack) : MatteryPacket {
	override fun write(buff: FriendlyByteBuf) {
		buff.writeItem(item)
	}

	override fun play(context: Supplier<NetworkEvent.Context>) {
		context.get().packetHandled = true
		context.get().enqueueWork { minecraft.player?.containerMenu?.carried = item }
	}

	companion object {
		fun read(buff: FriendlyByteBuf): SetCarriedPacket {
			return SetCarriedPacket(buff.readItem())
		}
	}
}

object MenuNetworkChannel : MatteryNetworkChannel(
	version = "3",
	name = "menu"
) {
	fun register() {
		add(SetCarriedPacket::class.java, SetCarriedPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		add(ItemFilterSlotPacket::class.java, ItemFilterSlotPacket.Companion::read)

		// networked view
		add(ClearItemViewPacket::class.java, ClearItemViewPacket::read, NetworkDirection.PLAY_TO_CLIENT)
		add(ItemViewInteractPacket::class.java, ItemViewInteractPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
		add(StackAddPacket::class.java, StackAddPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		add(StackChangePacket::class.java, StackChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		add(StackRemovePacket::class.java, StackRemovePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)

		// Menu data
		// Server->Client
		add(MenuFieldPacket::class.java, MenuFieldPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		// Client->Server
		add(MatteryMenu.PlayerInputPacket::class.java, MatteryMenu::PlayerInputPacket, NetworkDirection.PLAY_TO_SERVER)

		// Item monitor
		add(ItemMonitorPlayerSettings::class.java, ItemMonitorPlayerSettings.Companion::read)

		// matter panel menu
		add(CancelTaskPacket::class.java, CancelTaskPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
		add(PatternsChangePacket::class.java, PatternsChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		add(TasksChangePacket::class.java, TasksChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		add(ReplicationRequestPacket::class.java, ReplicationRequestPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)

		add(InventoryScrollPacket::class.java, InventoryScrollPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
	}
}
