package mhtree;

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

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

import static mhtree.ObjectToNodeDistance.FURTHEST;

/**
 * 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;
    }

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

        this.children = children;
    }

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

    protected int getNumberOfChildren() {
        return children.size();
    }

    /**
//     * 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) {
        Optional<Node> nearestCoveredNode = children
                .stream()
                .filter(child -> child.isCovered(object))
                .min(Comparator.comparing(child -> -FURTHEST.getDistance(object, child)));

        if (nearestCoveredNode.isPresent()) {
            return nearestCoveredNode.get();
        }

        Optional<Node> nearestChild = children
                .stream()
                .min(Comparator.comparing(child -> FURTHEST.getDistance(object, child)));

        return nearestChild.orElseThrow(() -> new IllegalStateException("Internal node has no children"));
    }

    @Override
    protected void addObject(LocalAbstractObject object) {
        addObjectIntoHull(object);
    }

    @Override
    protected List<LocalAbstractObject> getObjects() {
        return children
                .stream()
                .map(Node::getObjects)
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }

    @Override
    protected int getHeight() {
        return children
                .stream()
                .mapToInt(Node::getHeight)
                .summaryStatistics()
                .getMax() + 1;
    }

    @Override
    protected void gatherNodes(List<Node> nodes) {
        nodes.add(this);
        children.forEach(child -> child.gatherNodes(nodes));
    }

    @Override
    protected void gatherLeafNodes(List<LeafNode> leafNodes) {
        children.forEach(child -> child.gatherLeafNodes(leafNodes));
    }
}
