package org.sunflow.math;

/**
 * This class describes a transformation matrix that changes over time. Note
 * that while unlimited motion segments are supported, it is assumed that these
 * segments represent equidistant samples within a given time range.
 */
public final class MovingMatrix4 {
    private Matrix4[] transforms;
    private float t0, t1, inv;

    /**
     * Constructs a simple static matrix.
     * 
     * @param m matrix value at all times
     */
    public MovingMatrix4(Matrix4 m) {
        transforms = new Matrix4[] { m };
        t0 = t1 = 0;
        inv = 1;
    }

    private MovingMatrix4(int n, float t0, float t1, float inv) {
        transforms = new Matrix4[n];
        this.t0 = t0;
        this.t1 = t1;
        this.inv = inv;
    }

    /**
     * Redefines the number of steps in the matrix. The contents are only
     * re-allocated if the number of steps changes. This is to allow the matrix
     * to be incrementally specified.
     * 
     * @param n
     */
    public void setSteps(int n) {
        if (transforms.length != n) {
            transforms = new Matrix4[n];
            if (t0 < t1)
                inv = (transforms.length - 1) / (t1 - t0);
            else
                inv = 1;
        }
    }

    /**
     * Updates the matrix for the given time step.
     * 
     * @param i time step to update
     * @param m new value for the matrix at this time step
     */
    public void updateData(int i, Matrix4 m) {
        transforms[i] = m;
    }

    /**
     * Get the matrix for the given time step.
     * 
     * @param i time step to get
     * @return matrix for the specfied time step
     */
    public Matrix4 getData(int i) {
        return transforms[i];
    }

    /**
     * Get the number of matrix segments
     * 
     * @return number of segments
     */
    public int numSegments() {
        return transforms.length;
    }

    /**
     * Update the time extents over which the matrix data is changing. If the
     * interval is empty, no motion will be produced, even if multiple values
     * have been specified.
     * 
     * @param t0
     * @param t1
     */
    public void updateTimes(float t0, float t1) {
        this.t0 = t0;
        this.t1 = t1;
        if (t0 < t1)
            inv = (transforms.length - 1) / (t1 - t0);
        else
            inv = 1;
    }

    public MovingMatrix4 inverse() {
        MovingMatrix4 mi = new MovingMatrix4(transforms.length, t0, t1, inv);
        for (int i = 0; i < transforms.length; i++) {
            if (transforms[i] != null) {
                mi.transforms[i] = transforms[i].inverse();
                if (mi.transforms[i] == null)
                    return null; // unable to invert
            }
        }
        return mi;
    }

    public Matrix4 sample(float time) {
        if (transforms.length == 1 || t0 >= t1)
            return transforms[0];
        else {
            float nt = (MathUtils.clamp(time, t0, t1) - t0) * inv;
            int idx0 = (int) nt;
            int idx1 = Math.min(idx0 + 1, transforms.length - 1);
            return Matrix4.blend(transforms[idx0], transforms[idx1], (float) (nt - idx0));
        }
    }
}