package com.github.davidmoten.rtree;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.github.davidmoten.guavamini.Preconditions;
import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
import com.github.davidmoten.rtree.geometry.HasGeometry;
import com.github.davidmoten.rtree.geometry.ListPair;

public final class SplitterRStar implements Splitter {

    private final Comparator<ListPair<?>> comparator;

    public SplitterRStar() {
        this.comparator = new Comparator<ListPair<?>>() {

            @Override
            public int compare(ListPair<?> p1, ListPair<?> p2) {
                // check overlap first then areaSum
                int value = Double.compare(overlap(p1), overlap(p2));
                if (value == 0) {
                    return Double.compare(p1.areaSum(), p2.areaSum());
                } else {
                    return value;
                }
            }
        };
    }

    @Override
    public <T extends HasGeometry> ListPair<T> split(List<T> items, int minSize) {
        Preconditions.checkArgument(!items.isEmpty());
        // sort nodes into increasing x, calculate min overlap where both groups
        // have more than minChildren

        // compute S the sum of all margin-values of the lists above
        // the list with the least S is then used to find minimum overlap

        List<ListPair<T>> pairs = null;
        float lowestMarginSum = Float.MAX_VALUE;
        List<T> list = null;
        for (SortType sortType : SortType.values()) {
            if (list == null) {
                list = new ArrayList<T>(items);
            }
            Collections.sort(list, comparator(sortType));
            List<ListPair<T>> p = getPairs(minSize, list);
            float marginSum = marginValueSum(p);
            if (marginSum < lowestMarginSum) {
                lowestMarginSum = marginSum;
                pairs = p;
                // because p uses subViews of list we need to create a new one
                // for further comparisons
                list = null;
            }
        }
        return Collections.min(pairs, comparator);
    }

    private static Comparator<HasGeometry> comparator(SortType sortType) {
        switch (sortType) {
        case X_LOWER:
            return INCREASING_X_LOWER;
        case X_UPPER:
            return INCREASING_X_UPPER;
        case Y_LOWER:
            return INCREASING_Y_LOWER;
        case Y_UPPER:
            return INCREASING_Y_UPPER;
        default:
            throw new IllegalArgumentException("unknown SortType " + sortType);
        }
    }

    private enum SortType {
        X_LOWER, X_UPPER, Y_LOWER, Y_UPPER;
    }

    private static <T extends HasGeometry> float marginValueSum(List<ListPair<T>> list) {
        float sum = 0;
        for (ListPair<T> p : list)
            sum += p.marginSum();
        return sum;
    }

    @VisibleForTesting
    static <T extends HasGeometry> List<ListPair<T>> getPairs(int minSize, List<T> list) {
        List<ListPair<T>> pairs = new ArrayList<ListPair<T>>(list.size() - 2 * minSize + 1);
        for (int i = minSize; i < list.size() - minSize + 1; i++) {
            // Note that subList returns a view of list so creating list1 and
            // list2 doesn't
            // necessarily incur array allocation costs.
            List<T> list1 = list.subList(0, i);
            List<T> list2 = list.subList(i, list.size());
            ListPair<T> pair = new ListPair<T>(list1, list2);
            pairs.add(pair);
        }
        return pairs;
    }

    private static final Comparator<HasGeometry> INCREASING_X_LOWER = new Comparator<HasGeometry>() {

        @Override
        public int compare(HasGeometry n1, HasGeometry n2) {
            return Double.compare(n1.geometry().mbr().x1(), n2.geometry().mbr().x1());
        }
    };

    private static final Comparator<HasGeometry> INCREASING_X_UPPER = new Comparator<HasGeometry>() {

        @Override
        public int compare(HasGeometry n1, HasGeometry n2) {
            return Double.compare(n1.geometry().mbr().x2(), n2.geometry().mbr().x2());
        }
    };

    private static final Comparator<HasGeometry> INCREASING_Y_LOWER = new Comparator<HasGeometry>() {

        @Override
        public int compare(HasGeometry n1, HasGeometry n2) {
            return Double.compare(n1.geometry().mbr().y1(), n2.geometry().mbr().y1());
        }
    };

    private static final Comparator<HasGeometry> INCREASING_Y_UPPER = new Comparator<HasGeometry>() {

        @Override
        public int compare(HasGeometry n1, HasGeometry n2) {
            return Double.compare(n1.geometry().mbr().y2(), n2.geometry().mbr().y2());
        }
    };

    private static double overlap(ListPair<? extends HasGeometry> pair) {
        return pair.group1().geometry().mbr().intersectionArea(pair.group2().geometry().mbr());
    }

}

@@ -1464,21 +1464,22 @@
         
-float
+double
  lowestM
@@ -1493,23 +1493,32 @@
 m = 
-Float.MAX_VALUE
+Double.POSITIVE_INFINITY
 ;%0A  
@@ -1821,21 +1821,22 @@
         
-float
+double
  marginS
@@ -1886,16 +1886,17 @@
 ginSum %3C
+=
  lowestM
@@ -2795,21 +2795,22 @@
 ometry%3E 
-float
+double
  marginV
@@ -2855,13 +2855,14 @@
     
-float
+double
  sum

use double arithmetic in SplitterRStar lowestMarginSum, initialize with Double.POSITIVE_INFINITY, ensures pairs is non-null at exit of SplitterRStar.split