package arbitrarytree;

import java.util.*;

public class ArbitraryTree {
    public static final int ROOT = -1;
    private Set<Integer> nodeIds;
    private Set<String> nodeNames;

    public ArbitraryTree() {}

    /* ======= TASK 1 ======== */

    /**
     * Add the root node.
     * If no root node exists then this method adds the root and returns true.
     * If there is a root node already then this method does nothing and returns false.
     *
     * @param rootId is greater than or equal to 0.
     * @param rootName is not null and is not the empty string.
     * @return true if the root node was added and false otherwise.
     */
    public boolean addRoot(int rootId, String rootName) {
        if (ROOT == -1) {
            
        }
        return false;
        
    }

    /**
     * Add a node as a child to a specific parent node. This method returns true if
     * the child was successfully added. This method returns false if the parent does
     * not exist in this tree or if the child identifier is a duplicate.
     *
     * @param parentId the identifier of the parent node;
     *        requires {@code this.contains(parentId) == true}.
     * @param childId the identifier of the child node, >= 0, and should be unique to the tree.
     * @param childName is not null and is not the empty string.
     * @return true if the child was added successfully and false otherwise.
     */
    public boolean addChildOf(int parentId, int childId, String childName) {
        return false; // TODO: Implement this method
    }

    /**
     * Checks if a node with {@code nodeId} as identifier is present in this tree.
     * @param nodeId the node to check for, is >= 0.
     * @return true if there is a code with id as {@code nodeId} and false otherwise.
     */
    public boolean contains(int nodeId) {
        return false; // TODO: Implement this method
    }

    /**
     * Retrieves the name of the node associated with the specified identifier {@code nodeId}.
     *
     * @param nodeId the identifier of the node whose name is to be retrieved;
     *        requires {@code this.contains(nodeId) == true}.
     * @return the name of the node with the given {@code nodeId}.
     */
    public String getNodeName(int nodeId) {
        return null; // TODO: Implement this method
    }

    /**
     * Returns the parent id given a node id. If the given node id
     * is for the root of the tree then this method returns
     * {@code ArbiraryTree.ROOT}.
     *
     * @param nodeId the node to find the parent for;
     *      requires {@code this.contains(nodeId) == true}.
     * @return the id of the parent for the given node id and
     * {@code ArbiraryTree.ROOT} if the given id is that of the root node.
     */
    public int getParentId(int nodeId) {
        return -1; // TODO: Implement this method
    }

    /* ======= TASK 2 ======== */

    /**
     * Add a node as a child to a specific parent node. The parent is identified by
     * its name and not its identifier. If there is a unique node that matches the
     * specified name then treat that node as the parent node and add the child node.
     * If multiple nodes match {@code parentName}, this method returns false.
     * @param parentName the name of the parent; is not null and is not the empty string.
     * @param childId the identifier for the child, > 0.
     * @param childName the name for the child, is not null and is not the empty string.
     * @return true if the child node was successfully added and false otherwise.
     */
    public boolean addChildOf(String parentName, int childId, String childName) {
        return false; // TODO: Implement this method
    }

    /**
     * Checks if there are one or more nodes with {@code nodeName} as the name.
     * @param nodeName the name to check for, is not null and is not the empty string.
     * @return true if there is at least one node that matches {@code nodeName} and false otherwise.
     */
    public boolean contains(String nodeName) {
        return false; // TODO: Implement this method
    }

    /**
     * Returns the node identifier for the node associated with the name
     * {@code nodeName}. If there are multiple nodes with the same name then
     * this method throws an {@code AmbiguityException}.
     * @param nodeName the name to search for, is not null and is not the empty string.
     * @return the node identifier associated with {@code nodeName}.
     * @throws AmbiguityException if there are multiple nodes with the same name.
     */
    public int getNodeId(String nodeName) throws AmbiguityException {
        return -1; // TODO: Implement this method
    }

    /**
     * Return the nodeIds for all nodes that have the name given by
     * {@code nodeName}.
     * @param nodeName the name to search for, is not null and is not the empty string.
     * @return the nodeIds for all nodes that have the name given by {@code nodeName}.
     */
    public Set<Integer> getAllMatches(String nodeName) {
        childNode.parent = this;
        this.children.add(childNode);

        System.out.println(childNode);
        return childNode;

        
    }

    /**
     * Get the name of the parent of the node identified by {@code nodeId}.
     *
     * @param nodeId the node for which the parent's name is needed, > 0.
     * @return the name of the parent of the node identified by {@code nodeId}.
     * @throws NoParentException if {@code nodeId} refers to the root node.
     */
    public String getParentName(int nodeId) throws NoParentException {
        return null; // TODO: Implement this method
    }

    /**
     * Retrieves all sibling nodes of the specified {@code nodeId}.
     *
     * @param nodeId the node identifier for which to find siblings; requires {@code nodeId > 0}.
     * @return a set of all sibling node identifiers that share the same parent as {@code nodeId},
     *         excluding {@code nodeId} itself. Returns an empty set if there are no siblings.
     */
    public Set<Integer> getSiblings(int nodeId) {
        return Collections.emptySet(); // TODO: Implement this method
    }

    /* ======= TASK 3 ======== */

    /**
     * Initializes an ArbitraryTree from an array structure and an array of node names.
     *
     * @param arbitraryTree an array defining the tree structure:
     *        each entry has a `nodeId`, the count of children, and
     *        references to the indices of the children in the same array.
     *        Parents are always listed before their children in this array representation.
     * @param nodeNames an array where each index represents the name of the node with the same id.
     * @throws IllegalArgumentException if the structure is invalid or the arrays are insufficient.
     */
    public ArbitraryTree(int[] arbitraryTree, String[] nodeNames) {
        // TODO: Implement this method
    }

    /**
     * Checks if the node identified by {@code ancestorId} is an ancestor of the
     * node identified by {@code childId}.
     *
     * @param ancestorId the identifier of the node that might be the ancestor, > 0.
     * @param childId the identifier of the node that might be a descendant of {@code ancestorId}, > 0.
     * @return true if the node with {@code ancestorId} is an ancestor of the node
     *         with {@code childId}, and false otherwise.
     */
    public boolean isAncestorOf(int ancestorId, int childId) {
        return false; // TODO: Implement this method
    }

    /**
     * Checks if the node identified by {@code descendantId} is a descendant of the
     * node identified by {@code ancestorId}.
     *
     * @param descendantId the identifier of the node that might be a descendant, > 0.
     * @param ancestorId the identifier of the node that might be the ancestor of {@code descendantId}, > 0.
     * @return true if the node with {@code descendantId} is a descendant of the node
     *         with {@code ancestorId}, and false otherwise.
     */
    public boolean isDescendantOf(int descendantId, int ancestorId) {
        return false; // TODO: Implement this method
    }


    /* ======= TASK 4 ======== */

    /**
     * Computes the total reward for this tree based on family sizes and rewards array.
     *
     * @param rewards is not null and rewards.size == number of nodes in the tree
     * @return the total reward computed from family sizes and corresponding rewards.
     */
    public double totalReward(double[] rewards) {
        return 0; // TODO: Implement this method
    }

    /**
     * Computes the maximum reward increase that is possible by adding a new leaf node.
     * If the total reward will not increase then this method will return 0.0 to
     * indicate that adding a new node is not beneficial.
     *
     * @param rewards is not null and rewards.size == (number of nodes in the tree) + 1
     * @return the maximum reward increase that is possible by adding a new leaf node.
     */
    public double rewardIncreaseWithSingleNodeAddition(double[] rewards) {
        return -1;
    }

    /* ======== TASK 5 ======== */

    /**
     * Computes the maximum possible reward for any tree with n nodes.
     *
     * @param rewards the rewards array corresponding to family sizes 0 to n-1.
     * @return the maximum possible reward achievable for any tree
     *         with rewards.size nodes.
     */
    public static double maxPossibleReward(double[] rewards) {
        return 0; // TODO: Implement this method
    }
}