package net.heartshield.sensors;

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

/**
 * Time series signal handling (with ArrayList-like automatic array memory management).
 * Timebase is always nanoTime.
 *
 * @author David Madl (git@abanbytes.eu)
 * @date 2016-03-21
 */
public class SensorData {
    /**
     * Time series of each dimension: e.g. {{t1, t2, ...}, {x1, x2, ...}}
     * Note that backing arrays may be longer than the actual data contained.
     */
    private List<double[]> mSamples;

    /** actual number of samples */
    private int mLength;

    private static final int START_BUF_SIZE = 1;

    /** @param ndim number of dimensions, excluding timestamp */
    SensorData(int ndim) {
        if(ndim < 1)
            throw new IllegalArgumentException("ndim must be at least 1");

        mLength = 0;
        mSamples = new ArrayList<>();
        for(int i = 0; i < ndim+1; i++)
            mSamples.add(new double[START_BUF_SIZE]);
    }

    /**
     * Time series of each dimension: e.g. {{t1, t2, ...}, {x1, x2, ...}}
     * Deep copies are returned.
     *
     * @return sensor samples recorded since {@link ISensor#start} was called
     */
    synchronized List<double[]> getSamples() {
        List<double[]> samples = new ArrayList<>();
        for(int i = 0; i < mSamples.size(); i++) {
            double[] dim = new double[mLength];
            System.arraycopy(mSamples.get(i), 0, dim, 0, mLength);
            samples.add(dim);
        }
        return samples;
    }

    /** @return number of samples */
    synchronized int length() { return mLength; }

    private void extendIfNeeded(int writeIndex) {
        int oldLength = mSamples.get(0).length;
        if(writeIndex < oldLength)
            return;

        int newLength = oldLength;
        while(newLength <= writeIndex)
            newLength *= 2;

        List<double[]> newSamples = new ArrayList<>();

        for(int i = 0; i < mSamples.size(); i++) {
            double[] dim = new double[newLength];
            System.arraycopy(mSamples.get(i), 0, dim, 0, mLength);
            newSamples.add(dim);
        }
        mSamples = newSamples;
    }

    /** @param sample array of single timestamped measurement, e.g. {t, x, y, z} */
    synchronized void addSample(double[] sample) {
        if(sample.length != mSamples.size())
            throw new IllegalArgumentException("a single sample must have sample.length == ndim+1");
        extendIfNeeded(mLength);
        for(int i = 0; i < mSamples.size(); i++)
            mSamples.get(i)[mLength] = sample[i];
        mLength++;
    }

    synchronized void clear() {
        int ndimp1 = mSamples.size();
        mLength = 0;
        mSamples.clear();
        for(int i = 0; i < ndimp1; i++)
            mSamples.add(new double[START_BUF_SIZE]);
    }

    /**
     * @param startTime  seconds in nanoTime timebase
     */
    public static List<double[]> transposeSamples(List<double[]> samples, double startTime) {
        final int a = samples.get(0).length;
        final int b = samples.size();
        List<double[]> result = new ArrayList<>();

        for(int i = 0; i < a; i++) {
            double[] entry = new double[b];
            for(int j = 0; j < b; j++)
                entry[j] = samples.get(j)[i];
            entry[0] -= startTime;
            result.add(entry);
        }

        return result;
    }

    public static List<double[]> transposeSamples(List<double[]> samples) {
        final int a = samples.get(0).length;
        final int b = samples.size();
        List<double[]> result = new ArrayList<>();

        for(int i = 0; i < a; i++) {
            double[] entry = new double[b];
            for(int j = 0; j < b; j++)
                entry[j] = samples.get(j)[i];
            result.add(entry);
        }

        return result;
    }
}
