package net.heartshield.signal;

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

/**
 * Numerical utilities on double[] -- compare numpy functions.
 *
 * @author David Madl (git@abanbytes.eu)
 * @date 2017-03-19
 */
public class DVec {
    /** return zero-array of specified length */
    public static double[] zeros(int len) {
        return new double[len];
    }
    /** return one-array of specified length */
    public static double[] ones(int len) {
        double[] a = new double[len];
        for(int i = 0; i < len; i++)
            a[i] = 1.0;
        return a;
    }

    public static double[] arange(double start, double stop) {
        return arange(start, stop, 1.0);
    }

    /** evenly spaced values within the interval [start, stop) */
    public static double[] arange(double start, double stop, double step) {
        int len = (int) ((stop - start) / step);
        if(len <= 0)
            return new double[0];

        double[] a = new double[len];
        int i = 0;
        for(double d = start; i < len; d += step) {
            a[i] = d;
            i++;
        }
        return a;
    }

    public static double[] linspace(double start, double stop, int num) {
        return linspace(start, stop, num, true);
    }

    /** `num` evenly spaced numbers over interval [start,stop] with endpoint=true or [start,stop) with endpoint=false */
    public static double[] linspace(double start, double stop, int num, boolean endpoint) {
        if(num < 0)
            throw new IllegalArgumentException("num must be >= 0");

        int end = endpoint ? num : (num-1);
        double step = (stop - start) / (double) end;

        double[] a = new double[num];
        double d = start;

        for(int i = 0; i < num; i++) {
            a[i] = d;
            d += step;
        }
        return a;
    }

    /** concatenate two or more arrays */
    public static double[] stack(double[]... arrs) {
        int len = 0;
        for(double[] x : arrs)
            len += x.length;

        double[] a = new double[len];

        int i = 0;
        for(double[] x : arrs) {
            System.arraycopy(x, 0, a, i, x.length);
            i += x.length;
        }

        return a;
    }

    /** returns the sorted version of x */
    public static double[] sort(double[] x) {
        double[] y = Arrays.copyOf(x, x.length);
        Arrays.sort(y);
        return y;
    }

    private static void throwIfOutOfBounds(double[] x, int start, int end) {
        int len = end - start;

        if(x.length == 0) {
            if(start != 0 || end != 0)
                throw new IndexOutOfBoundsException("out of bounds on empty Vec");
        } else {
            // getting a zero-length slice at the end is still OK
            if(start > x.length || start < 0)
                throw new IndexOutOfBoundsException("start out of bounds");
            if(end > x.length || end < 0)
                throw new IndexOutOfBoundsException("end out of bounds");
        }

        if(len < 0)
            throw new IndexOutOfBoundsException("end >= start was violated");
    }

    private static void throwIfLengthMismatch(double[] x, double[] y) {
        if(x.length != y.length)
            throw new IllegalArgumentException("x.length=" + x.length + " != y.length=" + y.length);
    }

    /** get a slice of the array[start,end) (deep copy) */
    public static double[] get(double[] x, int start, int end) {
        int len = end - start;
        throwIfOutOfBounds(x, start, end);
        double[] a = new double[len];
        System.arraycopy(x, start, a, 0, len);
        return a;
    }

    /** element-wise addition */
    public static double[] add(double[] x, double[] y) {
        throwIfLengthMismatch(x, y);
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = x[i] + y[i];
        return a;
    }

    /** element-wise addition of a scalar */
    public static double[] add(double[] x, double y) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = x[i] + y;
        return a;
    }

    /** element-wise subtraction of a scalar */
    public static double[] sub(double[] x, double y) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = x[i] - y;
        return a;
    }

    /** element-wise subtraction */
    public static double[] sub(double[] x, double[] y) {
        throwIfLengthMismatch(x, y);
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = x[i] - y[i];
        return a;
    }

    /** element-wise multiplication with a scalar */
    public static double[] mul(double[] x, double y) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = x[i] * y;
        return a;
    }

    /** element-wise multiplication */
    public static double[] mul(double[] x, double[] y) {
        throwIfLengthMismatch(x, y);
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = x[i] * y[i];
        return a;
    }

    /** element-wise comparison, without tolerance -> boolean */
    public static int[] eq(double[] x, double[] y) {
        throwIfLengthMismatch(x, y);
        int[] a = new int[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = (x[i] == y[i]) ? 1 : 0;
        return a;
    }

    /** element-wise comparison, without tolerance -> boolean */
    public static int[] lt(double[] x, double[] y) {
        throwIfLengthMismatch(x, y);
        int[] a = new int[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = (x[i] < y[i]) ? 1 : 0;
        return a;
    }

    /** element-wise comparison, with tolerance -> boolean */
    public static int[] eqt(double[] x, double[] y, double tol) {
        throwIfLengthMismatch(x, y);
        int[] a = new int[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = (Math.abs(x[i] - y[i]) < tol) ? 1 : 0;
        return a;
    }

    /** element-wise comparison to a scalar, without tolerance -> boolean */
    public static int[] eq(double[] x, double y) {
        int[] a = new int[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = (x[i] == y) ? 1 : 0;
        return a;
    }

    /**
     * element-wise signum function;
     * * zero if the argument is zero,
     * * 1.0 if the argument is greater than zero,
     * * -1.0 if the argument is less than zero.
     */
    public static double[] sign(double[] x) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = Math.signum(x[i]);
        return a;
    }

    /** element-wise rounding */
    public static double[] round(double[] x) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = Math.round(x[i]);
        return a;
    }

    /** element-wise abs */
    public static double[] abs(double[] x) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = Math.abs(x[i]);
        return a;
    }


    /** element-wise sin */
    public static double[] sin(double[] x) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = Math.sin(x[i]);
        return a;
    }


    /** element-wise exp */
    public static double[] exp(double[] x) {
        double[] a = new double[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = Math.exp(x[i]);
        return a;
    }

    /** numerical differences */
    public static double[] diff(double[] x) {
        if(x.length == 0)
            throw new IllegalArgumentException("cannot diff() empty array");
        double[] a = new double[x.length - 1];
        for(int i = 0; i < x.length - 1; i++)
            a[i] = x[i+1] - x[i];
        return a;
    }

    /** cumulative sum */
    public static double[] cumsum(double[] x) {
        double[] a = new double[x.length];
        if(x.length > 0)
            a[0] = x[0];
        for(int i = 1; i < x.length; i++)
            a[i] = a[i-1] + x[i];
        return a;
    }

    /** sum of all elements */
    public static double sum(double[] x) {
        double s = 0.0;
        for(int i = 0; i < x.length; i++)
            s += x[i];
        return s;
    }

    /** max of all elements */
    public static double max(double[] x) {
        if(x.length == 0)
            throw new IllegalArgumentException("max is undefined for zero elements");
        double m = Double.MIN_VALUE;
        for(int i = 0; i < x.length; i++) {
            if(x[i] > m)
                m = x[i];
        }
        return m;
    }

    /** min of all elements */
    public static double min(double[] x) {
        if(x.length == 0)
            throw new IllegalArgumentException("max is undefined for zero elements");
        double m = Double.MAX_VALUE;
        for(int i = 0; i < x.length; i++) {
            if(x[i] < m)
                m = x[i];
        }
        return m;
    }

    /** mean of elements */
    public static double mean(double[] x) {
        if(x.length == 0)
            throw new IllegalArgumentException("mean is undefined for zero elements");
        return sum(x) / x.length;
    }

    /** standard deviation of elements */
    public static double std(double[] x) {
        if(x.length == 0)
            throw new IllegalArgumentException("std is undefined for zero elements");
        double m = mean(x);
        double s = 0.0;
        for(int i = 0; i < x.length; i++)
            s += (x[i] - m) * (x[i] - m);
        return Math.sqrt(s / x.length);
    }

    /** indexing x by idx */
    public static double[] index(double[] x, int[] idx) {
        double[] a = new double[idx.length];
        for(int i = 0; i < idx.length; i++)
            a[i] = x[idx[i]];
        return a;
    }

    /** integer downsampling */
    public static double[] downsample(double[] x, int ratio) {
        double[] a = new double[x.length / ratio];
        if(x.length > 0)
            a[0] = x[0];
        for(int i = 1; i < a.length; i++)
            a[i] = x[i*ratio];
        return a;
    }

    public static String toString(double[] x) {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        for(int i = 0; i < x.length; i++) {
            if(i != 0)
                sb.append(", ");
            sb.append(Double.toString(x[i]));
        }
        sb.append("}");
        return sb.toString();
    }

    /** convert to int[] */
    public static int[] toInteger(double[] x) {
        int[] a = new int[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = (int) x[i];
        return a;
    }

    /** convert to short[] */
    public static short[] toShort(double[] x) {
        short[] a = new short[x.length];
        for(int i = 0; i < x.length; i++)
            a[i] = (short) x[i];
        return a;
    }

    /** convert List<Double> into double[] */
    public static double[] toArray(List<Double> lst) {
        double[] a = new double[lst.size()];
        int l = lst.size();
        for(int i = 0; i < l; i++)
            a[i] = lst.get(i);
        return a;
    }

    /** convert double[] into List<Double> */
    public static List<Double> toList(double[] x) {
        List<Double> lst = new ArrayList<Double>(x.length);
        for(int i = 0; i < x.length; i++)
            lst.add(x[i]);
        return lst;
    }
}
