package math.diffeq;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import math.matrix.Matrix;
import math.matrix.Vector;
import awt.Draw;
import exception.IllegalDimensionException;

public class Signal {
	
	public enum Function{
		Lorenz(new Signal.Lorenz());
		Object obj;
		Function(Object obj){ this.obj = obj; }
	}

	private Vector xi; // TODO: do I need this?
	private Matrix x;
	
	public Signal() {
		// TODO Auto-generated constructor stub
	}
	
	public Signal(double x, double y, double z){
		this.xi = new Vector(x,y,z);
	}
	
	/**
	 * @param n number of values for each data array
	 */
	public Signal(int n){
		x = new Matrix(3,n);
	}
	
	public double xi(){
		return xi.x();
	}
	public double yi(){
		return xi.y();
	}
	public double zi(){
		return xi.z();
	}
	
	/**
	 * @return first data array, represent x
	 */
	public Vector getX(){
		return x.getM(0); // x stored in first row
	}
	
	/**
	 * @return second data array, represent y
	 */
	public Vector getY(){
		return x.getM(1); // y stored in second row
	}
	
	/**
	 * @return third data array, represent z
	 */
	public Vector getZ(){
		return x.getM(2); // z stored in third row
	}
	
	/**
	 * @param i index
	 * @return i-th value in first data array, represent x<sub>i</sub>
	 */
	public double getX(int i){
		return x.get(0, i); // xi stored in first row and i-th column
	}
	
	/**
	 * @param i index
	 * @return i-th value in first data array, represent y<sub>i</sub>
	 */
	public double getY(int i){
		return x.get(1, i); // yi stored in second row and i-th column
	}
	
	/**
	 * @param i index
	 * @return i-th value in first data array, represent z<sub>i</sub>
	 */
	public double getZ(int i){
		return x.get(2, i); // zi stored in third row and i-th column
	}
	
	/**
	 * data with new first value in first data array, represent x<sub>0</sub>
	 * @param x0 value
	 */
	public void setX(double x0){
		x.set(0, 0, x0); // x0 stored in first row and first column
	}
	
	/**
	 * data with new first value in second data array, represent y<sub>0</sub>
	 * @param y0 value
	 */
	public void setY(double y0){
		x.set(1, 0, y0); // y0 stored in second row and first column
	}
	
	/**
	 * data with new first value in third data array, represent z<sub>0</sub>
	 * @param z0 value
	 */
	public void setZ(double z0){
		x.set(2, 0, z0); // z0 stored in third row and first column
	}
	
	public void setX(int i, double xi){
		x.set(0, i, xi);
	}
	public void setY(int i, double yi){
		x.set(1, i, yi);
	}
	public void setZ(int i, double zi){
		x.set(2, i, zi);
	}

	/**
	 * Euler's method to numerically solve ODE
	 * @param dx
	 * @param dy
	 * @param dz
	 * @param dt
	 * @return xi
	 */
	public Vector eulerODE(double dx, double dy, double dz, double dt){
        try {
			xi = xi.plus(Matrix.diag(dx,dy,dz).times(Vector.fill(3, dt)));
		} catch (IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return xi;
	}

	/**
	 * Euler's method to numerically solve ODE
	 * @param xi
	 * @param dx
	 * @param yi
	 * @param dy
	 * @param zi
	 * @param dz
	 * @param dt
	 * @return xi
	 * @throws IllegalDimensionException
	 */
	public Vector eulerODE(double xi, double dx, double yi, double dy, double zi, double dz, double dt) throws IllegalDimensionException{
		this.xi = new Vector(xi,yi,zi).plus(Matrix.diag(dx,dy,dz).times(Vector.fill(3, dt)));
		return this.xi;
	}
	
	/**
	 * Euler's method to numerically solve ODE
	 * @param fct
	 * @param x0
	 * @param y0
	 * @param z0
	 * @param dt
	 * @param n
	 * @return signal
	 * @throws Exception
	 */
	public static Signal eulerODE(Function fct, double x0, double y0, double z0, double dt, int n) throws Exception{
		Object obj = fct.obj;
		Signal sn = new Signal(n);
		sn.setX(x0);
		sn.setY(y0);
		sn.setZ(z0);
		int i;
        for(i=0; i<(n-1); i++){
        	sn.setX(i+1, sn.getX(i) + (double) invokeMethod("dx", 
            	new Class[]{double.class, double.class, double.class },
            	new Object[] { sn.getX(i), sn.getY(i), sn.getZ(i) },
            	obj.getClass(), obj) * dt);
        	sn.setY(i+1, sn.getY(i) + (double) invokeMethod("dy", 
                	new Class[]{double.class, double.class, double.class },
                	new Object[] { sn.getX(i), sn.getY(i), sn.getZ(i) },
                	obj.getClass(), obj) * dt);
        	sn.setZ(i+1, sn.getZ(i) + (double) invokeMethod("dz", 
                	new Class[]{double.class, double.class, double.class },
                	new Object[] { sn.getX(i), sn.getY(i), sn.getZ(i) },
                	obj.getClass(), obj) * dt);
        }
        return sn;
	}
	   
    private static Object invokeMethod(String methodName, Class<?>[] signature,
            Object[] parameters, Class<? extends Object> targetClass, Object target)
            throws Exception {
        Method m = targetClass.getDeclaredMethod(methodName, signature);
   
        if (Modifier.isPrivate(m.getModifiers())) {
            m.setAccessible(true);
        }
   
        return m.invoke(target, parameters);
    }
    
    /**
     * Lorenz attractor
     * @author Daniel
     */
	public static class Lorenz{
	    public double dx(double x, double y, double z) {
	        return -10*(x - y);
	    }
	
	    public double dy(double x, double y, double z) {
	        return -x*z + 28*x - y;
	    }
	
	    public double dz(double x, double y, double z) {
	        return x*y - 8*z/3;
	    }
	}
	
	/**
	 * Plot phase space (x vs. z) of Lorenz attractor using Euler method.
	 * @throws Exception 
	 */
	public void demoButterfly() throws Exception{
        double x = 0.0, y = 20.0, z = 25.0;
        double dt = 0.002; // 0.001
        int n = 50000;
        
		Signal signal = Signal.eulerODE(Signal.Function.Lorenz, x, y, z, dt, n);
		Draw draw = new Draw(signal.getX(), signal.getZ(), "Butterfly");
//		draw.cross();
		draw.animate();
	}
	
	/**
	 * Plot phase space (x vs. z) of Lorenz attractor with one set of
	 * initial conditions and another set of slightly perturbed initial
	 * conditions. Uses Euler method.
	 * @throws Exception 
	 */
	public void demoLorenz() throws Exception{
        double dt = 0.002;
        int n = 50000;
        
		Signal signal1 = Signal.eulerODE(Signal.Function.Lorenz, 0, 20.00, 25, dt, n);
		Signal signal2 = Signal.eulerODE(Signal.Function.Lorenz, 0, 20.01, 25, dt, n);
		Matrix signalX = new Matrix(signal1.getX(), signal2.getX()).transpose();
		Matrix signalZ = new Matrix(signal1.getZ(), signal2.getZ()).transpose();
		Draw draw = new Draw(signalX, signalZ, "Lorenz");
//		draw.cross();
		draw.animate();
	}
	
    public static void main(String[] args) throws Exception {
//    	new Signal().demoButterfly();
    	new Signal().demoLorenz();
    }
}
