/*******************************************************************************
 * JANNLab Neural Network Framework for Java
 * Copyright (C) 2012-2013 Sebastian Otte
 *
 * 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 <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package de.jannlab.misc;

import java.io.StringWriter;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Random;

/**
 * This class provides some methods for handling arrays
 * of doubles.
 * <br></br>
 * @author Sebastian Otte
 */
public final class DoubleTools {

    public static void copy(
            final double[] data,
            final int dataoffset,
            final double[] result,
            final int resultoffset,
            final int size
    ) {
        int o1 = dataoffset;
        int o2 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o2++] = data[o1++];
        }
    }

    public static void copy(
            final double[] data,
            final int dataoffset,
            final int[] dataselection,
            final double[] result,
            final int resultoffset
    ) {
        int o2 = resultoffset;
        //
        for (int i = 0; i < dataselection.length; i++) {
            result[o2++] = data[dataoffset + dataselection[i]];
        }
    }

    public static void copy(
            final double[] data,
            final int dataoffset,
            final double[] result,
            final int resultoffset,
            final int[] resultselection
    ) {
        int o1 = dataoffset;
        //
        for (int i = 0; i < resultselection.length; i++) {
            result[resultoffset + resultselection[i]] = data[o1++];
        }
    }
    
    public static void copy(
            final double[] data,
            final int dataoffset,
            final int datastep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size
    ) {
        int o1 = dataoffset;
        int o2 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o2] = data[o1];
            o1 += datastep;
            o2 += resultstep;
        }
    }
    
    
    public static void fill(
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size,
            final Random rnd,
            double lbd,
            double ubd
    ) {
        if (lbd > ubd) {
            final double temp = lbd;
            lbd = ubd;
            ubd = temp;
        }
        final double width = ubd - lbd;
        //
        int o = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            final double value = (rnd.nextDouble() * width) + lbd; 
            result[o] = value;
            o += resultstep;
        }
    }   
    
    public static void fill(
            final double[] result,
            final int resultoffset,
            final int size,
            final Random rnd,
            double lbd,
            double ubd
    ) {
        if (lbd > ubd) {
            final double temp = lbd;
            lbd = ubd;
            ubd = temp;
        }
        final double width = ubd - lbd;
        //
        int o = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            final double value = (rnd.nextDouble() * width) + lbd; 
            result[o++] = value;
        }
    }    
    
    public static void fill(
            final double[] result,
            final int resultoffset,
            final int size,
            final double value
    ) {
        int o = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o++] = value;
        }
    }    
    
    public static void fill(
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size,
            final double value
    ) {
        int o = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o] = value;
            o += resultstep;
        }
    }    
    
    public static void threshold(
            final double[] data,
            final int dataoffset,
            final double[] result,
            final int resultoffset,
            final int size,
            final double threshold
    ) {
        int o1 = dataoffset;
        int o2 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o2++] = (data[o1++] > threshold)?(1.0):(0.0);
        }
    }
    
    public static void threshold(
            final double[] data,
            final int dataoffset,
            final int datastep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size,
            final double threshold
    ) {
        int o1 = dataoffset;
        int o2 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o2] = (data[o1] > threshold)?(1.0):(0.0);
            o1 += datastep;
            o2 += resultstep;
        }
    }
    

    

        
    public static void map(
            final double[] data,
            final int dataoffset,
            final double[] result,
            final int resultoffset,
            final int size,
            final UnaryFunctionDouble f
    ) {
        int o1 = dataoffset;
        int o2 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o2++] = f.perform(data[o1++]);
        }
    }
    
    public static void map(
            final double[] data,
            final int dataoffset,
            final int datastep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size,
            final UnaryFunctionDouble f
    ) {
        int o1 = dataoffset;
        int o2 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o2] = f.perform(data[o1]);
            o1 += datastep;
            o2 += resultstep;
        }
    }
    
    public static void map(
            final double[] first,
            final int firstoffset,
            final double[] second,
            final int secondoffset,
            final double[] result,
            final int resultoffset,
            final int size,
            final BinaryFunctionDouble f
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3++] = f.perform(first[o1++], second[o2++]);
        }
    }

    public static void map(
            final double[] first,
            final int firstoffset,
            final int firststep,
            final double[] second,
            final int secondoffset,
            final int secondstep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size,
            final BinaryFunctionDouble f
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3] = f.perform(first[o1], second[o2]);
            o1 += firststep;
            o2 += secondstep;
            o3 += resultstep;
        }
    }

    public static double absSum(
            final double[] data,
            final int offset,
            final int size
    ) {
        int o = offset;
        //
        double value = 0.0;
        //
        for (int i = 0; i < size; i++) {
            final double x = data[o++];
            value += Math.abs(x);
        }
        //
        return value;
    }
    
    public static double absSum(
            final double[] data,
            final int offset,
            final int size,
            final int step
    ) {
        int o = offset;
        //
        double value = 0.0;
        //
        for (int i = 0; i < size; i++) {
            final double x = data[o];
            value += Math.abs(x);
            o += step;
        }
        //
        return value;
    }
    
    public static double meanSquareSum(
            final double[] data,
            final int offset,
            final int size
    ) {
        int o = offset;
        //
        double value = 0.0;
        //
        for (int i = 0; i < size; i++) {
            final double x = data[o++];
            value += (x * x);
        }
        //
        return value / ((double)size);
    }
    
    public static double squareSum(
            final double[] data,
            final int offset,
            final int size
    ) {
        int o = offset;
        //
        double value = 0.0;
        //
        for (int i = 0; i < size; i++) {
            final double x = data[o++];
            value += (x * x);
        }
        //
        return value;
    }
    
    public static double squareSum(
            final double[] data,
            final int offset,
            final int size,
            final int step
    ) {
        int o = offset;
        //
        double value = 0.0;
        //
        for (int i = 0; i < size; i++) {
            final double x = data[o];
            value += (x * x);
            o += step;
        }
        //
        return value;
    }
    
    public static double sum(
            final double[] data,
            final int offset,
            final int size
    ) {
        int o = offset;
        //
        double value = 0.0;
        //
        for (int i = 0; i < size; i++) {
            value += data[o++];
        }
        //
        return value;
    }

    public static double sum(
            final double[] data,
            final int offset,
            final int size,
            final int step
    ) {
        int o = offset;
        //
        double value = 0.0;
        //
        for (int i = 0; i < size; i++) {
            value += data[o];
            o += step;
        }
        //
        return value;
    }
    
    public static double mul(
            final double[] data,
            final int offset,
            final int size
    ) {
        int o = offset;
        //
        double value = 1.0;
        //
        for (int i = 0; i < size; i++) {
            value *= data[o++];
        }
        //
        return value;
    }

    public static double mul(
            final double[] data,
            final int offset,
            final int size,
            final int step
    ) {
        int o = offset;
        //
        double value = 1.0;
        //
        for (int i = 0; i < size; i++) {
            value *= data[o];
            o += step;
        }
        //
        return value;
    }
    
    public static void dot(
            final double[] first,
            final int firstoffset,
            final double[] second,
            final int secondoffset,
            final int size,
            final double[] result,
            final int resultoffset
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        double sum = 0;
        //
        for (int i = 0; i < size; i++) {
            sum += (first[o1++] * second[o2++]);
        }
        //
        result[o3] = sum;
    }
    
    public static void dot(
            final double[] first,
            final int firstoffset,
            final int firststep,
            final double[] second,
            final int secondoffset,
            final int secondstep,
            final int size,
            final double[] result,
            final int resultoffset
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        double sum = 0;
        //
        for (int i = 0; i < size; i++) {
            sum += (first[o1] * second[o2]);
            o1 += firststep;
            o2 += secondstep;
        }
        //
        result[o3] = sum;
    }
    
    public static void add(
            final double[] first,
            final int firstoffset,
            final double[] second,
            final int secondoffset,
            final double[] result,
            final int resultoffset,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3++] = first[o1++] + second[o2++];
        }
    }
    
    public static void add(
            final double[] first,
            final int firstoffset,
            final int firststep,
            final double[] second,
            final int secondoffset,
            final int secondstep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3] = first[o1] + second[o2];
            o1 += firststep;
            o2 += secondstep;
            o3 += resultstep;
        }
    }
    
    public static void sub(
            final double[] first,
            final int firstoffset,
            final double[] second,
            final int secondoffset,
            final double[] result,
            final int resultoffset,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3++] = first[o1++] - second[o2++];
        }
    }
    
    public static void sub(
            final double[] first,
            final int firstoffset,
            final int firststep,
            final double[] second,
            final int secondoffset,
            final int secondstep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3] = first[o1] - second[o2];
            o1 += firststep;
            o2 += secondstep;
            o3 += resultstep;
        }
    }
    

    
    public static void mul(
            final double[] first,
            final int firstoffset,
            final double[] second,
            final int secondoffset,
            final double[] result,
            final int resultoffset,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            final double v1 = first[o1++];
            final double v2 = second[o2++];
            //
            final double value = v1 * v2; 
            result[o3++] = value;
        }
    }
    
    public static void mul(
            final double[] first,
            final int firstoffset,
            final int firststep,
            final double[] second,
            final int secondoffset,
            final int secondstep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3] = first[o1] * second[o2];
            o1 += firststep;
            o2 += secondstep;
            o3 += resultstep;
        }
    }
    
    public static void div(
            final double[] first,
            final int firstoffset,
            final double[] second,
            final int secondoffset,
            final double[] result,
            final int resultoffset,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3++] = first[o1++] / second[o2++];
        }
    }
    
    public static void div(
            final double[] first,
            final int firstoffset,
            final int firststep,
            final double[] second,
            final int secondoffset,
            final int secondstep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            result[o3] = first[o1] / second[o2];
            o1 += firststep;
            o2 += secondstep;
            o3 += resultstep;
        }
    }
    
    public static void divSafe(
            final double[] first,
            final int firstoffset,
            final double[] second,
            final int secondoffset,
            final double[] result,
            final int resultoffset,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            final double value = first[o1++];
            final double quo = second[o2++];
            result[o3++] = (quo == 0)?(value):(value / quo); 
        }
    }
    
    public static void divSafe(
            final double[] first,
            final int firstoffset,
            final int firststep,
            final double[] second,
            final int secondoffset,
            final int secondstep,
            final double[] result,
            final int resultoffset,
            final int resultstep,
            final int size
    ) {
        int o1 = firstoffset;
        int o2 = secondoffset;
        int o3 = resultoffset;
        //
        for (int i = 0; i < size; i++) {
            final double value = first[o1];
            final double quo = second[o2];
            result[o3] = (quo == 0)?(value):(value / quo); 
            o1 += firststep;
            o2 += secondstep;
            o3 += resultstep;
        }
    }

    public static String asString(final double[] data, final int decimals) {
        return asString(data, 0, 1, data.length, decimals);
    }

    public static String asString(final double value, final int decimals) {
        //
        DecimalFormat f = new DecimalFormat();
        f.setDecimalSeparatorAlwaysShown(true);
        f.setMaximumFractionDigits(decimals);
        f.setMinimumFractionDigits(decimals);
        f.setGroupingUsed(false);
        //
        f.setDecimalFormatSymbols(new DecimalFormatSymbols() {
            private static final long serialVersionUID = -2464236658633690492L;
            public char getGroupingSeparator() { return ' '; }
            public char getDecimalSeparator() { return '.'; }
        });
        return f.format(value);    
    }
    
    public static String asString(
            final double[] data, 
            final String delimiter, 
            final int decimals
    ) {
        return asString(data, delimiter, 0, 1, data.length, decimals);
    }

    public static String asString(
            final double[] data, final int offset, final int size, final int decimals
    ) {
        return asString(data, offset, 1, size, decimals);
    }

    public static String asString(
            final double[] data, 
            final String delimiter,
            final int offset, final int size, final int decimals
    ) {
        return asString(data, delimiter, offset, 1, size, decimals);
    }
    
    public static String asString(
            final double[] data, final int offset, 
            final int step, final int size,
            final int decimals
    ) {
        return asString(data, ", ", offset, step, size, decimals);
    }
    
    public static String asString(
            final double[] data, 
            final String delimiter,
            final int offset, 
            final int step, final int size,
            final int decimals
    ) {
        StringWriter out = new StringWriter();
        
        DecimalFormat f = new DecimalFormat();
        f.setDecimalSeparatorAlwaysShown(true);
        f.setMaximumFractionDigits(decimals);
        f.setMinimumFractionDigits(decimals);
        f.setGroupingUsed(false);
        
        f.setDecimalFormatSymbols(new DecimalFormatSymbols() {
            private static final long serialVersionUID = -2464236658633690492L;
            public char getGroupingSeparator() { return ' '; }
            public char getDecimalSeparator() { return '.'; }
        });
            
        int o = offset;
        for (int i = 0; i < size; i++) {
            if (i > 0) out.append(delimiter);
            out.append(f.format(data[o]));
            o += step;
        }
        return out.toString();
    }
    
    
    public static double[] tail(
            final double[] data,
            final int size
    ) {
        double[] result = new double[size];
        int offset = data.length - 1;
        for (int i = size - 1; i >= 0; i--) {
            if (offset < 0) break;
            result[i] = data[offset--];
        }
        return result;
    }
    
    
    public static double[] merge(final double[] ...arrays) {
        int length = 0;
        for(int i = 0; i < arrays.length; i++) {
            length += arrays[i].length;
        }
        double[] result = new double[length];
        int offset = 0;
        for(int i = 0; i < arrays.length; i++) {
            final int l = arrays[i].length;
            copy(arrays[i], 0, result, offset, l);
            offset += l;
        }
        return result;
    }
    
    
    
    
    
    
}