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.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener
import net.minecraft.util.profiling.ProfilerFiller
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.AddReloadListenerEvent
import net.minecraftforge.event.OnDatapackSyncEvent
import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.network.NetworkEvent
import net.minecraftforge.network.PacketDistributor
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.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft
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.toJsonStrict
import ru.dbotthepony.mc.otm.core.toNetwork
import ru.dbotthepony.mc.otm.core.util.readJson
import ru.dbotthepony.mc.otm.matter.MatterManager
import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel
import ru.dbotthepony.mc.otm.network.enqueueWork
import ru.dbotthepony.mc.otm.network.packetHandled
import ru.dbotthepony.mc.otm.onceServer
import java.util.LinkedList
import java.util.function.Supplier

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()
				}
			}
		}
	}

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

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

		if (event.player != null) {
			RegistryNetworkChannel.send(event.player!!, packet)
		} else {
			RegistryNetworkChannel.send(PacketDistributor.ALL.noArg(), packet)
		}
	}

	class SyncPacket(val collection: Collection<AndroidResearchType>) : MatteryPacket {
		override 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")
		}

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

			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 {
				minecraft.player?.matteryPlayer?.reloadResearch()
			}
		}
	}

	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) }))
	}
}
