/*
 *
 * AttributeTree
 *
 * Handles a players individual attribute tree
 *
 *
*/

package jesse.keeblarcraft.AttributeMgr;

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

import jesse.keeblarcraft.Keeblarcraft;
import jesse.keeblarcraft.AttributeMgr.AttributeNodes.AbstractNode;
import jesse.keeblarcraft.ConfigMgr.ConfigManager;
import net.minecraft.server.network.ServerPlayerEntity;

public class AttributeTree {

    PlayerTree playerAttributeTree = new PlayerTree();
    ConfigManager config = new ConfigManager();
    private AbstractNode root = new RootNode();
    private ServerPlayerEntity player;

    /////////////////////////////////////////////////////////////////////////////
    /// @class      TreeNode
    ///
    /// @brief      This is an individual node that goes within the larger
    ///             PlayerTree class object
    /////////////////////////////////////////////////////////////////////////////
    public class TreeNode {
        public TreeNode(AbstractNode node, Integer maxLevel, List<String> parents, List<String> children) {
            thisNode = node;
            parentNodes = parents;
            childrenNodes = children;
            maxNodeLevel = maxLevel;
            currentNodeLevel = 1;
        }

        public AbstractNode thisNode;
        public Integer currentNodeLevel;
        public Integer maxNodeLevel;

        // Store the names of the parent and children nodes; this lets a node have any number of parents and children
        // NOTE TO DEVELOPERS: Be aware! The more nodes the harrier the tree will look in the GUI!!! Always test your
        // code before just adding stuff willy-nilly!
        public List<String> parentNodes;
        public List<String> childrenNodes;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @class      PlayerTree
    ///
    /// @brief      This is the tree object of a given player. It contains all
    ///             valid attribute information for a player that is statically
    ///             stored inside the AttributeMgr class
    /////////////////////////////////////////////////////////////////////////////
    private class PlayerTree {
        public String uuid;
        // Key = name of AbstractNode
        // Val = The attribute itself
        public HashMap<String, TreeNode> tree = new HashMap<String, TreeNode>();
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @class      RootNode
    ///
    /// @brief      This is the default root node of a tree. It is mostly left
    ///             blank and used to code around as an "anchor" object
    /////////////////////////////////////////////////////////////////////////////
    private class RootNode extends AbstractNode {
        @Override
        public String GetNodeTitle() {
            return "root";
        }

        @Override
        public String GetNodeDescription() {
            return "This is the players tree root! All attributes extend from here";
        }

        @Override
        public HashMap<String, List<String>> GetDetails() {
            HashMap<String, List<String>> ret = new HashMap<String, List<String>>();

            ret.put("First Attribute", List.of("This is your skill tree", "All attributes grow from this root! Unlocking nodes requires all nodes connected to that previously to be unlocked first"));

            return ret;
        }

		@Override
		public void RegisterCallbacks() {
		}
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         RegisterActiveCallbacks
    ///
    /// @brief      This will register all the active callbacks on all nodes that
    ///             are unlocked within a players tree instance
    /////////////////////////////////////////////////////////////////////////////
    public void RegisterActiveCallbacks() {
        for(Entry<String, TreeNode> tree : playerAttributeTree.tree.entrySet()) {
            if (tree.getValue().currentNodeLevel != 0) {

                // We need to construct the node object if it's null. If it's not null; we assume
                // the callback registration process has already happened
                if (tree.getValue().thisNode == null) {
                    System.out.println("REGISTERACTIVECALLBACKS - NULL NODE");
                    tree.getValue().thisNode.RegisterCallbacks();
                } else {
                    System.out.println("REGISTERACTIVECALLBACKS - NOT NULL NODE");
                }
            }
        }
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         AddNewNode
    ///
    /// @arg[in]    newNode is the attribute node to be added to the player tree
    ///
    /// @arg[in]    maxNodeLevel is the max level of the node
    ///
    /// @arg[in]    parents is the list of parent attributes. If empty,
    ///             root is default
    ///
    /// @arg[in]    children is the list of children attributes.
    ///             Root cannot be child
    ///
    /// @brief      Add a new node to the tree arbitrarily. Root can never be a
    ///             child node parents can never be empty or null (null or empty
    ///             would imply you are adding root)
    ///
    /// @note       developer warning: you must personally verify your nodes are
    ///             in the tree. anywhere is fine, and the tree is very dynamic
    ///             (which is why self verification is required). When the tree
    ///             is drawn in the GUI, it starts from the root node. As long as
    ///             your node is attached to this node anywhere in the indirected
    ///             graph; then your attribute will be drawn in the gui
    /////////////////////////////////////////////////////////////////////////////
    public Boolean AddNewNode(AbstractNode newNode, Integer maxNodeLevel, List<String> parents, List<String> children) {
        Boolean ret = false;
        TreeNode nodeReference = playerAttributeTree.tree.get(newNode.GetNodeTitle());

        System.out.println("Is node reference null? -> " + (nodeReference == null ? "YES":"NO"));
        System.out.println("Is root reference null? -> " + (root == null ? "YES":"NO"));
        System.out.println("Is parents null? -> " + (parents == null ? "YES":"NO"));

        // Some special handling is required on checking child list in case it is null
        Boolean validChildren = true;
        if (children != null) {
            validChildren = !children.contains(root.GetNodeTitle());
        }

        if (nodeReference == null && validChildren) {
            // Since these values can be left as null, we want to guarentee they are at least initialized here
            maxNodeLevel = (maxNodeLevel == null ? 1 : maxNodeLevel);
            parents = (parents == null ? new ArrayList<String>() : parents);
            children = (children == null ? new ArrayList<String>() : children);

            if (parents.size() == 0) {
                parents.add(root.GetNodeTitle()); // No disconnected nodes allowed! All parentless nodes are adopted by root by default
            }

            // Node implementation here
            playerAttributeTree.tree.put(newNode.GetNodeTitle(), new TreeNode(newNode, maxNodeLevel, parents, children));

            // if the new nodes level is not 0 we need to manually register the callbacks
            System.out.println("Node title: " + newNode.GetNodeTitle() + " has power level " +
            playerAttributeTree.tree.get(newNode.GetNodeTitle()).currentNodeLevel);
            if (playerAttributeTree.tree.get(newNode.GetNodeTitle()).currentNodeLevel != 0) {
                System.out.println("Registering new callback for new node");
                playerAttributeTree.tree.get(newNode.GetNodeTitle()).thisNode.RegisterCallbacks();
            }
        } else {
            // Some fancy error handling for console log
            if (nodeReference != null) {
                Keeblarcraft.LOGGER.error("Attribute with name " + newNode.GetNodeTitle() + " was already found within the tree! Rename your node to add it");
            } else {
                Keeblarcraft.LOGGER.error("The attribute you attempted to add (name: " + newNode.GetNodeTitle() + "), has root in the children");
            }
            ret = false;
        }

        FlashConfig();
        return ret;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         ChangeNodeLevel
    ///
    /// @arg[in]    nodeName is the attribute node title
    ///
    /// @arg[in]    newLevel is the new level you wish to grant the node
    ///
    /// @brief      If you wish to change the level of an attribute, you can use
    ///             this function to do so
    /////////////////////////////////////////////////////////////////////////////
    public void ChangeNodeLevel(String nodeName, Integer newLevel) {
        TreeNode nodeReference = playerAttributeTree.tree.get(nodeName);

        if (nodeReference != null) {
            if (newLevel <= nodeReference.maxNodeLevel) {
                nodeReference.currentNodeLevel = newLevel;
            }
        }

        FlashConfig();
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         public Boolean ContainsAttribute(String nodeName) {
    ///
    /// @arg[in]    nodeName is the attribute node title
    ///
    /// @brief      Returns true if this player presently has the 'nodeName'
    ///             attribute unlocked
    /////////////////////////////////////////////////////////////////////////////
    public Boolean ContainsAttribute(String nodeName) {
        return playerAttributeTree.tree.containsKey(nodeName);
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         DeleteNode
    ///
    /// @arg[in]    nodeName is the attribute node title
    ///
    /// @brief      Delete a node via the name
    /////////////////////////////////////////////////////////////////////////////
    public void DeleteNode(String nodeName) {
        // Do not delete root!
        if (nodeName != root.GetNodeTitle()) {
            playerAttributeTree.tree.remove(nodeName);
        }

        FlashConfig();
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         GetNodeDetails
    ///
    /// @arg[in]    nodeName is the attribute node title
    ///
    /// @brief      Returns the detail map from the attribute if it has any
    /////////////////////////////////////////////////////////////////////////////
    public HashMap<String, List<String>> GetNodeDetails(String nodeName) {
        HashMap<String, List<String>> ret = null;

        TreeNode nodeReference = playerAttributeTree.tree.get(nodeName);

        if (nodeReference != null) {
            ret = nodeReference.thisNode.GetDetails();
        }

        return ret;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         GetPlayerTree
    ///
    /// @brief      Returns the player tree. Will be deprecated in use of the
    ///             static active user map list instead for performance reasons
    /////////////////////////////////////////////////////////////////////////////
    @Deprecated
    public PlayerTree GetPlayerTree() {
        return playerAttributeTree;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         AttributeTree
    ///
    /// @arg[in]    uuid is the player uuid
    ///
    /// @brief      Constructor for class
    /////////////////////////////////////////////////////////////////////////////
    public AttributeTree(ServerPlayerEntity p) {
        this.player = p;
        String uuid = player.getUuidAsString();

        Boolean existingFile = false;
        try {
            playerAttributeTree = config.GetJsonObjectFromFile("attributes/" + uuid + ".json", PlayerTree.class);
            existingFile = playerAttributeTree != null;
        } catch (Exception e) {
            System.out.println("Attribute tree file does not exist for this player");
            // Do nothing. This means the file does not exist
        }

        // In the event the above code failed out, this means a new file has to be created for the player's uuid
        if (!existingFile)
        {
            System.out.println("Trying to create new file");
            try {
                playerAttributeTree = new PlayerTree();
                playerAttributeTree.uuid = uuid;
                FlashConfig();
            } catch (Exception e) {
                System.out.println("Could not write to file");
            }
        }

        // It's possible the above code will return a blank class if a file doesn't exist. This will make
        // a new file with this players uuid
        if ("".equals(playerAttributeTree.uuid)) {
            playerAttributeTree.uuid = uuid;
        }

        // If the tree is empty or missing root, add it!
        if (playerAttributeTree.tree.size() == 0 || playerAttributeTree.tree.containsKey(root.GetNodeTitle()) == false) {
            playerAttributeTree.tree.put(root.GetNodeTitle(), new TreeNode(root, 1, null, null));
        }

        RegisterActiveCallbacks();
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         FlashConfig
    ///
    /// @brief      Flashes the config to the disk
    /////////////////////////////////////////////////////////////////////////////
    public void FlashConfig() {
        /// TODO: This is broken because of a 'java.util.Option#value it cannot write. Idk, fix soon'
        // config.WriteToJsonFile("attributes/" + playerAttributeTree.uuid + ".json", playerAttributeTree);
    }
}
