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.core.particles.ParticleTypes
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.RandomSource
import net.minecraft.world.level.Level
import net.minecraftforge.network.NetworkDirection
import net.minecraftforge.network.PacketDistributor
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.android.AndroidResearchManager
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 ru.dbotthepony.mc.otm.item.QuantumBatteryItem
import ru.dbotthepony.mc.otm.matter.MatterManager
import java.util.*
import kotlin.collections.ArrayList

class SmokeParticlesPacket(val x: Double, val y: Double, val z: Double) : MatteryPacket {
	override fun write(buff: FriendlyByteBuf) {
		buff.writeDouble(x)
		buff.writeDouble(y)
		buff.writeDouble(z)
	}

	override fun play(context: MNetworkContext) {
		minecraft.player?.level()?.let {
			makeSmoke(x, y, z, it.random, it)
		}
	}

	companion object {
		fun read(buff: FriendlyByteBuf): SmokeParticlesPacket {
			return SmokeParticlesPacket(buff.readDouble(), buff.readDouble(), buff.readDouble())
		}

		fun makeSmoke(x: Double, y: Double, z: Double, random: RandomSource, level: Level) {
			for (i in 0 .. random.nextInt(4, 8))
				level.addParticle(ParticleTypes.POOF, x + random.nextDouble() * 0.5 - 0.25, y + random.nextDouble() * 0.5 - 0.25, z + random.nextDouble() * 0.5 - 0.25, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02)
		}
	}
}

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: MNetworkContext) {
		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 GenericNetworkChannel : MatteryNetworkChannel(
	version = 5,
	name = "generic"
) {
	fun makeSmoke(x: Double, y: Double, z: Double, level: Level) {
		send(PacketDistributor.NEAR.with { PacketDistributor.TargetPoint(x, y, z, 64.0, level.dimension()) }, SmokeParticlesPacket(x, y, z))
	}

	fun makeSmoke(excluded: ServerPlayer, x: Double, y: Double, z: Double) {
		send(PacketDistributor.NEAR.with { PacketDistributor.TargetPoint(excluded, x, y, z, 64.0, excluded.level().dimension()) }, SmokeParticlesPacket(x, y, z))
	}

	fun register() {
		add(QuantumBatteryItem.ChargePacket::class.java, QuantumBatteryItem.Companion::readPacket, NetworkDirection.PLAY_TO_CLIENT)

		add(BlockEntitySyncPacket::class.java, BlockEntitySyncPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
		add(ItemEntityDataPacket::class.java, ItemEntityDataPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)

		add(AndroidResearchManager.SyncPacket::class.java, AndroidResearchManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT, handleOnMainThread = false)
		add(MatterManager.SyncPacket::class.java, MatterManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT)

		add(SmokeParticlesPacket::class, SmokeParticlesPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
	}
}
