package ru.dbotthepony.mc.otm.android

import com.google.common.collect.ImmutableMap
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import net.minecraft.client.server.IntegratedServer
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.network.protocol.common.custom.CustomPacketPayload
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener
import net.minecraft.util.profiling.ProfilerFiller
import net.neoforged.neoforge.event.AddReloadListenerEvent
import net.neoforged.neoforge.event.OnDatapackSyncEvent
import net.neoforged.neoforge.network.PacketDistributor
import net.neoforged.neoforge.network.handling.IPayloadContext
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.ResourceLocation
import ru.dbotthepony.mc.otm.core.fromJsonStrict
import ru.dbotthepony.mc.otm.core.fromNetwork
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.toNetwork
import ru.dbotthepony.mc.otm.onceServer
import java.util.*

object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> {
	const val DIRECTORY = "otm_android_research"
	private val LOGGER = LogManager.getLogger()

	operator fun get(id: ResourceLocation): AndroidResearchType? {
		return researchMap[id] ?: maskedMap[id]
	}

	override fun iterator(): Iterator<AndroidResearchType> {
		return researchMap.values.iterator()
	}

	private val maskedMap = HashMap<ResourceLocation, AndroidResearchType>()

	/**
	 * Internal use only, used by [AndroidResearchDataProvider] for validation purposes
	 */
	internal fun put(value: AndroidResearchType) {
		maskedMap[value.id] = value
	}

	var researchMap: Map<ResourceLocation, AndroidResearchType> = mapOf()
		private set

	override fun apply(
		jsonElementMap: Map<ResourceLocation, JsonElement>,
		manager: ResourceManager,
		profiler: ProfilerFiller
	) {
		val builder = ImmutableMap.builder<ResourceLocation, AndroidResearchType>()

		for ((k, v) in jsonElementMap) {
			if (v !is JsonObject) {
				LOGGER.error("Skipping Android Feature $k because top value is not a Object")
				continue
			}

			v["id"] = k.toString()

			try {
				builder.put(k, AndroidResearchType.CODEC.fromJsonStrict(v))
			} catch(err: RuntimeException) {
				throw JsonSyntaxException("Caught an exception while decoding android research $k", err)
			}
		}

		researchMap = builder.build()

		for (value in researchMap.values) {
			value.validate()
		}

		if (SERVER_IS_LIVE) {
			onceServer {
				for (ply in MINECRAFT_SERVER.playerList.players) {
					ply.matteryPlayer.reloadResearch(MINECRAFT_SERVER.registryAccess())
				}
			}
		}
	}

	fun reloadEvent(event: AddReloadListenerEvent) {
		event.addListener(this)
	}

	fun syncEvent(event: OnDatapackSyncEvent) {
		val packet = SyncPacket(researchMap.values)

		if (event.player != null) {
			PacketDistributor.sendToPlayer(event.player as ServerPlayer, packet)
		} else {
			PacketDistributor.sendToAllPlayers(packet)
		}
	}

	val SYNC_TYPE = CustomPacketPayload.Type<SyncPacket>(ResourceLocation(OverdriveThatMatters.MOD_ID, "research_sync"))
	val SYNC_CODEC: StreamCodec<FriendlyByteBuf, SyncPacket> = StreamCodec.ofMember(SyncPacket::write, ::readSyncPacket)

	class SyncPacket(val collection: Collection<AndroidResearchType>) : CustomPacketPayload {
		fun write(buff: FriendlyByteBuf) {
			buff.writeCollection(collection) { a, b -> AndroidResearchType.CODEC.toNetwork(a, b) }
			LOGGER.debug("Constructed android research registry packet, ${buff.writerIndex()} bytes in size")
		}

		fun play(context: IPayloadContext) {
			if (NULLABLE_MINECRAFT_SERVER is IntegratedServer)
				return

			val builder = ImmutableMap.builder<ResourceLocation, AndroidResearchType>()

			for (v in collection) {
				builder.put(v.id, v)
			}

			researchMap = builder.build()

			for (value in researchMap.values) {
				value.validate()
			}

			context.enqueueWork {
				context.player().matteryPlayer.reloadResearch(context.player().registryAccess())
			}
		}

		override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
			return SYNC_TYPE
		}
	}

	fun readSyncPacket(buff: FriendlyByteBuf): SyncPacket {
		LOGGER.info("Received android research registry packet, ${buff.readableBytes()} bytes in size")
		return SyncPacket(buff.readCollection({ LinkedList() }, { AndroidResearchType.CODEC.fromNetwork(it) }))
	}
}
