/*
 *
 * FactionManager
 *
 * Class is responsible for keeping track of factions chosen by the players in the game and saves to the configuration
 * file for persistent data storage. Class handles checks as well for eligibility purposes (making sure players can join, etc)
 *
*/

package jesse.keeblarcraft.FactionMgr;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import jesse.keeblarcraft.ConfigMgr.ConfigManager;
import jesse.keeblarcraft.FactionMgr.FactionConfig.VALID_FACTION_ROLES;
import jesse.keeblarcraft.FactionMgr.FactionTier.FactionTierEnum;
import jesse.keeblarcraft.MailSystem.MailMgr;
import jesse.keeblarcraft.Keeblarcraft;
import jesse.keeblarcraft.ChatStuff.ChatMenu;
import jesse.keeblarcraft.ChatStuff.ChatMsg;
import jesse.keeblarcraft.ChatStuff.ChatFormatting.COLOR_CODE;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;

public class FactionManager {
    private static String FACTION_CFG_FILE = "factions/factions.json";
    ConfigManager config = new ConfigManager();
    private static FactionManager static_inst;

    public static FactionManager GetInstance() {
        if (static_inst == null) {
            static_inst = new FactionManager();
        }

        return static_inst;
    }

    private class FactionConfigClassWrapper {
        FactionConfig factions = new FactionConfig();
    }
    FactionConfigClassWrapper factionConfig;// = new FactionConfigClassWrapper();


    // Constructor
    public FactionManager() {
        // Read in config at start of object
        System.out.println("FACTIONMANAGER CONSTRUCTOR CALLED");
        Boolean existingFile = false;
        factionConfig = new FactionConfigClassWrapper();

        Boolean tmpchck = factionConfig == null;
        System.out.println("Is factionConfig null still? " + (tmpchck ? "YES" : "NO"));
        try {
            factionConfig = config.GetJsonObjectFromFile(FACTION_CFG_FILE, FactionConfigClassWrapper.class);
            tmpchck = factionConfig == null;
            System.out.println("Is factionconfig null after trying to load stuff? " + (tmpchck ? "YES" : "NO"));
            existingFile = true;
        } catch (Exception e) {
            // Do nothing
        }

        // Create the file if it didn't exist before
        if (!existingFile)
        {
            try {
                config.CreateDirectory(FACTION_CFG_FILE);
                FlashConfig();
            } catch (Exception e) {
                Keeblarcraft.LOGGER.error("Could not write to file");
            }
        }

        if (factionConfig == null) {
            // the only way for this to be possible is if the read-in was bad. flash config file then try again
            factionConfig = new FactionConfigClassWrapper();

            //TODO: Add safe-guard in here to check if default faction dir exists and move it to OLD/CORRUPTED (so data is not nuked from orbit)
            factionConfig.factions = new FactionConfig();
            FlashConfig();
        }

        FactionTier.InitializeFactionTiers();
    }

    // This is purely a chat-based call. Here to help with `/faction info` basically.
    public void GetFactionInformation(ServerPlayerEntity player) {
        String factionName = GetFactionOfPlayer(player.getUuidAsString());

        if (!factionName.equals("")) {
            Integer fPower = factionConfig.factions.GetPower(factionName);
            HashMap<String, VALID_FACTION_ROLES> displayNames = factionConfig.factions.GetMemberNames(factionName);
            Integer fBankBalance = factionConfig.factions.GetBankBalance(factionName);

            System.out.println((fPower == null ? "YES":"NO") + " " + (displayNames == null ? "YES":"NO") + " " + (fBankBalance == null ? "YES":"NO"));

            ArrayList<ChatMsg> chatPlayerList = new ArrayList<>();
            for (Entry<String, VALID_FACTION_ROLES> entry : displayNames.entrySet()) {
                ChatMsg temp = new ChatMsg();
                temp.ColorMsg(entry.getKey() + " - " + entry.getValue().name(), COLOR_CODE.BLUE);
                chatPlayerList.add(temp);
            }

            // Let's make our chat objects first so things are copyable and whatnot
            ChatMsg chatHeader = new ChatMsg();
            chatHeader.ColorMsg(Text.of("----[" + factionName + "]----"), COLOR_CODE.GOLD);

            ChatMsg chatBalance = new ChatMsg();
            chatBalance.ColorMsg(Text.of("Balance: " + Integer.toString(fBankBalance)), COLOR_CODE.GREEN);

            ChatMsg chatPower = new ChatMsg();
            chatPower.ColorMsg(Text.of("Power: " + Integer.toString(fPower)), COLOR_CODE.RED);

            ChatMenu chatBlock = new ChatMenu();
            chatBlock.SetHeader(chatHeader);
            chatBlock.AddMsg(chatBalance);
            chatBlock.AddMsg(chatPower);

            chatBlock.SendMsg(player);
        } else {
            player.sendMessage(Text.of("You aren't in a faction!"));
        }
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         LeaveFaction
    ///
    /// @param[in]  player is the player on the server
    ///
    /// @brief      Leave a faction
    ///
    /// @return     True if player can leave their faction, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean LeaveFaction(ServerPlayerEntity player) {
        Boolean success = false;

        String playerFac = factionConfig.factions.FindFactionOfPlayer(player.getUuidAsString());

        if (playerFac != "") {
            success = factionConfig.factions.LeaveFaction(playerFac, player.getUuidAsString(), player.getEntityName());
            player.sendMessage(Text.of("[Factions]: You left your faction!"));
            FlashConfig();
        } else {
            player.sendMessage(Text.of("[Factions]: You are not in a faction!"));
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         CreateFaction
    ///
    /// @param[in]  factionName is the faction to be created
    ///
    /// @param[in]  player is the player on the server
    ///
    /// @brief      Create a new faction
    ///
    /// @return     True if successfully created, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean CreateFaction(String factionName, ServerPlayerEntity creator) {
        Boolean success = false;
        String facOfPlayer = factionConfig.factions.FindFactionOfPlayer(creator.getUuidAsString());

        if (facOfPlayer == "") {
            // creator.sendMessage(Text.of("Your display name: " + creator.getDisplayName().toString()));
            // creator.sendMessage(Text.of("Your name: " + creator.getName()));
            // creator.sendMessage(Text.of("Your custom name: " + creator.getCustomName()));
            // creator.sendMessage(Text.of("Your entity name: " + creator.getEntityName()));
            success = factionConfig.factions.CreateFaction(factionName, creator.getUuidAsString(), creator.getEntityName());

            if (!success) {
                creator.sendMessage(Text.of("[Factions]: Could not create faction - faction already exists."));
            } else {
                creator.sendMessage(Text.of("[Factions]: Successfully created faction!"));
                FlashConfig();
            }
        } else {
            creator.sendMessage(Text.of("[Factions]: You are already in a faction! You cannot create one."));
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         DeleteFaction
    ///
    /// @param[in]  factionName is the faction to be created
    ///
    /// @param[in]  caller is the player on the server
    ///
    /// @brief      Delete a faction
    ///
    /// @return     True if successfully deleted, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean DeleteFaction(String factionName, ServerPlayerEntity caller) {
        Boolean success = factionConfig.factions.DeleteFaction(factionName, caller.getUuidAsString());

        if (!success) {
            caller.sendMessage(Text.of("[Factions]: Could not delete faction. You must be owner & faction must exist."));
        } else {
            caller.sendMessage(Text.of("[Factions]: Successfully deleted faction."));
            FlashConfig();
        }

        return success;
    }

    public Boolean JoinFaction(ServerPlayerEntity player, String factionName) {
        Boolean joinedFaction = factionConfig.factions.JoinFaction(factionName, player.getUuidAsString(), player.getEntityName());

        if (joinedFaction) {
            player.sendMessage(Text.of("Successfully joined the faction " + factionName));
        } else {
            player.sendMessage(Text.of("Failed to join faction - you were not on the invite list!"));
        }

        return joinedFaction;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         ListOfFactions
    ///
    /// @brief      Get a list of all factions on the server
    ///
    /// @return     Faction list. Empty list if no factions
    /////////////////////////////////////////////////////////////////////////////
    public List<String> ListOfFactions() {
        System.out.println("Callthrough of listoffactions");
        return factionConfig.factions.ListOfAllFactions();
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         GetFactionOfPlayer
    ///
    /// @param[in]  playerUuid is the uuid of the player
    ///
    /// @brief      Gets the faction of a player on the server
    ///
    /// @return     String with faction name. Empty string if no faction tie
    /////////////////////////////////////////////////////////////////////////////
    public String GetFactionOfPlayer(String playerUuid) {
        return factionConfig.factions.FindFactionOfPlayer(playerUuid);
    }

    public Boolean InvitePlayerToFaction(ServerPlayerEntity caller, String inviteeUuid, String inviteeDisplayName) {
        String playerFaction = factionConfig.factions.FindFactionOfPlayer(caller.getUuidAsString());
        Boolean success = false;
        if (!playerFaction.equals("")) {
            success = factionConfig.factions.InvitePlayerToFaction(playerFaction, caller.getUuidAsString(), inviteeUuid);
        } else {
            caller.sendMessage(Text.of("You aren't in a faction!"));
        }

        if (success) {
            String mailMsg = "You received a faction invite from " + caller.getEntityName() + "! You can join with /faction join " + playerFaction;
            MailMgr.GetInstance().SendMail(inviteeUuid, mailMsg);
            FlashConfig();
        }
        return success;
    }

    public Boolean SetFactionPower(ServerPlayerEntity caller, String factionName, Integer amount) {
        Boolean success = false;

        if (caller.hasPermissionLevel(4)) {
            success = factionConfig.factions.SetPower(factionName, amount);
            FlashConfig();
        } else {
            caller.sendMessage(Text.of("You do not have permission to use this command"));
        }

        return success;
    }

    public HashMap<String, VALID_FACTION_ROLES> GetFactionPlayers(String factionName) {
        return factionConfig.factions.GetFactionPlayers(factionName);
    }

    public Integer GetFactionPower(String factionName) {
        int fPower = 0;
        if (factionConfig.factions.IsValid(factionName)) {
            fPower = factionConfig.factions.GetPower(factionName);
        }
        return fPower;
    }

    public FactionTierEnum GetFactionTier(String factionName) {
        return FactionTier.GetFactionTier(factionConfig.factions.GetPower(factionName));
    }

    public Integer GetFactionPower(ServerPlayerEntity caller) {
        Integer amount = 0;

        String playerFaction = factionConfig.factions.FindFactionOfPlayer(caller.getUuidAsString());
        if (!playerFaction.equals("")) {
            amount = factionConfig.factions.GetPower(playerFaction);
            caller.sendMessage(Text.of("[" + playerFaction + " - POWER]: " + Integer.toString(amount)));
        }
        return amount;
    }

    public Boolean PromotePlayer(ServerPlayerEntity caller, String promoteeUuid, String promoteeDisplayName) {
        Boolean success = factionConfig.factions.PromotePlayer(GetFactionOfPlayer(caller.getUuidAsString()), caller.getUuidAsString(), promoteeUuid, promoteeDisplayName);

        if (!success) {
            caller.sendMessage(Text.of("[Factions]: Could not promote player - you need to be a higher rank than them and they cannot be promoted to your level!"));
        } else {
            caller.sendMessage(Text.of("[Factions]: Successfully promoted player!"));
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         DemotePlayer
    ///
    /// @param[in]  caller is the player who is demoting another player
    ///
    /// @param[in]  demoteeUuid is the player's uuid who is being demoted
    ///
    /// @param[in]  demoteeDisplayName is the players display name who is being demoted
    ///
    /// @brief      Demote a player from a faction
    ///
    /// @return     True if player is demoted. False if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean DemotePlayer(ServerPlayerEntity caller, String demoteeUuid, String demoteeDisplayName) {
        Boolean success = factionConfig.factions.DemotePlayer(GetFactionOfPlayer(caller.getUuidAsString()), caller.getUuidAsString(), demoteeUuid, demoteeDisplayName);

        if (!success) {
            caller.sendMessage(Text.of("[Factions]: Could not demote player - you need to be a higher rank than them to demote them!"));
        } else {
            caller.sendMessage(Text.of("[Factions]: Successfully demoted player!"));
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         FlashConfig
    ///
    /// @brief      Update the faction configuration file on disk
    /////////////////////////////////////////////////////////////////////////////
    public void FlashConfig() {
        config.WriteToJsonFile(FACTION_CFG_FILE, factionConfig);
    }
}