package mhtree;

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

import java.util.function.ToDoubleFunction;

/**
 * Specifies possible distance measurements between an object and a node.
 */
public enum ObjectToNodeDistance {

    /**
     * Average distance between {@code object} and every hull object in {@code node}.
     */
    AVERAGE {
        @Override
        protected double getDistance(Node node, ToDoubleFunction<? super LocalAbstractObject> getDistance) {
            return node
                    .getHullObjects()
                    .stream()
                    .mapToDouble(getDistance)
                    .sum() / node.getHullObjects().size();
        }
    },

    /**
     * Distance between {@code object} and {@code node}'s furthest hull object.
     */
    FURTHEST {
        @Override
        protected double getDistance(Node node, ToDoubleFunction<? super LocalAbstractObject> getDistance) {
            return node
                    .getHullObjects()
                    .stream()
                    .mapToDouble(getDistance)
                    .max()
                    .orElse(Double.MIN_VALUE);
        }
    },

    /**
     * Distance between {@code object} and {@code node}'s nearest hull object.
     */
    NEAREST {
        @Override
        protected double getDistance(Node node, ToDoubleFunction<? super LocalAbstractObject> getDistance) {
            return node
                    .getHullObjects()
                    .stream()
                    .mapToDouble(getDistance)
                    .min()
                    .orElse(Double.MAX_VALUE);
        }
    };

    /**
     * Returns the distance between {@code object} and {@code node}.
     *
     * @param object an object
     * @param node   a node
     * @return the distance between {@code object} and {@code node}
     */
    public double getDistance(LocalAbstractObject object, Node node) {
        return this.getDistance(node, object::getDistance);
    }

    /**
     * Returns the distance between {@code object} and {@code node} using {@code distances}.
     *
     * @param object    an object
     * @param node      a node
     * @param distances precomputed object distances
     * @return the distance between {@code object} and {@code node}
     */
    public double getDistance(LocalAbstractObject object, Node node, PrecomputedDistances distances) {
        return this.getDistance(node, otherObject -> distances.getDistance(object, otherObject));
    }

    protected abstract double getDistance(Node node, ToDoubleFunction<? super LocalAbstractObject> distanceFunction);
}
