package mhtree;

import cz.muni.fi.disa.similarityoperators.cover.AbstractRepresentation.PrecomputedDistances;
import messif.objects.LocalAbstractObject;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Represents an internal node in MH-Tree.
 */
class InternalNode extends Node implements Serializable {

    /**
     * Serialization ID
     */
    private static final long serialVersionUID = 2L;

    private final List<Node> children;

    protected InternalNode(PrecomputedDistances distances, InsertType insertType, ObjectToNodeDistance objectToNodeDistance, List<Node> children) {
        super(distances, insertType, objectToNodeDistance);

        this.children = children;
    }

    /**
     * Returns the list of child nodes.
     *
     * @return the list of child nodes
     */
    protected List<Node> getChildren() {
        return children;
    }

    /**
     * Returns the nearest child to the {@code object}.
     *
     * @param object object to which the distance is measured
     * @return the nearest child to the {@code object}
     */
    protected Node getNearestChild(LocalAbstractObject object) {
        Node nearestChild = children.get(0);
        double minDistance = nearestChild.getDistance(object);

        for (int i = 1; i < children.size(); i++) {
            double distance = children.get(i).getDistance(object);

            if (distance < minDistance) {
                minDistance = distance;
                nearestChild = children.get(i);
            }
        }

        return nearestChild;
    }

    /**
     * Adds {@code object} into this node.
     *
     * @param object object to be added
     */
    protected void addObject(LocalAbstractObject object) {
        addObjectIntoHull(object);
    }

    /**
     * Returns the list of objects stored in node's descendants.
     *
     * @return the list of objects stored in node's descendants
     */
    protected List<LocalAbstractObject> getObjects() {
        return children
                .stream()
                .map(Node::getObjects)
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }

    /**
     * Returns the height of this node.
     *
     * @return the height of this node
     */
    protected int getHeight() {
        return children
                .stream()
                .mapToInt(Node::getHeight)
                .summaryStatistics()
                .getMax() + 1;
    }

    /**
     * Adds this node and this node's descendants into {@code nodes}.
     *
     * @param nodes list of nodes
     */
    protected void gatherNodes(List<Node> nodes) {
        nodes.add(this);
        nodes.addAll(children);
    }

    /**
     * Calls {@code gatherLeafNodes} on every child.
     *
     * @param leafNodes list of leaf nodes
     */
    protected void gatherLeafNodes(List<LeafNode> leafNodes) {
        children.forEach(child -> child.gatherLeafNodes(leafNodes));
    }
}