public enum Preference
{ 
	HIGHEST, 
	HIGH, 
	NORMAL, 
	LOW; 
}
 public abstract class TabAPI
{
	static final Class<? extends Plugin> parent = TinyProtocolAPI.class;	
	static final HashMap<UUID, TabHolder> holder = new HashMap<>();
	static final TinyProtocol manager = TinyProtocolAPI.getTinyProtocol();
	static final HashMap<Class<?>, Preference> priorities = new HashMap<>();

	
	protected static TabHolder holder(Player a)
	{
		UUID b = a.getUniqueId();
		if(!holder.containsKey(b))
			holder.put(b, new TabHolder());
		return holder.get(b);
	}
	public static void refresh(Class<? extends Plugin> plugin,
			Player player, Preference priority, SettingAPI<TabModify> api)
	{
    	if(plugin == null) throw new IllegalArgumentException("Class of plugin cannot be null !");
    	if(player == null) throw new IllegalArgumentException("Player cannot be null !");
    	if(priority == null) throw new IllegalArgumentException("Preference cannot be null !");
    	if(api == null) throw new IllegalArgumentException("TabModify cannot be null !");

    	TabHolder hold = holder(player);
		if(hold.priority.compareTo(priority) < 0)
			return;
        if (!hold.plugin.isAssignableFrom(plugin)) {
        }
		api.call(hold);
		manager.sendAbstractPacket(player, hold.update());
		hold.takeOver(plugin, priority);
	}
	
	public static void refresh(Class<? extends Plugin> plugin, Player player, SettingAPI<TabModify> modify) {
		Preference priority = priorities.getOrDefault(plugin, Preference.LOW);
		refresh(plugin, player, priority, modify);
	}
	
	public static void remove(Class<? extends Plugin> plugin, Player player)
	{
    	if(plugin == null) throw new IllegalArgumentException("Class of plugin cannot be null !");
    	if(player == null) throw new IllegalArgumentException("Player cannot be null !");
    	final TabHolder hold = holder(player);
		Preference priority = priorities.getOrDefault(plugin, Preference.LOW);
    	if(hold.plugin.isAssignableFrom(parent) || hold.plugin.isAssignableFrom(plugin) || hold.priority.compareTo(priority) < 0)
		manager.sendAbstractPacket(player, hold.remove());
	}
	
    public static void setDefaultPreference(Class<? extends Plugin> plugin, Preference priority) 
    {
    	if(plugin == null) throw new IllegalArgumentException("Class of plugin cannot be null !");
    	if(priority == null) throw new IllegalArgumentException("Preference cannot be null !");
    	priorities.put(plugin, priority);
    }
    
    public static boolean hasAPI(Player player) 
    {
    	if(player == null) throw new IllegalArgumentException("Player cannot be null !");
        return holder(player).exist;
    }
}
 public class TabListener implements Listener 
{
	
	@EventHandler
	void quit(PlayerQuitEvent e)
	{
		TabAPI.remove(TinyProtocolAPI.class, e.getPlayer());
	}
	
	@EventHandler
	void disable(PluginDisableEvent e)
	{
		if(!e.getPlugin().getClass().equals(TinyProtocolAPI.class)) return;
		for(Player player : Bukkit.getServer().getOnlinePlayers())
			TabAPI.remove(TinyProtocolAPI.class, player);
	}
}
 public abstract class TitleAPI
{
	
	static final TinyProtocol fb = TinyProtocolAPI.getTinyProtocol();

	public static void title(Player player, String title, int start, int stay, int end)
	{
    	if(player == null) throw new IllegalArgumentException("Player cannot be null !");
		PacketTitle a = new PacketTitle(start, stay, end);
		PacketTitle b = new PacketTitle(TITLE, title);
		fb.sendAbstractPacket(player, a, b);
	}
	
	public static void subtitle(Player player, String subtitle, int start, int stay, int end)
	{ 
    	if(player == null) throw new IllegalArgumentException("Player cannot be null !");
		PacketTitle a = new PacketTitle(start, stay, end);
		PacketTitle b = new PacketTitle(TITLE, "");
		PacketTitle c = new PacketTitle(SUBTITLE, subtitle);
		fb.sendAbstractPacket(player, a, b, c);
	}
	
	public static void subtitle(Player player, String subtitle)
	{   if(player == null) throw new IllegalArgumentException("Player cannot be null !");
		fb.sendAbstractPacket(player, new PacketTitle(SUBTITLE, subtitle)); 
	}
	
	public static void broadcast(Player player, String title, String subtitle, int start, int stay, int end)
	{
    	if(player == null) throw new IllegalArgumentException("Player cannot be null !");
		PacketTitle a = new PacketTitle(start, stay, end);
		PacketTitle b = new PacketTitle(TITLE, title);
		PacketTitle c = new PacketTitle(SUBTITLE, subtitle);
		fb.sendAbstractPacket(player, a, b, c);
	}
	
	public static void clear(Player player)
	{    	
		if(player == null) throw new IllegalArgumentException("Player cannot be null !");
		fb.sendAbstractPacket(player, new PacketTitle(CLEAR, ""));
	}
	
	public static void hotbar(Player player, String msg)
	{     	
		if(player == null) throw new IllegalArgumentException("Player cannot be null !");
		new PacketChat(msg, (byte) 2); 
	}
}
 public class ClassAccess
{
	public final Accessor accessor;
	public final Access access;
	
	public ClassAccess(String path)
	{accessor = Accessor.get(path);
	access = accessor.access;}
	
	public ClassAccess(Class<?> path) 
	{accessor = Accessor.get(path);
	access = accessor.access;}

	//constructor
	@SuppressWarnings("unchecked")
	public <T> T newInstance() 
	{ return (T) access.newInstance(); }
	
	@SuppressWarnings("unchecked")
	public <T> T newInstance(int index, Object... args) 
	{ return (T) access.newInstance(index, args); }
	
	public Class<?>[][] getConstructortParameterTypes() 
	{ return accessor.getConstructorParameterTypes(); }
	
	//field
	public void set(Object instance, int index, Object value) 
	{ access.set(instance, index, value); }
	
	public void set(Object instance, String fieldName, Object value)
	{ set(instance, getIndexOfField(fieldName), value);}
	
	public void set(Object instance, Class<?> fieldType, int index, Object value) 
	{ set(instance, getIndexOfField(fieldType, index), value); }
	
	public void set(Object instance, String fieldType, int index, Object value)
	{ set(instance, getCanonicalClass(fieldType), index, value);}

	public void setBoolean(Object instance, int index, boolean value) 
	{ access.setBoolean(instance, index, value); }

	public void setByte(Object instance, int index, byte value) 
	{ access.setByte(instance, index, value); }

	public void setShort(Object instance, int index, short value) 
	{ access.setShort(instance, index, value); }

	public void setInt(Object instance, int index, int value) 
	{ access.setInt(instance, index, value); }

	public void setLong(Object instance, int index, long value) 
	{ access.setLong(instance, index, value); }

	public void setDouble(Object instance, int index, double value) 
	{ access.setDouble(instance, index, value); }

	public void setFloat(Object instance, int index, float value)
	{ access.setFloat(instance, index, value);}

	public void setChar(Object instance, int index, char value)
	{ access.setChar(instance, index, value); }
		
	@SuppressWarnings("unchecked")
	public <T> T get(Object instance, int index) 
	{ return (T) access.get(instance, index); }
	
	public <T> T get(Object instance, String fieldName)
	{ return get(instance, getIndexOfField(fieldName)); }
	
	public <T> T get(Object instance, Class<?> fieldType, int index) 
	{ return get(instance, getIndexOfField(fieldType, index)); }
	
	@SuppressWarnings("unchecked")
	public <T> T get(Object instance, String fieldType, int index)
	{ return (T) get(instance, getCanonicalClass(fieldType), index); }

	public boolean getBoolean(Object instance, int index) 
	{ return access.getBoolean(instance, index); }

	public byte getByte(Object instance, int index) 
	{ return access.getByte(instance, index); }

	public short getShort(Object instance, int index)
	{ return access.getShort(instance, index); }

	public int getInt(Object instance, int index)
	{ return access.getInt(instance, index); }

	public long getLong(Object instance, int index)
	{ return access.getLong(instance, index); }

	public double getDouble(Object instance, int index) 
	{ return access.getDouble(instance, index); }

	public float getFloat(Object instance, int index)
	{ return access.getFloat(instance, index); }
	
	public char getChar(Object instance, int index)
	{ return access.getChar(instance, index); }

	public String getString(Object instance, int index)
	{ return get(instance, index); }
	
	public Enum<?> getEnum(int index)
	{ return get(null, index); }
	
	public Class<?>[] getFieldTypes() 
	{ return accessor.getFieldTypes(); }
	
	public String[] getFieldNames()
	{ return accessor.getFieldNames(); }

	public String[] getEnumNames()
	{ return accessor.getEnumNames(); }

	public int getFieldCount() 
	{ return accessor.getFieldCount(); }

	public int getEnumCount() 
	{ return accessor.getEnumCount();}
	
	//method
    @SuppressWarnings("unchecked")
	public <T> T invoke(Object object, int index, Object... args)
    { return (T) access.invoke(object, index, args); }
    
	@SuppressWarnings("unchecked")
	public <T> T invoke (Object object, String methodName, int index, Object... args) 
	{ return (T) invoke(object, getIndexOfMethod(methodName, index), args); }
	
	@SuppressWarnings("unchecked")
	public <T> T invoke (Object object, String methodName, Object... args) 
	{ return (T) invoke(object, getIndexOfMethod(methodName, args == null ? 0 : args.length), args); }
	
	public <T> T invoke (Object object, Class<?> type, Class<?>[] paramsTypes, Object... args) 
	{ return invoke(object, getIndexOfMethod(type, paramsTypes), args);}
	
	public <T> T invoke (Object object, Class<?> type, String[] paramsTypes, Object... args)
	{ return invoke(object, getIndexOfMethod(type, getCanonicalClasses(paramsTypes)), args); }
	
	public <T> T invoke (Object object, String type, Class<?>[] paramsTypes, Object... args) 
	{ return invoke(object, getIndexOfMethod(getCanonicalClass(type), paramsTypes), args); }
	
	public <T> T invoke (Object object, String type, String[] paramsTypes, Object... args)
	{ return invoke(object, getIndexOfMethod(getCanonicalClass(type), getCanonicalClasses(paramsTypes)), args); }
	
	public String[] getMethodNames () 
	{ return accessor.getMethodNames(); }

	public Class<?>[][] getMethodParameterTypes () 
	{ return accessor.getParameterTypes();  }

	public Class<?>[] getMethodTypes ()
	{ return accessor.getMethodTypes(); }
	
	//index
	int getIndexOfField(String fieldName) 
	{ return accessor.indexOfField(fieldName); }
	
	int getIndexOfField(Class<?> fieldType, int index)
	{ return accessor.indexOfField(fieldType, index); }
	
	int getIndexOfMethod (String methodName, int index)
	{ return accessor.indexOfMethod(methodName, index); }
	
	int getIndexOfMethod (Class<?> type, Class<?>... paramTypes) 
	{ return accessor.indexOfMethod(type, paramTypes); }
	
	//others
	public Class<?> getUsedClass()
	{ return accessor.getClassType(); }
	
	public Class<?> getCanonicalClass(String path) 
	{ return accessor.getCanonicalClass(path); }
	
	public Class<?>[] getCanonicalClasses(String... paths) 
	{
		int size = paths.length;
		Class<?>[] classes = new Class<?>[size];
		for(int i = 0; i < size; i++)
			classes[i] = getCanonicalClass(paths[i]);
		return classes;
	}
	
	@Override
	public String toString()
	{ return accessor.toString(); }

}
 public abstract class PacketEvent implements Cancellable
{
	public final Player player;
	public final Channel channel;
	public final Object handle;
	private boolean c;
	
	public PacketEvent(Player player, Channel channel, Object handle)
	{
		this.player = player;
		this.channel = channel;
		this.handle = handle;
		c = false;
	}
	
	public Player getPlayer() {
		return player;
	}
	
	public Channel getChannel() {
		return channel;
	}
	 
	public Object getPacket() {
		return handle;
	}

	@Override
	public boolean isCancelled() {
		return c;
	}

	@Override
	public void setCancelled(boolean cancel) {
		c = cancel;
	}
	
	abstract public PacketHandlerList getPacketHandlerList();
}
 public interface PacketExecutor
{
	void call(PacketListener listen, PacketEvent event) throws Exception;
} public class PacketHandlerList
{
    private static ArrayList<PacketHandlerList> allLists = Lists.newArrayList();
    private final EnumMap<PacketPriority, ArrayList<RegisteredPacket>> handlerslots = Maps.newEnumMap(PacketPriority.class);
    private volatile RegisteredPacket[] handlers = null;

    public PacketHandlerList ()
    {
        PacketPriority[] priority = PacketPriority.values();
        for (int i = 0; i < priority.length; i++)
            this.handlerslots.put(priority[i], new ArrayList<>());
        synchronized (allLists)
        {allLists.add(this);}
    }

   	protected static void unregisterAll ()
    {
        synchronized (allLists)
        {
            for (PacketHandlerList handler : allLists)
            {
                synchronized (handler)
                {
                    Collection<ArrayList<RegisteredPacket>> list = handler.handlerslots.values();
                    for (ArrayList<RegisteredPacket> packet : list)
                        packet.clear();
                }
                handler.handlers = null;
            }
        }
    }

    public static void unregisterAll (Plugin plugin)
    {
        synchronized (allLists)
        {
            for (PacketHandlerList packet : allLists)
                packet.unregister(plugin);
        }
    }


    public synchronized void register (RegisteredPacket listener)
    {
        if (handlerslots.get(listener.getPriority()).contains(listener))
        	return;
         handlers = null;
         handlerslots.get(listener.getPriority()).add(listener);
    }

    public void registerAll (Collection<RegisteredPacket> listeners)
    {listeners.stream().forEach(this::register);}

    public synchronized void unregister (Plugin plugin)
    {
        boolean changed = false;
        for (List<RegisteredPacket> priority : handlerslots.values())
        {
            ListIterator<RegisteredPacket> i = priority.listIterator();
            while (i.hasNext())
            {
                if (i.next().getPlugin().equals(plugin))
                {
                    i.remove();
                    changed = true;
                }
            }
        }
        if (changed) this.handlers = null;
    }

    public RegisteredPacket[] getRegisteredPacketListeners ()
    {
        while (true)
        {
            RegisteredPacket[] handlers = this.handlers;
            if (this.handlers != null) return handlers;
            
            ArrayList<RegisteredPacket> entries = Lists.newArrayList();
            this.handlerslots.entrySet().stream().forEach((map) -> entries.addAll(map.getValue()));
            return this.handlers = entries.toArray(new RegisteredPacket[entries.size()]);
        }
    }
    
	public static RegisteredPacket[] getAllRegisteredPacketListeners ()
    {
		int size = allLists.size();
        RegisteredPacket[] listeners = new RegisteredPacket[size];
        synchronized (allLists)
        {
        	int i = 0;
            for (PacketHandlerList handler : allLists)
                for(RegisteredPacket packet : handler.getRegisteredPacketListeners())
                	listeners[i++] = packet;
        }
        return listeners;
    }
}
 public interface PacketListener {}
 public class RegisteredPacket
{
	private final PacketType type;
    private final PacketListener listener;
    private final PacketExecutor executor;
    private final PacketPriority priority;
    private final Plugin plugin;
    private final boolean cancelled;
    private final ClassAccess access;

    public RegisteredPacket (PacketType a, PacketListener b, PacketExecutor c,
    		PacketPriority d, Plugin e, boolean f,
    		ClassAccess g)
    {
    	this.type = a;
        this.listener = b;
        this.executor = c;
        this.priority = d;
        this.plugin = e;
        this.cancelled = f;
        this.access = g;

    }

    public Class<?> getParentClass () { return listener.getClass(); }
    
    public ClassAccess getAccessor () { return access; }
    
    public PacketType getType ()
    { return type; }
    
    public PacketListener getListener ()
    { return listener; }

    public Plugin getPlugin ()
    { return plugin; }

    public PacketPriority getPriority ()
    {return priority;}

	public void callEvent (PacketEvent event) throws Exception
    {
        if (!event.isCancelled() || !isIgnoringCancelled())
            executor.call(listener, event);
    }

    public boolean isIgnoringCancelled ()
    {return cancelled;}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.io.FileUtils;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import com.avaje.ebeaninternal.server.lib.util.NotFoundException;
import com.gmail.zahusek.tinyprotocolapi.api.Preference;
import com.gmail.zahusek.tinyprotocolapi.api.tab.TabAPI;
import com.gmail.zahusek.tinyprotocolapi.api.tab.TabListener;
import com.gmail.zahusek.tinyprotocolapi.api.title.TitleAPI;
import com.gmail.zahusek.tinyprotocolapi.asm.reflection.ClassAccess;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketEvent;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketExecutor;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketHandler;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketHandlerList;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketID;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketListener;
import com.gmail.zahusek.tinyprotocolapi.listener.RegisteredPacket;
import com.google.common.collect.Maps;
import static org.bukkit.Bukkit.getBukkitVersion;

				log("&cUnexpected problem: Java encountered a problem when copying a file - &cused default file");
			}
			finally 
			{
				try { d.close();
				} catch (IOException e) {}
			}
		}
				
		FileInputStream e = null;
		try {
			e = new FileInputStream(c);
			properties.load(e);
		} catch (FileNotFoundException z) {
			throw new NotFoundException("File " + config + " is not exist !");
		} catch (IOException z) {
			log("&cUnexpected problem: TinyProtocolAPI could not loaded default file - Where is Settings.properties ? ;o");
			a.disablePlugin(this);
			return;
		}
		finally
		{
			try { e.close();
			} catch (IOException e1) {}
		}
			
		manager = new TinyProtocol();
		plugin = getPlugin(TinyProtocolAPI.class);
		
		a.registerEvents(new TabListener(), this);
		
		GameMode f = getServer().getDefaultGameMode();
		ChatColor[] g = ChatColor.values();
		Random h = new Random();
		if(getProperties().getProperty("testmode", "false").equals("true"))
		{
			new BukkitRunnable() 
			{
				
				@Override
				public void run() {
					for(Player a : getServer().getOnlinePlayers())
					{
						if(a.isOp() || a.isWhitelisted() || (a.getGameMode() != f && f == GameMode.CREATIVE))
						{
							TitleAPI.broadcast(a, "§eBETA §bTinyProtocolAPI §54.0", g[h.nextInt(g.length)] 
									+ "It is test mode !", 20*5, 20*5, 20*5);
							TabAPI.refresh(TinyProtocolAPI.class, a, Preference.LOW, holder ->
							{
								holder.setTexture("MHF_Exclamation");
								holder.setSignal(99999);
								holder.set(Arrays.asList("§9Last date refresh:", 
										String.format("§3%1$tF §a%1$tT", 
										System.currentTimeMillis())), 
										Arrays.asList("§eBETA §bTinyProtocolAPI §54.0"));
								for (int x = 0; x < 4; x++) 
								{
									for (int y = 0; y < 20; y++) 
									{
										if(y%2 == 0)
											holder.setMessage(x, x%2 == 0 ? y : y+1, 
													g[h.nextInt(g.length)] + "It is test mode !");
									}
								}
							});
						}
					}
				}
			}.runTaskTimerAsynchronously(this, 0L, 20*15);
		}
	}
	
	@Override
	public void onDisable() {
		manager.close();
	}
	
	public static TinyProtocol getTinyProtocol()
	{ return manager; }
	
	public static TinyProtocolAPI getTinyProtocolAPI()
	{ return plugin; }
	
	private static Properties getProperties()
	{ return properties; }
	
	
	boolean compatible()
	{
		int a = 0, b = 0, c = 0;
		e:for(char h : getBukkitVersion().toCharArray())
		{
			switch(h)
			{
				case '-': 
					break e;
				case '.':
					a++;
					break;
				default:
					if(a == 1)
						b += (h + 2) % 10;
					else if(a == 2)
						c += (h + 2) % 10;
					else 
						break;
			}
		}
		if(b < 8) 
			return false;
		if(b == 8 && c < 4)
			return false;
		return true;
	}
    
    protected void log(String a)
    {
    	a = ChatColor.translateAlternateColorCodes('&', a);
    	getServer().getConsoleSender().sendMessage(a);}
    

    static Map<Class<?>, Set<RegisteredPacket>> registered(PacketListener a, Plugin b) {
