package me.km.snuviscript;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import me.hammerle.snuviscript.code.ISnuviLogger;
import me.hammerle.snuviscript.code.ISnuviScheduler;
import me.hammerle.snuviscript.code.Script;
import me.hammerle.snuviscript.code.ScriptManager;
import me.km.events.CustomEventCaller;
import net.minecraft.entity.player.PlayerEntity;

public class Scripts {
    private final ScriptManager manager;
    private final ISnuviLogger logger;
    private CustomEventCaller cec = null;

    private final HashMap<UUID, Integer> playerScript = new HashMap<>();
    private final HashMap<Integer, List<UUID>> registeredPlayers = new HashMap<>();
    private final HashSet<String> scriptCommands = new HashSet<>();

    private final Limits entityLimits = new Limits();

    public Scripts(ISnuviLogger logger, ISnuviScheduler scheduler) {
        this.logger = logger;
        manager = new ScriptManager(logger, scheduler);
    }

    public void setCustomEventCaller(CustomEventCaller cec) {
        this.cec = cec;
    }

    public ScriptManager getScriptManager() {
        return manager;
    }

    public ISnuviLogger getLogger() {
        return logger;
    }

    public Limits getEntityLimits() {
        return entityLimits;
    }

    // -------------------------------------------------------------------------
    // custom commands
    // -------------------------------------------------------------------------
    public boolean isRegisteredScriptCommand(String s) {
        return scriptCommands.contains(s);
    }

    public void registerScriptCommand(String s) {
        if(s.equals("script")) {
            return;
        }
        scriptCommands.add(s);
    }

    public void unregisterScriptCommand(String s) {
        scriptCommands.remove(s);
    }

    public void clearScriptCommands() {
        scriptCommands.clear();
    }

    // -------------------------------------------------------------------------
    // player script
    // -------------------------------------------------------------------------
    public List<UUID> getPlayerList(int id) {
        return registeredPlayers.getOrDefault(id, Collections.EMPTY_LIST);
    }

    private List<UUID> getOrCreatePlayerList(int id) {
        List<UUID> list = registeredPlayers.get(id);
        if(list == null) {
            list = new ArrayList<>();
            registeredPlayers.put(id, list);
        }
        return list;
    }

    public boolean registerPlayer(Script sc, PlayerEntity p) {
        if(playerScript.putIfAbsent(p.getUniqueID(), sc.getId()) != null) {
            return false;
        }
        getOrCreatePlayerList(sc.getId()).add(p.getUniqueID());
        ScriptVars.setPlayerVars(sc, p);
        manager.callEvent("player_join", sc, null, null);
        return true;
    }

    public boolean isRegistered(PlayerEntity p) {
        return playerScript.containsKey(p.getUniqueID());
    }

    public Script getScript(PlayerEntity p) {
        // check if the player is mapped to a script
        Integer id = playerScript.get(p.getUniqueID());
        if(id == null) {
            return null;
        }
        // player mapping does not necessary mean the script is valid
        Script sc = manager.getScript(id);
        if(sc == null) {
            playerScript.remove(p.getUniqueID());
            logger.print(String.format("invalid script of '%s' was removed. (1)", p.getName()));
            return null;
        }
        return sc;
    }

    public boolean unregisterPlayer(Script callingScript, PlayerEntity p) {
        Integer id = playerScript.remove(p.getUniqueID());
        if(id == null) {
            return false;
        }
        List<UUID> list = registeredPlayers.get(id);
        if(list == null) {
            return false;
        }
        boolean b = list.remove(p.getUniqueID());
        if(list.isEmpty()) {
            Script sc = manager.getScript(id);
            if(sc == null) {
                logger.print(String.format("invalid script of '%s' was removed. (2)", p.getName()));
                return true;
            }
            if(sc != callingScript) {
                manager.callEvent("player_kicked", sc, (innerSc) -> ScriptVars.setPlayerVars(innerSc, p), null);
            }
            manager.removeScript(sc);
        }
        return b;
    }

    public void clearPlayerRegistry() {
        playerScript.clear();
        registeredPlayers.clear();
    }

    // -------------------------------------------------------------------------
    // script starting
    // -------------------------------------------------------------------------
    public Script startPlayerScript(PlayerEntity p, String... names) {
        if(isRegistered(p)) {
            return null;
        }
        Arrays.setAll(names, i -> "scripts/" + names[i] + ".txt");
        return manager.startScript(false, sc -> {
            int id = sc.getId();
            playerScript.put(p.getUniqueID(), id);
            getOrCreatePlayerList(id).add(p.getUniqueID());
        }, sc -> {
            getPlayerList(sc.getId()).forEach(uuid -> playerScript.remove(uuid));
            registeredPlayers.remove(sc.getId());
            cec.removeScriptData(sc);
        }, names[0], names);
    }

    public Script startScript(String name, String... names) {
        Arrays.setAll(names, i -> "scripts/" + names[i] + ".txt");
        return manager.startScript(true, sc -> {
        }, sc -> cec.removeScriptData(sc), name == null ? names[0] : name, names);
    }
}
