package net.galaxygaming.dispenser.event;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import net.galaxygaming.dispenser.GameDispenser;
import net.galaxygaming.dispenser.game.Game;
import net.galaxygaming.dispenser.game.GameManager;
import net.galaxygaming.dispenser.game.GameType;

import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.plugin.EventExecutor;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class EventManager {
    
    /** Singleton instance */
    private static final EventManager instance = new EventManager();
    
    private final Map<GameType, Set<Listener>> listeners;
    private GameDispenser plugin;

    private EventManager() {
        listeners = Maps.newHashMap();
    }
    
    public void setup(GameDispenser plugin) {
        this.plugin = plugin;
        plugin.getServer().getPluginManager().registerEvents(new Events(), plugin);
    }
    
    @Override
    public EventManager clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
    
    public void registerListener(Listener listener, GameType type) {
        Set<Method> methods;
        try {
            Method[] publicMethods = listener.getClass().getMethods();
            methods = new HashSet<Method>(publicMethods.length, Float.MAX_VALUE);
            for (Method method : publicMethods) {
                methods.add(method);
            }
            
            for (Method method : listener.getClass().getDeclaredMethods()) {
                methods.add(method);
            }
        } catch (NoClassDefFoundError e) {
            plugin.getLogger().severe(type.toString() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist.");
            return;
        }
        
        for (final Method method : methods) {
            final EventHandler eh = method.getAnnotation(EventHandler.class);
            if (eh == null) continue;
            final Class<?> checkClass = method.getParameterTypes()[0];
            final Class<? extends Game> gameClass;
            if (method.getParameterTypes().length == 2 && Game.class.isAssignableFrom(method.getParameterTypes()[1]) && (EntityEvent.class.isAssignableFrom(checkClass) || PlayerEvent.class.isAssignableFrom(checkClass))) {
                gameClass = method.getParameterTypes()[1].asSubclass(Game.class);
            } else if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass)) {
                plugin.getLogger().severe(type.toString() + " attempted to register an invalid EventHandler method signature '" + method.toGenericString() + "' in " + listener.getClass());
                continue;
            } else {
                gameClass = null;
            }

            final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class);
            method.setAccessible(true);
            
            EventExecutor executor = new EventExecutor() {
                public void execute(Listener listener, Event event) throws EventException {
                    try {
                        if (!eventClass.isAssignableFrom(event.getClass())) {
                            return;
                        }
                        
                        if (gameClass != null) {
                            Player player = null;
                            if (event instanceof PlayerEvent) {
                                player = ((PlayerEvent) event).getPlayer();
                            } else if (event instanceof EntityEvent) {
                                Entity entity = ((EntityEvent) event).getEntity();
                                if (entity instanceof Player) {
                                    player = (Player) entity;
                                }
                            }
                            
                            if (player == null) {
                                return;
                            }
                            
                            Game game = GameManager.getInstance().getGameForPlayer(player, gameClass);
                            if (game == null)
                                return;
                            
                            method.invoke(listener, event, gameClass.cast(game));
                        } else {
                            method.invoke(listener, event);
                        }
                    } catch (InvocationTargetException e) {
                        throw new EventException(e.getCause());
                    } catch (Throwable e) {
                        throw new EventException(e);
                    }
                }
            };
            
            Set<Listener> listenerSet = listeners.get(type);
            if (listenerSet == null) {
                listenerSet = Sets.newHashSet();
                listeners.put(type, listenerSet);
            }
            
            listenerSet.add(listener);
            plugin.getServer().getPluginManager().registerEvent(eventClass, listener, eh.priority(), executor, plugin, eh.ignoreCancelled());
        }          
    }
    
    public void unregisterListener(Listener listener) {
        HandlerList.unregisterAll(listener);
        for (Entry<GameType, Set<Listener>> entry : listeners.entrySet()) {
            if (entry.getValue() != null) {
                entry.getValue().remove(listener);
            }
        }
    }
    
    public void unregisterListeners(GameType type) {
        Set<Listener> listenerSet = listeners.get(type);
        if (listenerSet != null) {
            for (Listener listener : listenerSet) {
                HandlerList.unregisterAll(listener);
            }
        }
        listeners.remove(type);
    }
    
    public static EventManager getInstance() {
        return instance;
    }   
}
@@ -658,24 +658,213 @@
 t.Listener;%0A
+import org.bukkit.event.block.BlockBreakEvent;%0Aimport org.bukkit.event.block.BlockDamageEvent;%0Aimport org.bukkit.event.block.BlockPlaceEvent;%0Aimport org.bukkit.event.block.SignChangeEvent;%0A
 import org.b
@@ -891,24 +891,24 @@
 ntityEvent;%0A
-
 import org.b
@@ -2840,16 +2840,37 @@
 s()%5B1%5D) 
+%0A                    
 && (Enti
@@ -2961,16 +2961,289 @@
 ckClass)
+%0A                            %7C%7C BlockBreakEvent.class.isAssignableFrom(checkClass) %7C%7C BlockPlaceEvent.class.isAssignableFrom(checkClass)%0A                            %7C%7C BlockDamageEvent.class.isAssignableFrom(checkClass) %7C%7C SignChangeEvent.class.isAssignableFrom(checkClass)
 )) %7B%0A   
@@ -4720,16 +4720,16 @@
 entity;%0A
-
         
@@ -4746,32 +4746,654 @@
               %7D%0A
+                            %7D else if (event instanceof BlockBreakEvent) %7B%0A                                player = ((BlockBreakEvent) event).getPlayer();%0A                            %7D else if (event instanceof BlockPlaceEvent) %7B%0A                                player = ((BlockPlaceEvent) event).getPlayer();%0A                            %7D else if (event instanceof BlockDamageEvent) %7B%0A                                player = ((BlockDamageEvent) event).getPlayer();%0A                            %7D else if (event instanceof SignChangeEvent) %7B%0A                                player = ((SignChangeEvent) event).getPlayer();%0A
                 

add game param support for more events