package math;

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

/**
 * The motion of a vibrations
 * @author Daniel
 *
 */
public class FourierSeries{
	
	/**
	 * number of superpositions
	 */
	int n;
	
	/**
	 * time vector
	 */
	Vector t;
	
	Draw fsDraw;
	
	public FourierSeries(){
	}
	
	public FourierSeries(int n, Vector t){
		this.n = n;
		this.t = t;
	}
	
	/**
	 * k-th Fourier coefficient of sawtooth function.
	 * @param t time vector
	 * @param k -th coefficient
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector sawtoothC(Vector t, int k) throws IllegalSetupException{
        if(t == null) throw new IllegalSetupException();
		return (t.times(k*2)).sin().over(k);
	}
	
	/**
	 * k-th Fourier coefficient of sawtooth function. Inverse sawtooth wave
	 * @param t time vector
	 * @param k -th coefficient
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector sawtoothCI(Vector t, int k) throws IllegalSetupException{
        if(t == null) throw new IllegalSetupException();
		return (t.times(k*2)).sin().over(k).times(-1);
	}
	
	/**
	 * matrix of first n-th Fourier coefficients of sawtooth function.
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @return displacement matrix
	 * @throws IllegalSetupException
	 */
	public static Matrix sawtoothCM(Vector t, int n) throws IllegalSetupException{
        if(n == 0) throw new IllegalSetupException();
		int k;
		Matrix ff = new Matrix(n,t.n()); // Fourier coefficients
		for(k=1;k<=n;k++)
			ff.setM(k-1, sawtoothC(t, k));
		return ff;
	}
	
	/**
	 * FFT of sawtooth function.
	 * Fourier Synthesis of first n partial sums
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector sawtooth(Vector t, int n) throws IllegalSetupException{
		return sawtoothCM(t,n).sum();
	}
	
	/**
	 * Frequency Content of Sawtooth Signal.
	 * @param n first n-th coefficient
	 * @return matrix omega[2*k,2*k] x(&omega;)[0,1/k]
	 * @throws IllegalSetupException
	 */
	public static Matrix sawtoothFC(int n) throws IllegalSetupException{
		int k;
		Matrix x = new Matrix(4,n);
		for(k=1; k<=n; k++){
			x.set(0, k-1, 2*k);
			x.set(1, k-1, 2*k);
			x.set(2, k-1, 0);
			x.set(3, k-1, 1./k);
		}
		return x;
	}
	
	/**
	 * FFT of sawtooth function.
	 * first n partial sums of first n calculated Fourier coefficients
	 * @throws IllegalSetupException
	 */
	public Vector sawtooth() throws IllegalSetupException{
		return sawtooth(t,n);
	}
	
	/**
	 * Fourier Synthesis of sawtooth using 20 terms
	 */
	public void demoSawtooth(){
		n = 20;
		t = Vector.fill(0,0.001,10);
		try {
			animate(t, sawtooth());
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	/**
	 * Fourier Synthesis of sawtooth using 20 terms and first five coefficients
	 */
	public void demoSawtoothCM(){
		n = 20;
		int nc = 5;
		Vector ones = Vector.ones(nc+1);
		t = Vector.fill(0,0.003,10);
		Matrix T = ones.tensorProduct(t);
		Matrix X = new Matrix(nc+1,t.n());
		try {
			int i;
			for(i=0; i<=nc; i++){
				if(i!=nc) X.setM(i, FourierSeries.sawtoothC(t, i+1));
				else X.setM(i, FourierSeries.sawtooth(t, n));
			}
			animate(T.transpose(),X.transpose());
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	/**
	 * Fourier Synthesis of sawtooth using 20 terms and first five coefficients.
	 * parallel animation
	 * @param parallel boolean; true: parallel, false: serial
	 */
	public void demoSawtoothCM(boolean parallel){
		if(!parallel) demoSawtoothCM();
		n = 20;
		int nc = 5;
		Vector ones = Vector.ones(nc+1);
		t = Vector.fill(0,0.003,10);
		Matrix T = ones.tensorProduct(t);
		Matrix X = new Matrix(nc+1,t.n());
		try {
			int i;
			for(i=0; i<=nc; i++){
				if(i!=nc) X.setM(i, FourierSeries.sawtoothC(t, i+1));
				else X.setM(i, FourierSeries.sawtooth(t, n));
			}
			animate(T,X);
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	/**
	 * Fourier Synthesis of sawtooth using 20 terms and first five partial sums
	 */
	public void demoSawtoothPM(){
		n = 20;
		int nc = 5;
		Vector ones = Vector.ones(nc+1);
		t = Vector.fill(0,0.003,10);
		Matrix T = ones.tensorProduct(t);
		Matrix X = new Matrix(nc+1,t.n());
		try {
			int i, nn = 0;
			for(i=0; i<=nc; i++){
				nn = i;
				if(i==nc) nn=n;
				X.setM(i, FourierSeries.sawtooth(t, nn+1));
			}
			animate(T.transpose(),X.transpose()); // serial animation
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	/**
	 * Fourier Synthesis of sawtooth using 20 terms and first five partial sums
	 * @param parallel boolean; true: parallel, false: serial
	 */
	public void demoSawtoothPM(boolean parallel){
		if(!parallel) demoSawtoothPM();
		n = 20;
		int nc = 5;
		Vector ones = Vector.ones(nc+1);
		t = Vector.fill(0,0.003,10);
		Matrix T = ones.tensorProduct(t);
		Matrix X = new Matrix(nc+1,t.n());
		try {
			int i, nn = 0;
			for(i=0; i<=nc; i++){
				nn = i;
				if(i==nc) nn=n;
				X.setM(i, FourierSeries.sawtooth(t, nn+1));
			}
			animate(T,X); // parallel animation
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Fourier Synthesis of sawtooth using 20 terms and first five partial sums
	 */
	public void demoSawtoothFC(){
		n = 20;
		try {
			Matrix OX = sawtoothFC(n);
			Matrix O = OX.getM(new int[]{0, 1});
			Matrix X = OX.getM(new int[]{2, 3});
			animate(O,X);
//			animate(O.transpose(),X.transpose());
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * k-th Fourier coefficient of square function.
	 * @param t time vector
	 * @param k -th coefficient
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector squareC(Vector t, int k) throws IllegalSetupException{
        if(t == null) throw new IllegalSetupException();
		return (t.times((2*k-1)*2)).sin().over(2*k-1);
	}
	
	/**
	 * matrix of first n-th Fourier coefficients of square function.
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @return displacement matrix
	 * @throws IllegalSetupException
	 */
	public static Matrix squareCM(Vector t, int n) throws IllegalSetupException{
        if(n == 0) throw new IllegalSetupException();
		int k;
		Matrix ff = new Matrix(n,t.n()); // Fourier coefficients
		for(k=1;k<=n;k++)
			ff.setM(k-1, squareC(t, k));
		return ff;
	}
	
	/**
	 * FFT of square function.
	 * Fourier Synthesis of first n partial sums
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector square(Vector t, int n) throws IllegalSetupException{
		return squareCM(t,n).sum();
	}
	
	/**
	 * Frequency Content of square Signal.
	 * @param n first n-th coefficient
	 * @return matrix ω[2*(2*k-1),2*(2*k-1)] x(ω)[0,1/(2*k-1)]
	 * @throws IllegalSetupException
	 */
	public static Matrix squareFC(int n) throws IllegalSetupException{
		int k;
		Matrix x = new Matrix(4,n);
		for(k=1; k<=n; k++){
			x.set(0, k-1, 2*(2*k-1));
			x.set(1, k-1, 2*(2*k-1));
			x.set(2, k-1, 0);
			x.set(3, k-1, 1./(2*k-1));
		}
		return x;
	}
	
	/**
	 * FFT of sawtooth function.
	 * first n partial sums of first n calculated Fourier coefficients
	 * @throws IllegalSetupException
	 */
	public Vector square() throws IllegalSetupException{
		return square(t,n);
	}
	
	/**
	 * Fourier Synthesis of square using 20 terms
	 */
	public void demoSquare(){
		n = 20;
		t = Vector.fill(0,0.001,10);
		try {
			animate(t, square());
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Fourier Synthesis of square using 20 terms and first five coefficients
	 */
	public void demoSquareCM(){
		n = 20;
		int nc = 5;
		Vector ones = Vector.ones(nc+1);
		t = Vector.fill(0,0.003,10);
		Matrix T = ones.tensorProduct(t);
		Matrix X = new Matrix(nc+1,t.n());
		try {
			int i;
			for(i=0; i<=nc; i++){
				if(i!=nc) X.setM(i, FourierSeries.squareC(t, i+1));
				else X.setM(i, FourierSeries.square(t, n));
			}
			animate(T.transpose(),X.transpose());
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Fourier Synthesis of sawtooth using 20 terms and first five partial sums
	 */
	public void demoSquarePM(){
		n = 20;
		int nc = 5;
		Vector ones = Vector.ones(nc+1);
		t = Vector.fill(0,0.003,10);
		Matrix T = ones.tensorProduct(t);
		Matrix X = new Matrix(nc+1,t.n());
		try {
			int i, nn = 0;
			for(i=0; i<=nc; i++){
				nn = i;
				if(i==nc) nn=n;
				X.setM(i, FourierSeries.square(t, nn+1));
			}
			animate(T,X); // serial animation
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Fourier Synthesis of square using 20 terms and first five partial sums
	 */
	public void demoSquareFC(){
		n = 20;
		try {
			Matrix OX = squareFC(n);
			Matrix O = OX.getM(new int[]{0, 1});
			Matrix X = OX.getM(new int[]{2, 3});
			animate(O,X);
//			animate(O.transpose(),X.transpose());
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * k-th Fourier coefficient of pulse function.
	 * @param t time vector
	 * @param k -th coefficient
	 * @param τ pulse duration
	 * @param T period
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector pulseC(Vector t, int k, double τ, double T) throws IllegalSetupException{
        if(t == null) throw new IllegalSetupException();
        // replaced t with t-tau/2 so it is not symmetric with vertical axis but starts with the first pulse
		return (t.minus(τ/2).times(2*Math.PI*k/T)).cos().times(Math.sin(Math.PI*k*τ/T)*2/(k*Math.PI));
	}
	
	/**
	 * matrix of first n-th Fourier coefficients of pulse function.
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @param τ pulse duration
	 * @param T period
	 * @return displacement matrix
	 * @throws IllegalSetupException
	 */
	public static Matrix pulseCM(Vector t, int n, double τ, double T) throws IllegalSetupException{
        if(n == 0) throw new IllegalSetupException();
		int k;
		Matrix ff = new Matrix(n,t.n()); // Fourier coefficients
		for(k=1;k<=n;k++)
			ff.setM(k-1, pulseC(t, k, τ, T));
		return ff;
	}
	
	/**
	 * FFT of pulse function.
	 * Fourier Synthesis of first n partial sums
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @param τ pulse duration
	 * @param T period
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector pulse(Vector t, int n, double τ, double T) throws IllegalSetupException{
		return pulseCM(t,n,τ,T).sum().plus(τ/T);
	}
	
	/**
	 * Fourier Synthesis of pulse wave using 20 terms
	 */
	public void demoPulse(){
		n = 20;
		t = Vector.fill(0,0.001,10.);
		try {
			animate(t, pulse(t,n,1,4));
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * k-th Fourier coefficient of triangle function.
	 * @param t time vector
	 * @param k -th coefficient
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector triangleC(Vector t, int k) throws IllegalSetupException{
        if(t == null) throw new IllegalSetupException();
        // replaced t with t-tau/2 so it is not symmetric with vertical axis but starts with the first pulse
		return (t.times(2*k+1)).sin().times(Math.pow(-1, k)/Math.pow(2*k+1, 2));
	}
	
	/**
	 * matrix of first n-th Fourier coefficients of triangle function.
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @return displacement matrix
	 * @throws IllegalSetupException
	 */
	public static Matrix triangleCM(Vector t, int n) throws IllegalSetupException{
        if(n == 0) throw new IllegalSetupException();
		int k;
		Matrix ff = new Matrix(n,t.n()); // Fourier coefficients
		for(k=0;k<n;k++)
			ff.setM(k, triangleC(t, k));
		return ff;
	}
	
	/**
	 * FFT of triangle function.
	 * Fourier Synthesis of first n partial sums
	 * @param t time vector
	 * @param n first n-th coefficient
	 * @return displacement vector
	 * @throws IllegalSetupException
	 */
	public static Vector triangle(Vector t, int n) throws IllegalSetupException{
		return triangleCM(t,n).sum().times(8/Math.PI/Math.PI);
	}
	
	/**
	 * FFT of triangle function.
	 * First n partial sums of first n calculated Fourier coefficients
	 * @throws IllegalSetupException
	 */
	public Vector triangle() throws IllegalSetupException{
		return triangle(t,n);
	}
	
	/**
	 * Fourier Synthesis of pulse wave using 20 terms
	 */
	public void demoTriangle(){
		n = 20;
		t = Vector.fill(0,0.001,10.);
		try {
			animate(t, triangle());
		} catch (IllegalSetupException | IllegalDimensionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * animation
	 * @param T time vector
	 * @param X displacement vector
	 * @throws IllegalDimensionException
	 * @throws IllegalSetupException
	 */
	public void animate(Vector T, Vector X) throws IllegalDimensionException, IllegalSetupException{
        if(T == null || X == null) throw new IllegalSetupException();
        if(T.n() != X.n()) throw new IllegalDimensionException("Illegal vector dimensions.");
        fsDraw = new Draw("Fourier series");
        fsDraw.animate(T,X);
	}
	
	/**
	 * animation
	 * @param T time vector
	 * @param X displacement vector
	 * @throws IllegalDimensionException
	 * @throws IllegalSetupException
	 */
	public void animate(Matrix T, Matrix X) throws IllegalDimensionException, IllegalSetupException{
        if(T == null || X == null) throw new IllegalSetupException();
        if(T.getM() != X.getM()) throw new IllegalDimensionException("Illegal vector dimensions.");
        fsDraw = new Draw("Fourier series");
        fsDraw.animate(T,X);
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		FourierSeries fs = new FourierSeries();
//		fs.demoSawtooth();
//		fs.demoSawtoothCM();
//		fs.demoSawtoothCM(true);
//		fs.demoSawtoothPM();
//		fs.demoSawtoothPM(true);
//		fs.demoSawtoothFC();
//		fs.demoSquare();
//		fs.demoSquareCM();
//		fs.demoSquarePM();
//		fs.demoSquareFC();
//		fs.demoPulse();
		fs.demoTriangle();
	}

}
