/*
 *
 * FactionConfig
 *
 * This class is the configuration file for factions
 *
*/

package jesse.keeblarcraft.FactionMgr;

import static java.util.Map.entry;

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

// This class is written to a config file typically and represents
// all the details surrounding a faction
public class FactionConfig {
    // Key = Faction identifier
    // Val = Faction object
    HashMap<String, WriteableFaction> allFactions = new HashMap<String, WriteableFaction>();

    // Enum may be extended in future
    public static enum VALID_FACTION_ROLES {
        OWNER,
        COOWNER,
        MANAGEMENT,
        EMPLOYEE
    }

    private static Map<VALID_FACTION_ROLES, Integer> ROLE_LEVELS = Map.ofEntries
    (
        entry (VALID_FACTION_ROLES.EMPLOYEE, 0),
        entry (VALID_FACTION_ROLES.MANAGEMENT, 1),
        entry (VALID_FACTION_ROLES.COOWNER, 2),
        entry (VALID_FACTION_ROLES.OWNER, 3)
    );

    // The below map is 100x easier to just have statically typed here and maintained manually than expensively getting value by key (I am also lazy)
    private static Map<Integer, VALID_FACTION_ROLES> ROLES_BY_LEVEL = Map.ofEntries
    (
        entry (0, VALID_FACTION_ROLES.EMPLOYEE),
        entry (1, VALID_FACTION_ROLES.MANAGEMENT),
        entry (2, VALID_FACTION_ROLES.COOWNER),
        entry (3, VALID_FACTION_ROLES.OWNER)
    );

    public class WriteableFaction {
        // Key = Player UUID
        // Val = Faction role of player
        HashMap<String, VALID_FACTION_ROLES> factionPlayerList = new HashMap<String, VALID_FACTION_ROLES>();
        // Key = Player DISPLAY name
        // Val = Faction role of player
        HashMap<String, VALID_FACTION_ROLES> DISPLAY_ONLY_LIST = new HashMap<String, VALID_FACTION_ROLES>();

        // List contains UUID of players who are openly invited to this faction
        // TODO: Refactor to map so players can un-invite by name not UUID...
        List<String> openInvites = new ArrayList<String>();

        Integer factionBankBalance;
        Integer factionPower;
        String factionName;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         CreateFaction
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  creatorUuid is the caller of the command's UUID
    ///
    /// @param[in]  creatorDisplayName is the caller's display name
    ///
    /// @brief      Creates a faction on the server
    ///
    /// @return     True if a faction can be created, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean CreateFaction(String factionName, String creatorUuid, String creatorDisplayName) {
        Boolean success = false;
        if (!IsValid(factionName)) {
            WriteableFaction newFaction = new WriteableFaction();
            newFaction.factionPlayerList.put(creatorUuid, VALID_FACTION_ROLES.OWNER);
            newFaction.DISPLAY_ONLY_LIST.put(creatorDisplayName, VALID_FACTION_ROLES.OWNER);
            newFaction.factionBankBalance = 0;
            newFaction.factionPower = 1;
            allFactions.put(factionName, newFaction);
            success = true;
        }
        return success;
    }

    // It is assumed that the person calling this function has verified that the person
    // can in fact set faction power. This is generally just an admin command
    public Boolean SetPower(String factionName, Integer amount) {
        Boolean success = false;
        if (IsValid(factionName)) {
            allFactions.get(factionName).factionPower = amount;
            success = true;
        }
        return success;
    }

    // The user should verify the faction exists prior to calling this. 0 is default
    // return if faction does not exist (or the faction actually has a balance of 0)
    public Integer GetPower(String factionName) {
        Integer amount = 0;
        if (IsValid(factionName)) {
            amount = allFactions.get(factionName).factionPower;
        }
        return amount;
    }

    public Integer GetBankBalance(String factionName) {
        Integer amount = 0;
        if (IsValid(factionName)) {
            amount = allFactions.get(factionName).factionBankBalance;
        }

        return amount;
    }

    // You should probably verify the player is in the faction before calling this so the object is not empty
    public HashMap<String, VALID_FACTION_ROLES> GetMemberNames(String factionName) {
        HashMap<String, VALID_FACTION_ROLES> members = new HashMap<String, VALID_FACTION_ROLES>();

        if (IsValid(factionName)) {
            members = allFactions.get(factionName).DISPLAY_ONLY_LIST;
        }

        return members;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         DeleteFaction
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  creatorUuid is the caller of the command's UUID
    ///
    /// @brief      Deletes a faction on the server
    ///
    /// @return     True if a faction can be deleted, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean DeleteFaction(String factionName, String callerUuid) {
        Boolean success = false;
        // faction must exist
        if (IsValid(factionName)) {
            // faction must contain player
            if (allFactions.get(factionName).factionPlayerList.containsKey(callerUuid)) {
                // player must be owner of faction
                if (allFactions.get(factionName).factionPlayerList.get(callerUuid) == VALID_FACTION_ROLES.OWNER) {
                    //TODO: BankManager will connect with this in future and the money of the faction must go somewhere!
                    allFactions.remove(factionName);
                    success = true;
                }
            }
        }
        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         CanInvite
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  creatorUuid is the caller of the command's UUID
    ///
    /// @brief      Determines if the player is capable of inviting another player
    ///             to the faction
    ///
    /// @return     True if player can invite another player, false if not
    /////////////////////////////////////////////////////////////////////////////
    private Boolean CanInvite(String factionName, String callerUuid) {
        Boolean success = false;

        if (IsValid(factionName)) {
            if (allFactions.get(factionName).factionPlayerList.containsKey(callerUuid)) {
                VALID_FACTION_ROLES callerRole = allFactions.get(factionName).factionPlayerList.get(callerUuid);
                if (callerRole == VALID_FACTION_ROLES.MANAGEMENT || callerRole == VALID_FACTION_ROLES.COOWNER || callerRole == VALID_FACTION_ROLES.OWNER) {
                    success = true;
                }
            }
        }
        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         IsOnInviteList
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  playerUuid is the caller of the command's UUID
    ///
    /// @brief      Checks to see if a player has been invited to a faction
    ///
    /// @return     True if player is on invite list, false if not
    /////////////////////////////////////////////////////////////////////////////
    private Boolean IsOnInviteList(String factionName, String playerUuid) {
        Boolean success = false;

        if (IsValid(factionName)) {
            if (allFactions.get(factionName).openInvites.contains(playerUuid)) {
                success = true;
            }
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         AddToFaction
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  playerUuid is the caller of the command's UUID
    ///
    /// @param[in]  playerDisplayName is the caller's display name
    ///
    /// @brief      Adds a player to a faction
    /////////////////////////////////////////////////////////////////////////////
    private void AddToFaction(String factionName, String playerUuid, String playerDisplayName) {
        if (allFactions.containsKey(factionName)) {
            allFactions.get(factionName).factionPlayerList.put(playerUuid, VALID_FACTION_ROLES.EMPLOYEE);
            allFactions.get(factionName).DISPLAY_ONLY_LIST.put(playerDisplayName, VALID_FACTION_ROLES.EMPLOYEE);
            allFactions.get(factionName).openInvites.remove(playerUuid); // Remove them from the invite list if they joined
        }
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         InvitePlayerToFaction
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  callerUuid is the player inviting another player's UUID
    ///
    /// @param[in]  invitedUuid is the player being invited's UUID
    ///
    /// @brief      Invites a player to a faction
    ///
    /// @return     True if player successfully can invite another player
    /////////////////////////////////////////////////////////////////////////////
    public Boolean InvitePlayerToFaction(String factionName, String callerUuid, String invitedUuid) {
        Boolean success = false;

        if (CanInvite(factionName, callerUuid)) {
            allFactions.get(factionName).openInvites.add(invitedUuid);
            success = true;
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         JoinFaction
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  playerUuid is the player inviting another player's UUID
    ///
    /// @param[in]  playerDisplayName is the player being invited's UUID
    ///
    /// @brief      Function to determine if a player can join a faction
    ///
    /// @return     True if they join, false if they cannot
    /////////////////////////////////////////////////////////////////////////////
    public Boolean JoinFaction(String factionName, String playerUuid, String playerDisplayName) {
        Boolean success = false;

        if (IsOnInviteList(factionName, playerUuid) ) {
            AddToFaction(factionName, playerUuid, playerDisplayName);
            success = true;
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         LeaveFaction
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  playerUuid is the player inviting another player's UUID
    ///
    /// @param[in]  playerName is the player being invited's UUID
    ///
    /// @brief      Function to let player leave a faction
    ///
    /// @return     True if they leave, false if they did not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean LeaveFaction(String factionName, String playerUuid, String playerName) {
        Boolean success = false;

        if (IsValid(factionName)) {
            allFactions.get(factionName).factionPlayerList.remove(playerUuid);
            allFactions.get(factionName).DISPLAY_ONLY_LIST.remove(playerName);

            // TODO: In future add ability if owner leave then promote next person
            // Delete faction if all the players are gone
            if (allFactions.get(factionName).factionPlayerList.size() == 0) {
                allFactions.remove(factionName);
            }
            success = true;
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         HasPlayer
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  playerUuid is the player inviting another player's UUID
    ///
    /// @brief      Determines if a faction has a specific player by UUID
    ///
    /// @return     True if they have the player, false if not
    /////////////////////////////////////////////////////////////////////////////
    private Boolean HasPlayer(String factionName, String playerUuid) {
        Boolean success = false;

        if (IsValid(factionName)) {
            success = allFactions.get(factionName).factionPlayerList.containsKey(playerUuid);
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         IsValid
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @brief      Determines if a faction exists
    ///
    /// @return     True if a faction exists with the specified name, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean IsValid(String factionName) {
        if (allFactions.containsKey(factionName)) {
            return true;
        } else {
            return false;
        }
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         FindFactionOfPlayer
    ///
    /// @param[in]  playerUuid is the uuid of the player to find
    ///
    /// @brief      Determine the faction of a specified player
    ///
    /// @return     Returns the faction name of the player if the player is in
    ///             any faction. If the player is not found in any faction, an
    ///             empty string is returned
    /////////////////////////////////////////////////////////////////////////////
    public String FindFactionOfPlayer(String playerUuid) {
        String faction = "";

        System.out.println("Attempting to find player factions with uuid " + playerUuid);
        for (Entry<String, WriteableFaction> entry : allFactions.entrySet()) {
            if (entry.getValue().factionPlayerList.containsKey(playerUuid)) {
                System.out.println("FAC [" + entry.getKey() + "]: PLAY-LIST: " + entry.getValue().factionPlayerList);
                faction = entry.getKey();
                break;
            }
        }
        return faction;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         PromotePlayer
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  callerUuid is the caller of the command's UUID
    ///
    /// @param[in]  promoteeUuid is the player to be promoted's UUID
    ///
    /// @param[in]  promoteeDisplayName is the player ot be promoted's display name
    ///
    /// @brief      Attempts to promote a player in a faction. Whether or not a
    ///             player is promoted is determined by their level in the faction
    ///             and the promoter's level in the faction. Factors that can affect
    ///             this are:
    ///                 1.  Both the caller and promotee must exist inside
    ///                     the same faction
    ///                 2.  The promoter must be a higher rank than the promotee
    ///                 3.  The promotee's post-promotion rank still must be below
    ///                     the promoter's rank
    ///
    /// @return     True if the player was successfully promoted, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean PromotePlayer(String factionName, String callerUuid, String promoteeUuid, String promoteeDisplayName) {
        Boolean success = false;

        if (CanInvite(factionName, callerUuid) && HasPlayer(factionName, promoteeUuid)) {
            VALID_FACTION_ROLES callerRole = allFactions.get(factionName).factionPlayerList.get(callerUuid);
            VALID_FACTION_ROLES promoteeRole = allFactions.get(factionName).factionPlayerList.get(promoteeUuid);
            Integer callerRoleLevel = ROLE_LEVELS.get(callerRole);
            Integer promoteeRoleLevel = ROLE_LEVELS.get(promoteeRole);

            // Factions is setup so that anyone can promote anybody UNDERNEATH them. However, you CANNOT promote a player to your level!
            if (callerRoleLevel > promoteeRoleLevel + 1) {
                // Get the new employee role
                promoteeRole = ROLES_BY_LEVEL.get(promoteeRoleLevel + 1);

                // Update role in faction
                allFactions.get(factionName).factionPlayerList.put(promoteeUuid, promoteeRole);
                allFactions.get(factionName).DISPLAY_ONLY_LIST.put(promoteeDisplayName, promoteeRole);
                success = true;
            }
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         CanKickPlayer
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  callerUuid is the caller of the command's UUID
    ///
    /// @param[in]  kickeeUuid is the player to be kickee's UUID
    ///
    /// @param[in]  kickeeDisplayName is the player ot be kickeeDisplayName's
    ///             display name
    ///
    /// @brief      Determines whether or not a player can be kicked. The caller
    ///             must be a higher rank to kick a player (and they both must
    ///             exist inside the faction)
    ///
    /// @return     True if the player was successfully kicked, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean CanKickPlayer(String factionName, String callerUuid, String kickeeUuid, String kickeeDisplayName) {
        Boolean success = false;

        if (IsValid(factionName) && HasPlayer(factionName, kickeeUuid)) {
            VALID_FACTION_ROLES callerRole = allFactions.get(factionName).factionPlayerList.get(callerUuid);
            VALID_FACTION_ROLES kickeeRole = allFactions.get(factionName).factionPlayerList.get(kickeeUuid);
            Integer callerRoleLevel = ROLE_LEVELS.get(callerRole);
            Integer kickeeRoleLevel = ROLE_LEVELS.get(kickeeRole);

            if (callerRoleLevel > kickeeRoleLevel) {
                success = true;
            }
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         KickPlayer
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  callerUuid is the caller of the command's UUID
    ///
    /// @param[in]  kickeeUuid is the player to be kickee's UUID
    ///
    /// @param[in]  kickeeDisplayName is the player ot be kickeeDisplayName's
    ///             display name
    ///
    /// @brief      Kicks a player from a faction
    ///
    /// @return     True if the player was successfully kicked, false if not
    /////////////////////////////////////////////////////////////////////////////
    private Boolean KickPlayer(String factionName, String callerUuid, String kickeeUuid, String kickeeDisplayName) {
        Boolean success = false;

        if (CanKickPlayer(factionName, callerUuid, kickeeUuid, kickeeDisplayName)) {
            allFactions.get(factionName).factionPlayerList.remove(kickeeUuid);
            allFactions.get(factionName).DISPLAY_ONLY_LIST.remove(kickeeDisplayName);
            success = true;
        }

        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         DemotePlayer
    ///
    /// @param[in]  factionName is the name of the faction
    ///
    /// @param[in]  callerUuid is the caller of the command's UUID
    ///
    /// @param[in]  demoteeUuid is the player to be promoted's UUID
    ///
    /// @param[in]  demoteeDisplayName is the player ot be promoted's display name
    ///
    /// @brief      Attempts to demote a player in a faction. Whether or not a
    ///             player is demoted is determined by their level in the faction
    ///             and the demoter's level in the faction. Factors that can affect
    ///             this are:
    ///                 1.  Both the caller and demotee must exist inside
    ///                     the same faction
    ///                 2.  The demoter must be a higher rank than the promotee
    ///                 3.  Should there be no lower rank left to demote to, the
    ///                     player is kicked from the faction
    ///
    /// @return     True if the player was successfully demoted, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean DemotePlayer(String factionName, String callerUuid, String demoteeUuid, String demoteeDisplayName) {
        Boolean success = false;

        if (CanInvite(factionName, callerUuid) && HasPlayer(factionName, demoteeUuid)) {
            VALID_FACTION_ROLES callerRole = allFactions.get(factionName).factionPlayerList.get(callerUuid);
            VALID_FACTION_ROLES demoteeRole = allFactions.get(factionName).factionPlayerList.get(demoteeUuid);
            Integer callerRoleLevel = ROLE_LEVELS.get(callerRole);
            Integer demoteeRoleLevel = ROLE_LEVELS.get(demoteeRole);

            // Factions is setup so that anyone can demote anybody underneath them & the lowest level will cause a demotion to be a KICK from faction
            if (callerRoleLevel > demoteeRoleLevel) {
                // If the role level would be lower than bottom level, KICK player
                if (demoteeRoleLevel - 1 < ROLE_LEVELS.get(VALID_FACTION_ROLES.EMPLOYEE)) {
                    success = KickPlayer(factionName, callerUuid, demoteeUuid, demoteeDisplayName);
                } else {
                    // Regular demotion!
                    demoteeRole = ROLES_BY_LEVEL.get(demoteeRoleLevel - 1);

                    // Update faction
                    allFactions.get(factionName).factionPlayerList.put(demoteeUuid, demoteeRole);
                    allFactions.get(factionName).DISPLAY_ONLY_LIST.put(demoteeDisplayName, demoteeRole);
                    success = true;
                }
            }
        }
        return success;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         ListOfAllFactions
    ///
    /// @brief      Get a list of all factions on the server
    ///
    /// @return     List of factions
    /////////////////////////////////////////////////////////////////////////////
    public List<String> ListOfAllFactions() {
        List<String> facs = new ArrayList<String>();

        System.out.println("ListOfFactions - map size: " + allFactions.size());
        for (Entry<String, WriteableFaction> entry : allFactions.entrySet()) {
            facs.add(entry.getKey());
        }

        return facs;
    }
}
