/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.nativelibs4java.opencl.util;
import org.apache.commons.math.complex.Complex;
import com.nativelibs4java.opencl.util.fft.*;
import com.nativelibs4java.opencl.*;
import com.nativelibs4java.opencl.CLPlatform.DeviceFeature;
import java.io.IOException;
import java.util.*;
import java.nio.*;
import org.apache.commons.math.transform.FastFourierTransformer;
import org.junit.Test;
import static org.junit.Assert.*;

/**
 *
 * @author ochafik
 */
public class DiscreteFourierTransformTest {

    static double[] complexToInterleavedDoubles(Complex[] complexInput) {
        int length = complexInput.length;
        double[] output = new double[length * 2];
        for (int i = 0; i < length; i++) {
            int o = i * 2;
            Complex c = complexInput[i];
            output[o] = c.getReal();
            output[o + 1] = c.getImaginary();
        }
        return output;
    }
    static Complex[] interleavedDoublesToComplex(double[] input) {
        int length = input.length / 2;
        Complex[] complexOutput = new Complex[length];
        for (int i = 0; i < length; i++) {
            int o = i * 2;
            complexOutput[i] = new Complex(input[o], input[o + 1]);
        }
        return complexOutput;
    }
    static Complex[] realDoublesToComplex(double[] input) {
        int length = input.length;
        Complex[] complexOutput = new Complex[length];
        for (int i = 0; i < length; i++) {
            complexOutput[i] = new Complex(input[i], 0);
        }
        return complexOutput;
    }
    /*
    static void assertArrayEquals(String title, Object a, Object b) {
        if (a instanceof double[]) {
            double[] aa = (double[])a, bb = (double[])b;
            for (int i = 0; i < aa.length; i++) {
                if (Math.abs(aa[i] - bb[i]) > precisionDouble)
                    throw new RuntimeException("[" + title + "] Values different at index " + i + " : " + aa[i] + " vs. " + bb[i] + " !");
            }
        } else if (a instanceof float[]) {
            float[] aa = (float[])a, bb = (float[])b;
            for (int i = 0; i < aa.length; i++) {
                if (Math.abs(aa[i] - bb[i]) > precisionFloat)
                    throw new RuntimeException("[" + title + "] Values different at index " + i + " : " + aa[i] + " vs. " + bb[i] + " !");
            }
        }
    }*/
    static final float precisionFloat = 1e-3f, precisionInverseFloat = 1e-2f;
    static final double precisionDouble = 1e-10, precisionInverseDouble = 1e-5;
    
    static Collection<double[]> createTestDoubleInputs() {
        Collection<double[]> ret = new ArrayList<double[]>();
        for (int n : new int[] { 1024, 1, 2, 4, 8, 16 }) {
            double[] in = new double[2 * n];

            for (int i = 0; i < n; i++) {
                in[i * 2] = 1 / (double)(i + 1);
                in[i * 2 + 1] = 0;
            }
            ret.add(in);
        }
        return ret;
    }
    static Collection<float[]> createTestFloatInputs() {
    		Collection<float[]> ret = new ArrayList<float[]>();
    		for (double[] in : createTestDoubleInputs())
    			ret.add(toFloat(in));
    		return ret;
    }

    static float[] toFloat(double[] in) {
        float[] out = new float[in.length];
        for (int i = 0; i < in.length; i++)
            out[i] = (float)in[i];

        return out;
    }
    static double[] scale(double factor, double[] in) {
        double[] out = new double[in.length];
        for (int i = 0; i < in.length; i++)
            out[i] = factor * in[i];

        return out;
    }

    public void testDoubleValues(String title, Transformer<Double, DoubleBuffer, double[]> tr) {
        FastFourierTransformer apache = new FastFourierTransformer();
        CLQueue queue = tr.getContext() == null ? null : tr.getContext().createDefaultOutOfOrderQueueIfPossible();
        for (double[] data : createTestDoubleInputs()) {
            double[] expected = complexToInterleavedDoubles(apache.transform(interleavedDoublesToComplex(data)));
            assertArrayEquals(title + " (n = " + (data.length / 2) + ")", expected, tr.transform(queue, data, false), precisionDouble);
        }
    }
    public void testFloatValues(String title, Transformer<Float, FloatBuffer, float[]> tr) {
        FastFourierTransformer apache = new FastFourierTransformer();
        CLQueue queue = tr.getContext() == null ? null : tr.getContext().createDefaultOutOfOrderQueueIfPossible();
        for (double[] data : createTestDoubleInputs()) {
            float[] dataf = toFloat(data);
            double[] expected = complexToInterleavedDoubles(apache.transform(interleavedDoublesToComplex(data)));
            assertArrayEquals(title + " (n = " + (data.length / 2) + ")", toFloat(expected), tr.transform(queue, dataf, false), precisionFloat);
        }
    }

    @Test
    public void testDoubleFFTValues() throws IOException {
        testDoubleValues("Double FFT", new DoubleFFTPow2());
    }
    @Test
    public void testFloatFFTValues() throws IOException {
        testFloatValues("Float FFT", new FloatFFTPow2());
    }
    @Test
    public void testDoubleDFTValues() throws IOException {
        testDoubleValues("Double DFT", new DoubleDFT());
    }
    @Test
    public void testFloatDFTValues() throws IOException {
        testFloatValues("Float DFT", new FloatDFT());
    }
    @Test
    public void testDoubleDFTInverse() throws IOException, CLException {
        testDoubleTransformer("Double FFT Inverse", new DoubleDFT());
    }
    @Test
    public void testFloatDFTInverse() throws IOException, CLException {
        testFloatTransformer("Float DFT Inverse", new FloatDFT());
    }
    @Test
    public void testDoubleFFTInverse() throws IOException, CLException {
        testDoubleTransformer("Double FFT Inverse", new DoubleFFTPow2());
    }
    @Test
    public void testFloatFFTInverse() throws IOException, CLException {
        testFloatTransformer("Float FFT Inverse", new FloatFFTPow2());
    }
    void testDoubleTransformer(String title, Transformer<Double, DoubleBuffer, double[]> t) throws IOException, CLException {
        CLQueue queue = t.getContext().createDefaultOutOfOrderQueueIfPossible();
        //System.out.println("Context: " + t.getContext());
        for (double[] in : createTestDoubleInputs()) {
            double[] out = t.transform(queue, in, false);
            double[] back = t.transform(queue, out, true);
            assertArrayEquals(title + " (n = " + (in.length / 2) + ")", in, back, precisionInverseDouble);
        }
    }
    void testFloatTransformer(String title, Transformer<Float, FloatBuffer, float[]> t) throws IOException, CLException {
        CLQueue queue = t.getContext().createDefaultOutOfOrderQueueIfPossible();
        //System.out.println("Context: " + t.getContext());
        for (float[] in : createTestFloatInputs()) {
            float[] out = t.transform(queue, in, false);
            float[] back = t.transform(queue, out, true);
            assertArrayEquals(title + " (n = " + (in.length / 2) + ")", in, back, precisionInverseFloat);
        }
    }
}
