package ru.dbotthepony.mc.otm;

import kotlin.KotlinVersion;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature;
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity;
import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue;
import ru.dbotthepony.mc.otm.capability.MatteryCapability;
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
import ru.dbotthepony.mc.otm.capability.drive.DrivePool;
import ru.dbotthepony.mc.otm.client.AndroidAbilityKeyMapping;
import ru.dbotthepony.mc.otm.client.AndroidMenuKeyMapping;
import ru.dbotthepony.mc.otm.client.ClientEventHandlerKt;
import ru.dbotthepony.mc.otm.client.ClientTickHandlerKt;
import ru.dbotthepony.mc.otm.client.MatteryGUI;
import ru.dbotthepony.mc.otm.client.model.ExosuitModel;
import ru.dbotthepony.mc.otm.client.model.GravitationStabilizerModel;
import ru.dbotthepony.mc.otm.client.model.TritaniumArmorModel;
import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer;
import ru.dbotthepony.mc.otm.client.render.blockentity.BatteryBankRenderer;
import ru.dbotthepony.mc.otm.client.render.blockentity.MatterBatteryBankRenderer;
import ru.dbotthepony.mc.otm.compat.adastra.AdAstraCompatKt;
import ru.dbotthepony.mc.otm.compat.curios.CuriosCompatKt;
import ru.dbotthepony.mc.otm.compat.mekanism.MekanismHooks;
import ru.dbotthepony.mc.otm.compat.mekanism.QIOKt;
import ru.dbotthepony.mc.otm.config.AndroidConfig;
import ru.dbotthepony.mc.otm.config.ClientConfig;
import ru.dbotthepony.mc.otm.config.ExopackConfig;
import ru.dbotthepony.mc.otm.config.ItemsConfig;
import ru.dbotthepony.mc.otm.config.MachinesConfig;
import ru.dbotthepony.mc.otm.config.ServerCompatConfig;
import ru.dbotthepony.mc.otm.config.ServerConfig;
import ru.dbotthepony.mc.otm.config.ToolsConfig;
import ru.dbotthepony.mc.otm.core.math.Decimal;
import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem;
import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem;
import ru.dbotthepony.mc.otm.item.QuantumBatteryItem;
import ru.dbotthepony.mc.otm.item.weapon.AbstractWeaponItem;
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem;
import ru.dbotthepony.mc.otm.matter.MatterManager;
import ru.dbotthepony.mc.otm.network.*;
import ru.dbotthepony.mc.otm.registry.*;
import ru.dbotthepony.mc.otm.storage.*;
import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger;
import top.theillusivec4.curios.api.CuriosApi;

import static net.minecraftforge.common.MinecraftForge.EVENT_BUS;

import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Objects;

// The value here should match an entry in the META-INF/mods.toml file
@Mod(OverdriveThatMatters.MOD_ID)
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public final class OverdriveThatMatters {
	// Directly reference a log4j logger.
	public static final String MOD_ID = "overdrive_that_matters";
	private static final Logger LOGGER = LogManager.getLogger();

	public static OverdriveThatMatters INSTANCE;
	private StorageStackType<ItemStackWrapper> ITEM_STORAGE;

	@NotNull
	public StorageStackType<ItemStackWrapper> ITEM_STORAGE() {
		return Objects.requireNonNull(ITEM_STORAGE);
	}

	public static ResourceLocation loc(String path) {
		return new ResourceLocation(MOD_ID, path);
	}

	private static void checkIfKotlinIsInstalled() {
		if (!KotlinVersion.CURRENT.isAtLeast(1, 8, 0)) {
			throw new UnsupportedClassVersionError("Installed kotlin version is " + KotlinVersion.CURRENT + ", when at least 1.8.0 is required.");
		}
	}

	public OverdriveThatMatters() {
		if (INSTANCE != null) {
			throw new IllegalStateException("yo what the fuck");
		}

		try {
			checkIfKotlinIsInstalled();
		} catch (Throwable err) {
			if (err instanceof NoClassDefFoundError) {
				for (int i = 0; i < 16; i++)
					LOGGER.fatal("Overdrive That Matters requires Kotlin to be installed, get Kotlin for Forge from https://github.com/thedarkcolour/KotlinForForge");

				LOGGER.fatal("Overdrive That Matters requires Kotlin to be installed, get Kotlin for Forge from https://github.com/thedarkcolour/KotlinForForge", err);
				throw new RuntimeException("Overdrive That Matters requires Kotlin to be installed, get Kotlin for Forge from https://github.com/thedarkcolour/KotlinForForge");
			}

			for (int i = 0; i < 16; i++)
				LOGGER.fatal("Overdrive That Matters' Kotlin version is not satisfied, get newer Kotlin for Forge from https://github.com/thedarkcolour/KotlinForForge");

			LOGGER.fatal("Overdrive That Matters' Kotlin version is not satisfied, get newer Kotlin for Forge from https://github.com/thedarkcolour/KotlinForForge", err);

			throw err;
		}

		INSTANCE = this;

		var modBus = FMLJavaModLoadingContext.get().getModEventBus();

		MRegistry.INSTANCE.initialize(modBus);
		MatterManager.INSTANCE.initialize(modBus);

		modBus.addListener(EventPriority.HIGHEST, this::setup);
		modBus.addListener(EventPriority.NORMAL, this::setupClient);
		modBus.addListener(EventPriority.NORMAL, MatteryCapability::register);

		DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
			modBus.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::register);
			modBus.addListener(EventPriority.NORMAL, AndroidAbilityKeyMapping.INSTANCE::register);
			modBus.addListener(EventPriority.NORMAL, TritaniumArmorModel::register);
			modBus.addListener(EventPriority.NORMAL, GravitationStabilizerModel::register);
			modBus.addListener(EventPriority.NORMAL, MCreativeTabs.INSTANCE::register);

			modBus.addListener(EventPriority.NORMAL, BatteryBankRenderer.Companion::onRegisterAdditionalModels);
			modBus.addListener(EventPriority.NORMAL, MatterBatteryBankRenderer.Companion::onRegisterAdditionalModels);
		});

		ClientConfig.INSTANCE.register();
		ServerConfig.INSTANCE.register();
		ServerCompatConfig.INSTANCE.register();
		AndroidConfig.INSTANCE.register();
		ExopackConfig.INSTANCE.register();
		ItemsConfig.INSTANCE.register();
		MachinesConfig.INSTANCE.register();
		ToolsConfig.INSTANCE.register();
	}

	private void setup(final FMLCommonSetupEvent event) {
		EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::onServerPostTick);
		EVENT_BUS.addListener(EventPriority.HIGHEST, DrivePool.INSTANCE::serverStopEvent);
		EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::serverStartEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, DrivePool.INSTANCE::onWorldSave);

		EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopped);
		EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopping);
		EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStarting);
		EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onWorldTick);
		EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onServerTick);

		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerTick);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::isMobEffectApplicable);
		EVENT_BUS.addListener(EventPriority.LOW, MatteryPlayerCapability.Companion::onHurtEvent);
		EVENT_BUS.addListener(EventPriority.HIGH, MatteryPlayerCapability.Companion::onAttackEvent);
		EVENT_BUS.addGenericListener(Entity.class, EventPriority.NORMAL, MatteryPlayerCapability.Companion::onAttachCapabilityEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerChangeDimensionEvent);
		EVENT_BUS.addListener(EventPriority.LOWEST, MatteryPlayerCapability.Companion::onPlayerDeath);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerCloneEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onStartTracking);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onStopTracking);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::addCommands);

		EVENT_BUS.addListener(EventPriority.NORMAL, ExplosionQueue.Companion::onWorldTick);
		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::tick);
		EVENT_BUS.addListener(EventPriority.NORMAL, QuantumBatteryItem.Companion::tick);
		EVENT_BUS.addListener(EventPriority.LOWEST, PortableCondensationDriveItem.Companion::onPickupEvent);

		EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::reloadEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::syncEvent);

		EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::reloadEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::onServerStarted);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::onDataPackSync);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::addCommands);

		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onServerStopping);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onLevelUnload);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onWatch);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onForget);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::playerDisconnected);
		EVENT_BUS.addListener(EventPriority.LOWEST, MatteryBlockEntity.Companion::postLevelTick);

		EVENT_BUS.addListener(EventPriority.LOWEST, KillAsAndroidTrigger.INSTANCE::onKill);

		EVENT_BUS.addListener(EventPriority.NORMAL, EnderTeleporterFeature.Companion::onEntityDeath);
		EVENT_BUS.addListener(EventPriority.HIGH, TritaniumArmorItem.Companion::onHurt);

		EVENT_BUS.addListener(EventPriority.NORMAL, ExplosiveHammerItem.Companion::onLeftClickBlock);

		MatteryPlayerNetworkChannel.INSTANCE.register();
		MenuNetworkChannel.INSTANCE.register();
		WeaponNetworkChannel.INSTANCE.register();
		RegistryNetworkChannel.INSTANCE.register();
		WorldNetworkChannel.INSTANCE.register();
		GenericNetworkChannel.INSTANCE.register();

		ITEM_STORAGE = StorageRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY, new Decimal("3.125"));

		if (ModList.get().isLoaded("mekanism")) {
			EVENT_BUS.addGenericListener(BlockEntity.class, EventPriority.NORMAL, QIOKt::attachCapabilities);
		}

		if (ModList.get().isLoaded(CuriosApi.MODID)) {
			EVENT_BUS.addListener(EventPriority.NORMAL, CuriosCompatKt::onCuriosSlotModifiersUpdated);
		}

		if (AdAstraCompatKt.isAdAstraLoaded()) {
			EVENT_BUS.addListener(EventPriority.NORMAL, AdAstraCompatKt::onDamageEvent);
			EVENT_BUS.addListener(EventPriority.NORMAL, AdAstraCompatKt::onMatteryTick);
		}
	}

	private void setupClient(final FMLClientSetupEvent event) {
		EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::tooltipEvent);

		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::playerRenderHook);
		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::fovHook);
		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::clickHook);
		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::renderViewModel);

		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryGUI.INSTANCE::onScreenRender);
		EVENT_BUS.addListener(EventPriority.LOWEST, MatteryGUI.INSTANCE::onOpenGUIEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryGUI.INSTANCE::onRenderGuiEvent);
		EVENT_BUS.addListener(EventPriority.HIGH, MatteryGUI.INSTANCE::onLayerRenderEvent);

		EVENT_BUS.addListener(EventPriority.NORMAL, ShockwaveRenderer.INSTANCE::onRender);

		EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onMovementInputUpdate);
		EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onScreenOpen);
		EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onPostScreenInit);
		EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onMouseDragged);
		EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onMouseScrolled);
		EVENT_BUS.addListener(EventPriority.LOWEST, ClientTickHandlerKt::onClientTick);
		EVENT_BUS.addListener(EventPriority.HIGHEST, ClientTickHandlerKt::onClientConnected);
		EVENT_BUS.addListener(EventPriority.HIGHEST, ClientTickHandlerKt::onClientDisconnected);

		EVENT_BUS.addListener(EventPriority.NORMAL, QuantumBatteryItem.Companion::clientDisconnect);

		if (ModList.get().isLoaded("mekanism")) {
			EVENT_BUS.addListener(EventPriority.NORMAL, MekanismHooks::tooltipEvent);
		}

		EVENT_BUS.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::onRenderGuiEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::onMouseClick);

		EVENT_BUS.addListener(EventPriority.NORMAL, AndroidAbilityKeyMapping.INSTANCE::onRenderGuiEvent);
		EVENT_BUS.addListener(EventPriority.NORMAL, AndroidAbilityKeyMapping.INSTANCE::onRenderLevel);

		EVENT_BUS.addListener(EventPriority.NORMAL, ExosuitModel::onPlayerRendered);

		event.enqueueWork(GlobalEventHandlerKt::recordClientThread);
	}
}
