/*
 * WorldEdit, a Minecraft world manipulation toolkit
 * Copyright (C) sk89q <http://www.sk89q.com>
 * Copyright (C) WorldEdit team and contributors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

// $Id$

package com.sk89q.worldedit.math.interpolation;

import com.sk89q.worldedit.math.Vector3;

import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Simple linear interpolation. Mainly used for testing.
 */
public class LinearInterpolation implements Interpolation {

    private List<Node> nodes;

    @Override
    public void setNodes(List<Node> nodes) {
        checkNotNull(nodes);

        this.nodes = nodes;
    }

    @Override
    public Vector3 getPosition(double position) {
        if (nodes == null) {
            throw new IllegalStateException("Must call setNodes first.");
        }

        if (position > 1) {
            return null;
        }

        position *= nodes.size() - 1;

        final int index1 = (int) Math.floor(position);
        final double remainder = position - index1;

        final Vector3 position1 = nodes.get(index1).getPosition();
        final Vector3 position2 = nodes.get(index1 + 1).getPosition();

        return position1.multiply(1.0 - remainder).add(position2.multiply(remainder));
    }

    /*
    Formula for position:
        p1*(1-t) + p2*t
    Formula for position in Horner/monomial form:
        (p2-p1)*t + p1
    1st Derivative:
        p2-p1
    2nd Derivative:
        0
    Integral:
        (p2-p1)/2*t^2 + p1*t + constant
    Integral in Horner form:
        ((p2-p1)/2*t + p1)*t + constant
    */

    @Override
    public Vector3 get1stDerivative(double position) {
        if (nodes == null) {
            throw new IllegalStateException("Must call setNodes first.");
        }

        if (position > 1) {
            return null;
        }

        position *= nodes.size() - 1;

        final int index1 = (int) Math.floor(position);

        final Vector3 position1 = nodes.get(index1).getPosition();
        final Vector3 position2 = nodes.get(index1 + 1).getPosition();

        return position2.subtract(position1);
    }

    @Override
    public double arcLength(double positionA, double positionB) {
        if (nodes == null) {
            throw new IllegalStateException("Must call setNodes first.");
        }

        if (positionA > positionB) {
            return arcLength(positionB, positionA);
        }

        positionA *= nodes.size() - 1;
        positionB *= nodes.size() - 1;

        final int indexA = (int) Math.floor(positionA);
        final double remainderA = positionA - indexA;

        final int indexB = (int) Math.floor(positionB);
        final double remainderB = positionB - indexB;

        return arcLengthRecursive(indexA, remainderA, indexB, remainderB);
    }

    /**
     * Assumes a < b.
     */
    private double arcLengthRecursive(int indexA, double remainderA, int indexB, double remainderB) {
        switch (indexB - indexA) {
            case 0:
                return arcLengthRecursive(indexA, remainderA, remainderB);

            case 1:
                // This case is merely a speed-up for a very common case
                return arcLengthRecursive(indexA, remainderA, 1.0)
                        + arcLengthRecursive(indexB, 0.0, remainderB);

            default:
                return arcLengthRecursive(indexA, remainderA, indexB - 1, 1.0)
                        + arcLengthRecursive(indexB, 0.0, remainderB);
        }
    }

    private double arcLengthRecursive(int index, double remainderA, double remainderB) {
        final Vector3 position1 = nodes.get(index).getPosition();
        final Vector3 position2 = nodes.get(index + 1).getPosition();

        return position1.distance(position2) * (remainderB - remainderA);
    }

    @Override
    public int getSegment(double position) {
        if (nodes == null) {
            throw new IllegalStateException("Must call setNodes first.");
        }

        if (position > 1) {
            return Integer.MAX_VALUE;
        }

        position *= nodes.size() - 1;

        return (int) Math.floor(position);
    }

}
