package me.tom.sparse.spigot.chat.menu;

import me.tom.sparse.spigot.chat.protocol.ChatPacketInterceptor;
import me.tom.sparse.spigot.chat.protocol.PlayerChatIntercept;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.map.MapFont;
import org.bukkit.map.MinecraftFont;
import org.bukkit.plugin.Plugin;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

public final class ChatMenuAPI
{
	private static final Map<String, ChatMenu> MENUS        = new ConcurrentHashMap<>();
	private static final Map<Player, ChatMenu> OPENED_MENUS = new ConcurrentHashMap<>();
	
	private static Plugin                plugin;
	private static ChatPacketInterceptor interceptor;
	
	private ChatMenuAPI() {}
	
	/**
	 * @param player the player whose current menu should be returned
	 * @return the menu the player currently has open, or {@code null} if no menu is open.
	 */
	@Nullable
	public static ChatMenu getCurrentMenu(@NotNull Player player)
	{
		return OPENED_MENUS.get(player);
	}
	
	/**
	 * @param player the player whose current menu should be returned
	 * @param menu   the menu to set as current, or {@code null} if you want to close the current menu.
	 */
	public static void setCurrentMenu(@NotNull Player player, @Nullable ChatMenu menu)
	{
		ChatMenu old = OPENED_MENUS.remove(player);
		if(old != null && old != menu) old.onClosed(player);
		if(menu != null) OPENED_MENUS.put(player, menu);
	}

	@NotNull
	static String registerMenu(ChatMenu menu)
	{
		String id = generateIdentifier();
		MENUS.put(id, menu);
		return id;
	}

	static void unregisterMenu(@NotNull ChatMenu menu)
	{
		MENUS.values().remove(menu);
	}

	@NotNull
	private static String generateIdentifier()
	{
		String result = null;
		while(result == null || MENUS.containsKey(result))
		{
			int[] ints = ThreadLocalRandom.current().ints(4, 0x2700, 0x27BF).toArray();
			result = new String(ints, 0, ints.length);
		}
		
		return result;
	}
	
	/**
	 * Gets the current {@link PlayerChatIntercept} associated with the provided player.
	 * If the player does not have one, it will be created.
	 *
	 * @param player the player to get/create the {@link PlayerChatIntercept} for
	 * @return the {@link PlayerChatIntercept} associated with the provided player
	 */
	@NotNull
	public static PlayerChatIntercept getChatIntercept(@NotNull Player player)
	{
		return interceptor.getChat(player);
	}
	
	/**
	 * Calculates the width of the provided text.
	 * <br>
	 * Works with formatting codes such as bold.
	 *
	 * @param text the text to calculate the width for
	 * @return the number of pixels in chat the text takes up
	 */
	public static int getWidth(@NotNull String text)
	{
		if(text.contains("\n"))
			throw new IllegalArgumentException("Cannot get width of text containing newline");
		
		int width = 0;
		
		boolean isBold = false;
		
		char[] chars = text.toCharArray();
		for(int i = 0; i < chars.length; i++)
		{
			char c = chars[i];
			int charWidth = getCharacterWidth(c);
			
			if(c == ChatColor.COLOR_CHAR && i < chars.length - 1)
			{
				c = chars[++i];
				
				if(c != 'l' && c != 'L')
				{
					if(c == 'r' || c == 'R')
					{
						isBold = false;
					}
				}else
				{
					isBold = true;
				}
				
				charWidth = 0;
			}
			
			if(isBold && c != ' ' && charWidth > 0)
			{
				width++;
			}
			
			width += charWidth;
		}
		
		return width;
	}
	
	/**
	 * @param c the character to get the width of
	 * @return the width of the provided character in pixels
	 */
	public static int getCharacterWidth(char c)
	{
		if(c >= '\u2588' && c <= '\u258F')
		{
			return ('\u258F' - c) + 2;
		}
		
		switch(c)
		{
			case ' ':
				return 4;
			case '\u2714':
				return 8;
			case '\u2718':
				return 7;
			default:
				MapFont.CharacterSprite mcChar = MinecraftFont.Font.getChar(c);
				if(mcChar != null)
					return mcChar.getWidth() + 1;
				return 0;
		}
	}
	
	static ChatMenu getMenu(String id)
	{
		return MENUS.get(id);
	}
	
	/**
	 * <b>This method should only be called by you if you're including this API inside your plugin.</b>
	 * <br>
	 * Initializes all the necessary things for the ChatMenuAPI to function. This method can only be called once.
	 *
	 * @param plugin the plugin to initialize everything with, including listeners and scheduled tasks
	 */
	public static void init(@NotNull Plugin plugin)
	{
		if(ChatMenuAPI.plugin != null)
			return;
		
		ChatMenuAPI.plugin = plugin;
//		Bukkit.getPluginCommand("cmapi").setExecutor(new CMCommand());
		CMCommand.setLoggerFilter();
		new CMListener(plugin);
		
		try
		{
			interceptor = new ChatPacketInterceptor(plugin);
		}catch(ReflectiveOperationException e)
		{
			plugin.getLogger().severe("Unable to create ChatPacketInterceptor! The ChatMenuAPI will not function properly!");
			e.printStackTrace();
		}
	}
	
	/**
	 * <b>This method should only be called by you if you're including this API inside your plugin.</b>
	 * <br>
	 * Disables everything necessary for this API to be reloaded properly without restarting.
	 */
	public static void disable()
	{
		if(plugin == null)
			return;
		
		CMCommand.restoreLoggerFilter();
		plugin = null;
		interceptor.disable();
	}
}