package ru.dbotthepony.mc.otm.network

import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.level.Level
import net.minecraftforge.network.NetworkDirection
import net.minecraftforge.network.NetworkEvent
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.android.feature.ItemEntityDataPacket
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.onceClient
import java.lang.ref.SoftReference
import java.util.WeakHashMap
import java.util.function.Supplier

class BlockEntitySyncPacket(val position: BlockPos, val buffer: ByteArray, val validBytes: Int) : MatteryPacket {
	override fun write(buff: FriendlyByteBuf) {
		buff.writeBlockPos(position)
		buff.writeBytes(buffer, 0, validBytes)
	}

	private fun execute() {
		val level = minecraft.player?.level()

		if (level == null) {
			LOGGER.error("Received BlockEntitySyncPacket before we are in valid level.")

			onceClient {
				execute()
			}

			return
		}

		val blockEntity = level.getBlockEntity(position)

		if (blockEntity == null) {
			LOGGER.warn("Putting BlockEntitySyncPacket received for $position into backlog because there is literally no block entity there!")
			LOGGER.warn("This is CERTAINLY a bug in one of optimizing mods you have or server has installed!")
			LOGGER.warn("This can cause memory leak.")

			backlog.computeIfAbsent(level) { Object2ObjectOpenHashMap() }
				.computeIfAbsent(position, Object2ObjectFunction { ArrayList() })
				.add(this)

			return
		} else if (blockEntity !is MatteryBlockEntity) {
			LOGGER.warn("Dropping BlockEntitySyncPacket received for $position, because there is $blockEntity which is not MatteryBlockEntity!")
			backlog[level]?.remove(position)
			return
		}

		val packets = backlog[level]?.remove(position)

		try {
			if (packets != null) {
				for (packet in packets) {
					blockEntity.synchronizer.read(FastByteArrayInputStream(packet.buffer, 0, packet.validBytes))
				}
			}

			blockEntity.synchronizer.read(FastByteArrayInputStream(buffer, 0, validBytes))
		} catch(err: Throwable) {
			LOGGER.error("Exception while reading synchronized BlockEntity data!\nPosition: $position\nBlock: ${level.getBlockState(position)}\nBlock entity: $blockEntity", err)
		}
	}

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

		context.enqueueWork {
			execute()
		}
	}

	companion object {
		private val backlog = WeakHashMap<Level, Object2ObjectOpenHashMap<BlockPos, ArrayList<BlockEntitySyncPacket>>>()

		fun read(buff: FriendlyByteBuf): BlockEntitySyncPacket {
			val position = buff.readBlockPos()
			val size = buff.readableBytes()
			val array = ByteArray(size)
			buff.readBytes(array)
			return BlockEntitySyncPacket(position, array, size)
		}

		private val LOGGER = LogManager.getLogger()
	}
}

object WorldNetworkChannel : MatteryNetworkChannel(
	version = "4",
	name = "world"
) {
	fun register() {
		add(BlockEntitySyncPacket::class.java, BlockEntitySyncPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		add(ItemEntityDataPacket::class.java, ItemEntityDataPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
	}
}
