package org.openpixi.pixi.ui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.Timer;

import org.openpixi.pixi.physics.InitialConditions;
import org.openpixi.pixi.physics.Settings;
import org.openpixi.pixi.physics.Simulation;
import org.openpixi.pixi.physics.collision.algorithms.CollisionAlgorithm;
import org.openpixi.pixi.physics.collision.algorithms.MatrixTransformation;
import org.openpixi.pixi.physics.collision.algorithms.SimpleCollision;
import org.openpixi.pixi.physics.collision.algorithms.VectorTransformation;
import org.openpixi.pixi.physics.collision.detectors.AllParticles;
import org.openpixi.pixi.physics.collision.detectors.Detector;
import org.openpixi.pixi.physics.collision.detectors.SweepAndPrune;
import org.openpixi.pixi.physics.force.CombinedForce;
import org.openpixi.pixi.physics.force.ConstantForce;
import org.openpixi.pixi.physics.force.Force;
import org.openpixi.pixi.physics.force.SimpleGridForce;
import org.openpixi.pixi.physics.force.relativistic.ConstantForceRelativistic;
import org.openpixi.pixi.physics.force.relativistic.SimpleGridForceRelativistic;
import org.openpixi.pixi.physics.movement.boundary.ParticleBoundaryType;
import org.openpixi.pixi.physics.solver.Boris;
import org.openpixi.pixi.physics.solver.BorisDamped;
import org.openpixi.pixi.physics.solver.Euler;
import org.openpixi.pixi.physics.solver.EulerRichardson;
import org.openpixi.pixi.physics.solver.LeapFrog;
import org.openpixi.pixi.physics.solver.LeapFrogDamped;
import org.openpixi.pixi.physics.solver.LeapFrogHalfStep;
import org.openpixi.pixi.physics.solver.SemiImplicitEuler;
import org.openpixi.pixi.physics.solver.relativistic.BorisRelativistic;
import org.openpixi.pixi.physics.solver.relativistic.LeapFrogRelativistic;
import org.openpixi.pixi.physics.solver.relativistic.SemiImplicitEulerRelativistic;
import org.openpixi.pixi.ui.panel.Particle2DPanel;
import org.openpixi.pixi.ui.util.FrameRateDetector;

/**
 * Wrapper for the simulation class in GUI applications.
 */
public class SimulationAnimation {

	private Simulation s;

	private boolean relativistic = false;

	private boolean calculateFields = false;

	/** Milliseconds between updates */
	private int interval = 30;

	/** Timer for animation */
	private Timer timer;

	private FrameRateDetector frameratedetector;

	private ArrayList<SimulationAnimationListener> listeners = new ArrayList<SimulationAnimationListener>();

	/** Constructor */
	public SimulationAnimation() {
		timer = new Timer(interval, new TimerListener());
		frameratedetector = new FrameRateDetector(500);
		s = InitialConditions.initRandomParticles(10, 2);
	}

	/** Listener for timer */
	public class TimerListener implements ActionListener {

		public void actionPerformed(ActionEvent eve) {
			try {
				s.step();
			} catch (FileNotFoundException ex) {
				Logger.getLogger(Particle2DPanel.class.getName()).log(Level.SEVERE, null, ex);
			} catch (IOException ex2) {
				Logger.getLogger(Particle2DPanel.class.getName()).log(Level.SEVERE, null, ex2);
			}
			frameratedetector.update();
			repaint();
		}
	}

	public void startAnimation() {
		timer.start();
	}

	public void stopAnimation() {
		timer.stop();
		//test = false;
	}

	public Simulation getSimulation() {
		return s;
	}

	public FrameRateDetector getFrameRateDetector() {
		return frameratedetector;
	}

	public Timer getTimer() {
		return timer;
	}

	/**
	 * Add Listener for repaint() event.
	 * @param listener
	 */
	public void addListener(SimulationAnimationListener listener) {
		listeners.add(listener);
	}

	/**
	 * Removes Listener for repaint() event.
	 * @param listener
	 */
	public void removeListener(SimulationAnimationListener listener) {
		listeners.remove(listener);
	}

	private void repaint() {
		// Let all listeners know
		for (SimulationAnimationListener l : listeners) {
			l.repaint();
		}
	}

	private void clear() {
		// Let all listeners know
		for (SimulationAnimationListener l : listeners) {
			l.clear();
		}
	}

	public void resetAnimation(int id) {
		// timer.restart();
		timer.stop();
		clear();
		switch(id) {
		case 0:
			s = InitialConditions.initRandomParticles(10, 2);
			break;
		case 1:
			s = InitialConditions.initRandomParticles(100, 1);
			break;
		case 2:
			s = InitialConditions.initRandomParticles(1000, 0.5);
			break;
		case 3:
			s = InitialConditions.initRandomParticles(10000, 0.01);
			break;
		case 4:
			s = InitialConditions.initGravity(1, 2);
			break;
		case 5:
			s = InitialConditions.initElectric(1, 2);
			break;
		case 6:
			s = InitialConditions.initMagnetic(3, 2);
			break;
		case 7:
			s = InitialConditions.initPair(0.1,1);
			break;
		case 8:
			s = InitialConditions.initTwoStream(0.1,1,1000);
			break;
		case 9:
			s = InitialConditions.initWeibel(0.01,1,2000,4,0.9);
			break;
		case 10:
			s = InitialConditions.initOneTest(0.01,1);
			break;
		case 11:
			s = InitialConditions.initWaveTest(0.2);
			break;
		}
		updateFieldForce();
		s.prepareAllParticles();
		s.turnGridForceOn();
		timer.start();
	}

	/**
	 * Reset animation according to settings
	 *
	 * @param settings New settings for animation.
	 */
	public void resetAnimation(Settings settings) {
		// timer.restart();
		timer.stop();
		clear();
		s = new Simulation(settings);
		//updateFieldForce();
		s.prepareAllParticles();
		//s.turnGridForceOn();
		timer.start();
	}

	public void calculateFields() {
		calculateFields =! calculateFields;
		updateFieldForce();
	}

	private void updateFieldForce() {

		if(calculateFields) {
			s.turnGridForceOn();
		}
		else {
			s.turnGridForceOff();
		}
	}

	public void algorithmChange(int id)
	{
		s.completeAllParticles();

		switch(id) {
		case 0:
			s.getParticleMover().setSolver(new EulerRichardson());
			break;
		case 1:
			s.getParticleMover().setSolver(new LeapFrog());
			break;
		case 2:
			s.getParticleMover().setSolver(new LeapFrogDamped());
			break;
		case 3:
			s.getParticleMover().setSolver(new LeapFrogHalfStep());
			break;
		case 4:
			s.getParticleMover().setSolver(new Boris());
			break;
		case 5:
			s.getParticleMover().setSolver(new BorisDamped());
			break;
		case 6:
			s.getParticleMover().setSolver(new SemiImplicitEuler());
			break;
		case 7:
			s.getParticleMover().setSolver(new Euler());
			break;
			}

		s.prepareAllParticles();
	}

	public void relativisticEffects(int i) {
		relativistic =! relativistic;

		if(relativistic == false) {
			s.relativistic = false;
			if (s.f instanceof CombinedForce) {
				ArrayList<Force> forces = ((CombinedForce) s.f).forces;
				for (int j = 0; j < forces.size(); j++) {
					if (forces.get(j) instanceof ConstantForceRelativistic){
						forces.set(j, new ConstantForce());
					}
					if (forces.get(j) instanceof SimpleGridForceRelativistic){
						forces.set(j, new SimpleGridForce());
					}
				}
			}
			switch(i) {
			case 1:
				s.getParticleMover().setSolver(new LeapFrog());
			case 4:
				s.getParticleMover().setSolver(new Boris());
				break;
			case 6:
				s.getParticleMover().setSolver(new SemiImplicitEuler());
				break;
			}
		}

		if(relativistic == true) {
			s.relativistic = true;
			//System.out.println("relativistic version on");
			if (s.f instanceof CombinedForce) {
				ArrayList<Force> forces = ((CombinedForce) s.f).forces;
				for (int j = 0; j < forces.size(); j++) {
					if (forces.get(j) instanceof ConstantForce){
						forces.set(j, new ConstantForceRelativistic(s.getSpeedOfLight()));
					}
					if (forces.get(j) instanceof SimpleGridForce){
						forces.set(j, new SimpleGridForceRelativistic(s));
					}
				}
			}
			switch(i) {
			case 1:
				s.getParticleMover().setSolver(new LeapFrogRelativistic(s.getSpeedOfLight()));
			case 4:
				s.getParticleMover().setSolver(new BorisRelativistic(s.getSpeedOfLight()));
				break;
			case 6:
				s.getParticleMover().setSolver(new SemiImplicitEulerRelativistic(s.getSpeedOfLight()));
				break;
			}
		}

	}

	public void collisionChange(int i) {
		switch(i) {
		case 0:
			s.detector = new Detector();
			s.collisionalgorithm = new CollisionAlgorithm();
			break;
		case 1:
			s.detector = new AllParticles(s.particles);
			break;
		case 2:
			s.detector = new SweepAndPrune(s.particles);
			break;
		}
	}

	public void algorithmCollisionChange(int i) {
		switch(i) {
		case 0:
			s.collisionalgorithm = new SimpleCollision();
			break;
		case 1:
			s.collisionalgorithm = new VectorTransformation();
			break;
		case 2:
			s.collisionalgorithm = new MatrixTransformation();
			break;
		}
	}

	public void boundariesChange(int i) {
		switch(i) {
		case 0:
			s.getParticleMover().changeBoundaryType(ParticleBoundaryType.Hardwall);
			break;
		case 1:
			s.getParticleMover().changeBoundaryType(ParticleBoundaryType.Periodic);
		}

	}

}
