/**
 * Copyright (C) 2010-14 pvmanager developers. See COPYRIGHT.TXT
 * All rights reserved. Use is subject to license terms. See LICENSE.TXT
 */
package org.epics.pvmanager.sim;

import org.epics.util.array.ArrayDouble;
import org.epics.util.array.ArrayInt;
import org.epics.util.array.ListDouble;
import org.epics.vtype.VDoubleArray;
import org.epics.vtype.ValueFactory;
import static org.epics.vtype.ValueFactory.*;
import org.epics.util.time.Timestamp;
import org.epics.vtype.ValueUtil;

/**
 * Function to simulate a 2D waveform containing a sine wave.
 *
 * @author carcassi
 */
public class Square2DWaveform extends SimFunction<VDoubleArray> {

    private final double periodInSeconds;
    private final double wavelengthInSamples;
    private final int xSamples;
    private final int ySamples;
    private final double angle;
    private Timestamp initialReference;

    /**
     * Creates sine wave of 100 samples, with period of 1 second, wavelength of
     * 100 samples along the x axis, updating at 10 Hz.
     */
    public Square2DWaveform() {
        this(1.0, 100.0, 0.1);
    }
    
    /**
     * Creates sine wave of 100 samples, with given period and given wavelength of
     * 100 samples along the x axis, updating at given rate.
     *
     * @param periodInSeconds the period measured in seconds
     * @param wavelengthInSamples the wavelength measured in samples
     * @param updateRateInSeconds the update rate in seconds
     */
    public Square2DWaveform(Double periodInSeconds, Double wavelengthInSamples, Double updateRateInSeconds) {
        this(periodInSeconds, wavelengthInSamples, 100.0, updateRateInSeconds);
    }

    /**
     * Creates sine wave of 100 samples, with given period and given wavelength of
     * given number of samples along the x axis, updating at given rate.
     *
     * @param periodInSeconds the period measured in seconds
     * @param wavelengthInSamples the wavelength measured in samples
     * @param nSamples the number of samples
     * @param updateRateInSeconds the update rate in seconds
     */
    public Square2DWaveform(Double periodInSeconds, Double wavelengthInSamples, Double nSamples, Double updateRateInSeconds) {
        this(periodInSeconds, wavelengthInSamples, 0.0, nSamples, nSamples, updateRateInSeconds);
    }

    /**
     * Creates sine wave with given parameters.
     *
     * @param periodInSeconds the period measured in seconds
     * @param wavelengthInSamples the wavelength measured in samples
     * @param angle the direction of propagation for the wave
     * @param xSamples number of samples on the x direction
     * @param ySamples number of samples on the y direction
     * @param updateRateInSeconds the update rate in seconds
     */
    public Square2DWaveform(Double periodInSeconds, Double wavelengthInSamples, Double angle, Double xSamples, Double ySamples, Double updateRateInSeconds) {
        super(updateRateInSeconds, VDoubleArray.class);
        this.periodInSeconds = periodInSeconds;
        this.wavelengthInSamples = wavelengthInSamples;
        this.xSamples = xSamples.intValue();
        this.ySamples = ySamples.intValue();
        this.angle = angle;
        if (this.xSamples <= 0 || this.ySamples <= 0) {
            throw new IllegalArgumentException("Number of sample must be a positive integer.");
        }
    }

    private ListDouble generateNewValue(final double omega, final double t, double k) {
        double[] newArray = new double[xSamples*ySamples];
        double kx = Math.cos(angle * Math.PI / 180.0) * k;
        double ky = Math.sin(angle * Math.PI / 180.0) * k;
        for (int y = 0; y < ySamples; y++) {
            for (int x = 0; x < xSamples; x++) {
                double length = (omega * t + kx* x + ky * y) / (2 * Math.PI);
                double normalizedPositionInPeriod = length - (double) (long) length;
                if (normalizedPositionInPeriod < 0.5) {
                    newArray[y*xSamples + x] = 1.0;
                } else if (normalizedPositionInPeriod < 1.0) {
                    newArray[y*xSamples + x] = -1.0;
                } else {
                    newArray[y*xSamples + x] = 1.0;
                }
            }
        }
        return new ArrayDouble(newArray);
    }

    @Override
    VDoubleArray nextValue() {
        if (initialReference == null) {
            initialReference = lastTime;
        }
        double t = lastTime.durationFrom(initialReference).toSeconds();
        double omega = 2 * Math.PI / periodInSeconds;
        double k = 2 * Math.PI / wavelengthInSamples;
        double min = 1.0;
        double max = -1.0;
        double range = 0.0;
        return (VDoubleArray) ValueFactory.newVNumberArray(generateNewValue(omega, t, k), new ArrayInt(ySamples, xSamples),
                ValueUtil.defaultArrayDisplay(new ArrayInt(ySamples, xSamples)), alarmNone(),
                newTime(lastTime), newDisplay(min, min + range * 0.1, min + range * 0.2, "", Constants.DOUBLE_FORMAT,
                min + range * 0.8, min + range * 0.9, max, min, max));
    }
}

@@ -3447,66 +3447,13 @@
     
-double%5B%5D newArray = new double%5BxSamples*ySamples%5D;%0A       
+final
  dou
@@ -3500,32 +3500,38 @@
 0) * k;%0A        
+final 
 double ky = Math
@@ -3577,45 +3577,149 @@
     
-for (int y = 0; y %3C ySamples; y++) %7B%0A
+return new ListDouble() %7B%0A%0A            @Override%0A            public double getDouble(int index) %7B%0A                int x = index %25 xSamples;%0A 
     
@@ -3730,27 +3730,26 @@
     
-for (
+   
 int 
-x = 0; x %3C
+y = index /
  xSa
@@ -3758,15 +3758,8 @@
 les;
- x++) %7B
 %0A   
@@ -3998,34 +3998,14 @@
     
-newArray%5By*xSamples + x%5D =
+return
  1.0
@@ -4093,34 +4093,14 @@
     
-newArray%5By*xSamples + x%5D =
+return
  -1.
@@ -4151,34 +4151,14 @@
     
-newArray%5By*xSamples + x%5D =
+return
  1.0
@@ -4191,24 +4191,29 @@
       %7D%0A
+%0A    
         
 %7D%0A      
@@ -4208,50 +4208,115 @@
     
-%7D%0A        return new ArrayDouble(newArray)
+@Override%0A            public int size() %7B%0A                return xSamples*ySamples;%0A            %7D%0A        %7D
 ;%0A  

update to current SNAPSHOT