package edu.gatech.cs2340.trydent.math.curve;

import java.util.Arrays;

import edu.gatech.cs2340.trydent.internal.TrydentInternalException;

/**
 * TimingHandler which permits arbitrary durations for each span. (Advanced
 * functionality).
 */
public class ArbitraryTiming implements SplineTimingHandler {
    private double[] durations;
    private double[] times;
    private double totalDuration = 0;

    /**
     * Creates a new ArbitraryTiming object with the given array of durations,
     * such that the duration of the ith span is the ith value of the array.
     * (Advanced functionality).
     *
     * @param durations
     *            array of durations as described above
     */
    public ArbitraryTiming(double[] durations) {
        this.durations = durations;
        this.times = new double[durations.length];
        int i = 0;
        for (double d : durations) {
            this.times[i++] = this.totalDuration;
            this.totalDuration += d;
        }
    }

    @Override
    public SpanTime transformTime(double time) {
        if (time < 0)
            time = 0;
        if (time > totalDuration)
            time = totalDuration;
        int left = 0;
        int right = times.length - 1;
        int index = (int) (time * (times.length - 1) / totalDuration);
        int iterations = 0;
        // While time is not between (times[index]) and (times[index] +
        // durations[index])
        while (time > times[index] + durations[index] || time < times[index]) {
            if (left == right && index == left) {
                throw new TrydentInternalException(
                        "Impossible timing occurred. This may indicate an internal logical programming error."
                                + "\nTime = " + time + "\nTimes = " + Arrays.toString(times) + "\nDurations = "
                                + Arrays.toString(durations));
            }
            // Time at index is too big
            if (times[index] > time) {
                right = index - 1;
            }
            // Time at index is too small
            if (times[index] + durations[index] < time) {
                left = index + 1;
            }
            if (left > right)
                throw new TrydentInternalException(
                        "Impossible timing occurred. This may indicate an internal logical programming error.");
            index = (left + right) / 2;

            iterations++;
            if (iterations > times.length + 1) {
                throw new TrydentInternalException(
                        "BinarySearch is taking longer than O(n+1), something is seriously wrong.\n" + "time=" + time
                                + ", left=" + left + ", right=" + right + ", index=" + index);
            }
        }
        return new SpanTime(index, (time - times[index]) / durations[index]);
    }
}
