package com.wytehat.btlogger;

import java.util.ArrayList;
import java.util.List;

/**
 * Downsamples dense GPS history before it is turned into map markers.
 *
 * Time-bucket sampling: the timeline is cut into fixed buckets and one
 * representative point (the most accurate fix) survives per bucket. The very
 * first and last fixes are always kept so the trail keeps its real endpoints.
 *
 * A hard ceiling is applied afterwards - even "All Points" cannot hand the
 * WebView an unbounded number of circle markers.
 */
final class TrackPointSampler {

    /** Ceiling on rendered points, applied regardless of the chosen interval. */
    static final int MAX_RENDERED_POINTS = 750;

    private TrackPointSampler() {
    }

    /**
     * @param bucketMillis bucket width, or 0 for "All Points" (cap only).
     */
    static List<LocationPoint> sample(List<LocationPoint> points, long bucketMillis) {

        if (points == null || points.size() < 3) {
            return points;
        }

        List<LocationPoint> reduced =
            bucketMillis > 0
                ? bucket(points, bucketMillis)
                : points;

        return cap(reduced);
    }

    private static List<LocationPoint> bucket(List<LocationPoint> points, long bucketMillis) {

        ArrayList<LocationPoint> result = new ArrayList<LocationPoint>();

        LocationPoint best = null;
        long currentBucket = 0;

        for (int i = 0; i < points.size(); i++) {

            LocationPoint point = points.get(i);

            if (point == null) {
                continue;
            }

            long bucketIndex = point.timestamp / bucketMillis;

            if (best == null) {
                best = point;
                currentBucket = bucketIndex;
                continue;
            }

            if (bucketIndex != currentBucket) {
                result.add(best);
                best = point;
                currentBucket = bucketIndex;
                continue;
            }

            if (moreAccurate(point, best)) {
                best = point;
            }
        }

        if (best != null) {
            result.add(best);
        }

        /*
         * Always terminate on the newest fix - that is the one the user cares
         * about when reading a trail.
         */
        LocationPoint last = points.get(points.size() - 1);

        if (last != null &&
            (result.isEmpty() ||
             result.get(result.size() - 1) != last)) {

            result.add(last);
        }

        return result;
    }

    private static List<LocationPoint> cap(List<LocationPoint> points) {

        int size = points.size();

        if (size <= MAX_RENDERED_POINTS) {
            return points;
        }

        int stride = (size + MAX_RENDERED_POINTS - 1) / MAX_RENDERED_POINTS;

        ArrayList<LocationPoint> result = new ArrayList<LocationPoint>();

        for (int i = 0; i < size; i += stride) {
            result.add(points.get(i));
        }

        LocationPoint last = points.get(size - 1);

        if (result.isEmpty() ||
            result.get(result.size() - 1) != last) {

            result.add(last);
        }

        return result;
    }

    private static boolean moreAccurate(LocationPoint candidate, LocationPoint current) {

        if (candidate.accuracy <= 0) {
            return false;
        }

        return current.accuracy <= 0 ||
            candidate.accuracy < current.accuracy;
    }
}
