package cz.jzitnik.game.handlers.events;

import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.MineEventHandler;
import cz.jzitnik.game.annotations.PlaceEventHandler;
import cz.jzitnik.tui.ScreenRenderer;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ConfigurationBuilder;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class EventHandlerProvider {
    @FunctionalInterface
    public interface Function<T, U, V, W> {
        void accept(T t, U u, V v, W w);
    }

    public final List<Function<ScreenRenderer, Game, Integer, Integer>> mineHandlers = new ArrayList<>();
    public final List<Function<ScreenRenderer, Game, Integer, Integer>> placeHandlers = new ArrayList<>();

    public EventHandlerProvider() {
        registerHandlers();
    }

    public void handlePlace(ScreenRenderer screenRenderer, Game game, int x, int y) {
        for (Function<ScreenRenderer, Game, Integer, Integer> handler : placeHandlers) {
            handler.accept(screenRenderer, game, x, y);
        }
    }

    public void handleMine(ScreenRenderer screenRenderer, Game game, int x, int y) {
        for (Function<ScreenRenderer, Game, Integer, Integer> handler : mineHandlers) {
            handler.accept(screenRenderer, game, x, y);
        }
    }

    private void registerHandlers() {
        Reflections reflections = new Reflections(
                new ConfigurationBuilder()
                        .forPackage("cz.jzitnik.game.handlers.events.handlers")
                        .addScanners(Scanners.MethodsAnnotated)
        );
        Set<Method> mineHandlers = reflections.getMethodsAnnotatedWith(MineEventHandler.class);

        for (Method method : mineHandlers) {
            if (method.getParameterCount() == 4 &&
                    method.getParameterTypes()[0] == ScreenRenderer.class &&
                    method.getParameterTypes()[1] == Game.class &&
                    method.getParameterTypes()[2] == int.class &&
                    method.getParameterTypes()[3] == int.class &&
                    method.getReturnType() == void.class) {
                try {
                    Object instance = method.getDeclaringClass().getDeclaredConstructor().newInstance();
                    Function<ScreenRenderer, Game, Integer, Integer> handler = (screenRenderer, game, x, y) -> {
                        try {
                            method.invoke(instance, screenRenderer, game, x, y);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    };
                    this.mineHandlers.add(handler);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        Set<Method> placeHandlers = reflections.getMethodsAnnotatedWith(PlaceEventHandler.class);
        for (Method method : placeHandlers) {
            if (method.getParameterCount() == 4 &&
                    method.getParameterTypes()[0] == ScreenRenderer.class &&
                    method.getParameterTypes()[1] == Game.class &&
                    method.getParameterTypes()[2] == int.class &&
                    method.getParameterTypes()[3] == int.class &&
                    method.getReturnType() == void.class) {

                try {
                    Object instance = method.getDeclaringClass().getDeclaredConstructor().newInstance();
                    Function<ScreenRenderer, Game, Integer, Integer> handler = (screenRenderer, game, x, y) -> {
                        try {
                            method.invoke(instance, screenRenderer, game, x, y);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    };
                    this.placeHandlers.add(handler);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
