package cz.jzitnik.game.threads;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.ThreadRegistry;
import cz.jzitnik.game.entities.Player;
import cz.jzitnik.game.stats.Stats;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.extern.slf4j.Slf4j;

import org.jline.terminal.Terminal;
import org.reflections.Reflections;

/**
 * Scans and registers all threads annotated with {@link ThreadRegistry} and starts them when requested.
 */
@Slf4j
public class ThreadProvider {
    private final Game game;
    private final ScreenRenderer screenRenderer;
    private final Terminal terminal;
    private final boolean[] isRunning;
    private final List<Thread> list = new ArrayList<>();

    /**
     * Starts all registered threads.
     */
    public void start() {
        log.info("Loading all the threads");
        for (Thread thread : list) {
            thread.start();
        }
    }

    /**
     * Constructs a ThreadProvider and registers all eligible threads.
     *
     * @param game           The game instance.
     * @param screenRenderer The screen renderer.
     * @param terminal       The terminal.
     * @param isRunning      A shared boolean array for controlling thread execution.
     */
    public ThreadProvider(Game game, ScreenRenderer screenRenderer, Terminal terminal, boolean[] isRunning) {
        this.game = game;
        this.screenRenderer = screenRenderer;
        this.terminal = terminal;
        this.isRunning = isRunning;
        registerHandlers();
    }

    /**
     * Scans the package for classes annotated with {@link ThreadRegistry} and instantiates valid threads.
     * Matches constructors using known parameter types (e.g., Game, Stats, Terminal, etc.).
     */
    private void registerHandlers() {
        Reflections reflections = new Reflections("cz.jzitnik.game.threads");
        Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(ThreadRegistry.class);

        for (Class<?> clazz : handlerClasses) {
            if (Thread.class.isAssignableFrom(clazz)) {
                try {
                    for (var constructor : clazz.getDeclaredConstructors()) {
                        var paramTypes = constructor.getParameterTypes();
                        var params = new Object[paramTypes.length];
                        boolean suitable = true;

                        for (int i = 0; i < paramTypes.length; i++) {
                            Class<?> type = paramTypes[i];
                            if (type == Game.class)
                                params[i] = game;
                            else if (type == Stats.class)
                                params[i] = game.getStats();
                            else if (type == ScreenRenderer.class)
                                params[i] = screenRenderer;
                            else if (type == Terminal.class)
                                params[i] = terminal;
                            else if (type == boolean[].class)
                                params[i] = isRunning;
                            else if (type == Player.class)
                                params[i] = game.getPlayer();
                            else {
                                suitable = false;
                                break;
                            }
                        }

                        if (suitable) {
                            constructor.setAccessible(true);
                            Thread instance = (Thread) constructor.newInstance(params);
                            list.add(instance);
                            break; // Found a matching constructor, go to next class
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
