package net.heartshield.signal;

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

/**
 * Numerical utilities on int[] -- compare numpy functions.
 *
 * @author David Madl (git@abanbytes.eu)
 * @date 2017-03-19
 */
public class IVec {
    /** return zero-array of specified length */
    public static int[] zeros(int len) {
        return new int[len];
    }

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

    public static int[] arange(int start, int stop) {
        return arange(start, stop, 1);
    }

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

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

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

        int[] a = new int[len];

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

        return a;
    }

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

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

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

    /** element-wise comparison to a scalar -> boolean */
    public static int[] gt(int[] x, int 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 comparison to a scalar -> boolean */
    public static int[] lt(int[] x, int 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 comparison to a scalar -> boolean */
    public static int[] ge(int[] x, int 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 comparison -> boolean */
    public static int[] eq(int[] x, int[] 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;
    }

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

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

    /** array that has ones at `idx` locations and zeros otherwise. */
    public static int[] dirac(int len, int[] idx) {
        int[] a = new int[len];
        for(int i = 0; i < idx.length; i++)
            a[idx[i]] = 1;
        return a;
    }

    /** true if array is purely ones, i.e. x[i] == 1 for all i */
    public static boolean all(int[] x) {
        for(int i = 0; i < x.length; i++)
            if(x[i] != 1)
                return false;
        return true;
    }

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

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

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

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

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

    /** array of indices i where x[i] == 1 */
    public static int[] where(int[] x) {
        int nnz = 0;

        int l = x.length;
        for(int i = 0; i < l; i++)
            if(x[i] == 1) nnz++;

        int[] a = new int[nnz];

        int j = 0;
        for(int i = 0; i < l; i++)
            if(x[i] == 1)
                a[j++] = i;

        return a;
    }

    private static void throwIfOutOfBounds(int[] 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 {
            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");
    }

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