package org.inventivetalent.mapmanager;

import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ItemFrame;
import org.bukkit.map.MapView;
import org.bukkit.plugin.java.JavaPlugin;
import org.inventivetalent.mapmanager.manager.MapManager;
import org.inventivetalent.mapmanager.metrics.Metrics;
import org.inventivetalent.reflection.minecraft.Minecraft;
import org.inventivetalent.reflection.resolver.FieldResolver;
import org.inventivetalent.reflection.resolver.MethodResolver;
import org.inventivetalent.reflection.resolver.ResolverQuery;
import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver;
import org.inventivetalent.reflection.resolver.minecraft.OBCClassResolver;
import org.inventivetalent.update.spiget.SpigetUpdate;
import org.inventivetalent.update.spiget.UpdateCallback;
import org.inventivetalent.update.spiget.comparator.VersionComparator;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import static org.inventivetalent.mapmanager.manager.MapManager.Options.*;

/**
 * MapManager-Plugin
 * <p>
 * use <code>Bukkit.getPluginManager().getPlugin("MapManager")</code> to access the plugin instance or <code>Bukkit.getPluginManager().getPlugin("MapManager").getMapManager()</code> to access the {@link MapManager} instance
 */
public class MapManagerPlugin extends JavaPlugin {

	protected static MapManagerPlugin instance;

	protected MapManager mapManagerInstance;

	private   PacketListener packetListener;
	protected MapListener    mapListener;

	protected static NMSClassResolver nmsClassResolver = new NMSClassResolver();
	protected static OBCClassResolver obcClassResolver = new OBCClassResolver();

	private static FieldResolver  CraftWorldFieldResolver;
	private static FieldResolver  WorldFieldResolver;
	private static FieldResolver  WorldServerFieldResolver;
	private static MethodResolver IntHashMapMethodResolver;
	private static MethodResolver EntityMethodResolver;

	public MapManagerPlugin() {
		instance = this;
	}

	@Override
	public void onEnable() {
		if (!Bukkit.getPluginManager().isPluginEnabled("PacketListenerApi")) {
			getLogger().severe("****************************************");
			getLogger().severe("This plugin depends on PacketListenerApi");
			getLogger().severe("Download it here: https://r.spiget.org/2930");
			getLogger().severe("****************************************");
			Bukkit.getPluginManager().disablePlugin(this);
			return;
		}

		packetListener = new PacketListener(this);
		Bukkit.getPluginManager().registerEvents(mapListener = new MapListener(this), this);

		mapManagerInstance = new DefaultMapManager();

		saveDefaultConfig();
		reload();

		PluginCommand command = getCommand("mapmanager");
		CommandHandler commandHandler = new CommandHandler();
		command.setExecutor(commandHandler);
		command.setTabCompleter(commandHandler);

		if (MapManager.Options.ALLOW_VANILLA) {
			getLogger().info("Vanilla Maps are allowed. Trying to discover occupied Map IDs...");

			Set<Short> occupied = new HashSet<>();
			for (short s = 0; s < Short.MAX_VALUE; s++) {
				try {
					MapView view = Bukkit.getMap(s);
					if (view != null) {
						occupied.add(s);
					}
				} catch (Exception e) {
					if (!e.getMessage().toLowerCase().contains("invalid map dimension")) {
						getLogger().log(Level.WARNING, e.getMessage(), e);
					}
				}
			}
			getLogger().info("Found " + occupied.size() + " occupied IDs.");

			for (short s : occupied) {
				getMapManager().registerOccupiedID(s);
			}
			getLogger().fine("These IDs will not be used: " + occupied);
		}

		new Metrics(this);

		SpigetUpdate updater = new SpigetUpdate(this, 19198);
		updater.setUserAgent("MapManager/" + getDescription().getVersion()).setVersionComparator(VersionComparator.SEM_VER_SNAPSHOT);
		updater.checkForUpdate(new UpdateCallback() {
			@Override
			public void updateAvailable(String s, String s1, boolean b) {
				getLogger().info("A new version is available: https://r.spiget.org/19198");
			}

			@Override
			public void upToDate() {
				getLogger().info("Plugin is up-to-date");
			}
		});
	}

	@Override
	public void onDisable() {
		this.packetListener.disable();
	}

	void reload() {
		FileConfiguration config = getConfig();

		ALLOW_VANILLA = config.getBoolean("allowVanilla", ALLOW_VANILLA);
		FORCED_OFFSET = config.getInt("forcedOffset", FORCED_OFFSET);
		CHECK_DUPLICATES = config.getBoolean("checkDuplicates", CHECK_DUPLICATES);
		CACHE_DATA = getConfig().getBoolean("cacheData", CACHE_DATA);
		Sender.DELAY = getConfig().getInt("sender.delay", Sender.DELAY);
		Sender.AMOUNT = getConfig().getInt("sender.amount", Sender.AMOUNT);
		Sender.ALLOW_QUEUE_BYPASS = getConfig().getBoolean("sender.allowQueueBypass", Sender.ALLOW_QUEUE_BYPASS);
	}

	/**
	 * @return The {@link MapManager} instance
	 */
	public MapManager getMapManager() {
		if (mapManagerInstance == null) { throw new IllegalStateException("Manager not yet initialized"); }
		return mapManagerInstance;
	}

	/**
	 * Helper method to find an {@link ItemFrame} by its entity ID
	 *
	 * @param world    {@link World} the frame is located in
	 * @param entityId the frame's entity ID
	 * @return the {@link ItemFrame} or <code>null</code>
	 */
	public static ItemFrame getItemFrameById(World world, int entityId) {
		try {
			if (CraftWorldFieldResolver == null) {
				CraftWorldFieldResolver = new FieldResolver(MapManagerPlugin.obcClassResolver.resolve("CraftWorld"));
			}
			if (WorldFieldResolver == null) {
				WorldFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("World"));
			}
			if (WorldServerFieldResolver == null) {
				WorldServerFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("WorldServer"));
			}
			if (EntityMethodResolver == null) {
				EntityMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("Entity"));
			}

			Object nmsWorld = CraftWorldFieldResolver.resolve("world").get(world);
			Object entitiesById;
			// NOTE: this check can be false, if the v1_14_R1 doesn't exist (stupid java), i.e. in old ReflectionHelper versions
			if (Minecraft.VERSION.newerThan(Minecraft.Version.v1_8_R1)
					&& Minecraft.VERSION.olderThan(Minecraft.Version.v1_14_R1)) { /* seriously?! between 1.8 and 1.14 entitiesyId was moved to World */
				entitiesById = WorldFieldResolver.resolve("entitiesById").get(nmsWorld);
			} else {
				entitiesById = WorldServerFieldResolver.resolve("entitiesById").get(nmsWorld);
			}

			Object entity;
			if (Minecraft.VERSION.olderThan(Minecraft.Version.v1_14_R1)) {// < 1.14 uses IntHashMap
				if (IntHashMapMethodResolver == null) {
					IntHashMapMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("IntHashMap"));
				}

				entity = IntHashMapMethodResolver.resolve(new ResolverQuery("get", int.class)).invoke(entitiesById, entityId);
			} else {// > 1.14 uses Int2ObjectMap which implements Map
				entity = ((Map) entitiesById).get(entityId);
			}

			if (entity == null) { return null; }
			Entity bukkitEntity = (Entity) EntityMethodResolver.resolve("getBukkitEntity").invoke(entity);
			if (bukkitEntity != null && EntityType.ITEM_FRAME == bukkitEntity.getType()) {
				return (ItemFrame) bukkitEntity;
			}

			//				for (ItemFrame itemFrame : world.getEntitiesByClass(ItemFrame.class)) {
			//					if (itemFrame.getEntityId() == entityId) {
			//						return itemFrame;
			//					}
			//				}
			//				return null;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}