/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.math;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

/**
 * @author stephane
 */
public class Interpolator
{
    private static double[][] pointsToXY(List<Point> points)
    {
        final int len = points.size();
        final double[][] xy = new double[2][len];
        final double[] x = xy[0];
        final double[] y = xy[1];

        for (int i = 0; i < len; i++)
        {
            final Point p = points.get(i);
            x[i] = p.x;
            y[i] = p.y;
        }

        return xy;
    }

    private static double[] prepareYInterpolation(double[] x, double[] y, double xinc)
    {
        if ((x.length == 0) || (y.length == 0))
            throw new IllegalArgumentException("x[] and y[] should not be empty.");
        if (x.length != y.length)
            throw new IllegalArgumentException("x[] and y[] should have the same length.");
        if (xinc == 0)
            throw new IllegalArgumentException("step must be > 0");

        return new double[(int) ((x[x.length - 1] - x[0]) / xinc) + 1];
    }

    /**
     * Return Y linear interpolated coordinates from specified points and given X increment
     */
    public static double[] doYLinearInterpolation(List<Point> points, double xinc)
    {
        final double[][] xy = pointsToXY(points);
        return doYLinearInterpolation(xy[0], xy[1], xinc);
    }

    /**
     * Return Y linear interpolated coordinates from specified points and given X increment
     */
    public static double[] doYLinearInterpolation(double[] x, double[] y, double xinc)
    {
        final double[] result = prepareYInterpolation(x, y, xinc);
        final int len = result.length;

        if (len == 1)
            result[0] = x[0];
        else
        {
            final int xlen = x.length - 1;
            int index = 0;
            int offset = 0;
            double xvalue = x[0];
            double yvalue = y[0];
            double yinc = 0;

            while (offset < len)
            {
                while ((index < xlen) && (xvalue >= x[index]))
                {
                    index++;
                    final double dx = x[index] - xvalue;

                    if (dx != 0)
                        yinc = (y[index] - yvalue) / dx;
                    else
                        yinc = 0;
                }

                result[offset++] = yvalue;
                yvalue += yinc;
                xvalue += xinc;
            }
        }

        return result;
    }

    /**
     * Return Y spline interpolated coordinates from specified points and given X increment
     */
    public static double[] doYSplineInterpolation(ArrayList<Point> points, double xstep)
    {
        final double[][] xy = pointsToXY(points);
        return doYSplineInterpolation(xy[0], xy[1], xstep);
    }

    /**
     * Return Y spline interpolated coordinates from specified points and given X increment.<br>
     * Not yet implemented !
     */
    public static double[] doYSplineInterpolation(double[] x, double[] y, double xstep)
    {
        final double[] result = prepareYInterpolation(x, y, xstep);
        final int len = result.length;

        if (len > 1)
        {

        }

        return result;
    }

    /**
     * Do linear interpolation from start to end with specified increment step
     */
    public static double[] doLinearInterpolation(double start, double end, double step)
    {
        int size;

        if (step == 0)
            size = 1;
        else
            size = (int) ((end - start) / step) + 1;

        // size should be at least 1
        if (size < 1)
            size = 1;

        final double[] result = new double[size];

        double value = start;
        for (int i = 0; i < size; i++)
        {
            result[i] = value;
            value += step;
        }

        return result;
    }

    /**
     * Do linear interpolation from start to end with specified size (step number)
     */
    public static double[] doLinearInterpolation(double start, double end, int size)
    {
        if (size < 1)
            return null;

        // special case
        if (size == 1)
        {
            final double[] result = new double[size];
            result[0] = end;
            return result;
        }

        return doLinearInterpolation(start, end, (end - start) / (size - 1));
    }

    /**
     * Do logarithmic interpolation from start to end with specified size (step number)
     */
    public static double[] doLogInterpolation(double start, double end, int size)
    {
        // get linear interpolation
        final double[] result = doLinearInterpolation(start, end, size);

        // define input and output scaler
        final Scaler scalerIn = new Scaler(start, end, 2, 20, true, true);
        final Scaler scalerOut = new Scaler(Math.log(2), Math.log(20), start, end, true, true);

        final int len = result.length;

        // log scaling
        for (int i = 0; i < len; i++)
            result[i] = scalerOut.scale(Math.log(scalerIn.scale(result[i])));

        return result;
    }

    /**
     * Do exponential interpolation from start to end with specified size (step number)
     */
    public static double[] doExpInterpolation(double start, double end, int size)
    {
        // get linear interpolation
        final double[] result = doLinearInterpolation(start, end, size);

        // define input and output scaler
        final Scaler scalerIn = new Scaler(start, end, 0, 2, false, true);
        final Scaler scalerOut = new Scaler(Math.exp(0), Math.exp(2), start, end, false, true);

        final int len = result.length;

        // exp scaling
        for (int i = 0; i < len; i++)
            result[i] = scalerOut.scale(Math.exp(scalerIn.scale(result[i])));

        return result;
    }

}