package lpv.simulator;

import java.util.Observable;

import javax.vecmath.Point3i;

import lpv.ErrorStore;
import lpv.LPV;
import lpv.simulation.injector.Injector;
import lpv.simulation.propagator.Propagator;

public class Simulator extends Observable {
	private LPV first;
	private LPV second;
	private LPV accumulation;
	private int w;
	private int h;
	private int d;
	private int iteration;
	private LPV geometry;
	private ErrorStore errors;
	
	public Simulator(int w, int h, int d) {
		this.first = new LPV(w, h, d);
		this.second = new LPV(w, h, d);
		this.accumulation = new LPV(w, h, d);
		this.geometry = new LPV(w, h, d);
		this.errors = new ErrorStore();
		
		this.w = w;
		this.h = h;
		this.d = d;
		this.iteration = 0;
		
		//errors.registerSource("first");
		//errors.registerSource("second");
		errors.registerSource("accumulation");
		//errors.registerSource("geometry");
	}
	
	public void injectLight(Injector injector) {
		injector.inject(first);
		setChanged();
		notifyObservers(this);
	}
	
	public void injectGeometry(Injector injector) {
		injector.inject(geometry);
		setChanged();
		notifyObservers(this);
	}
	
	public void flip() {
		LPV tmp = first;
		first = second;
		second = tmp;
		iteration++;
	}
	
	public void propagate(Propagator propagator) {
		int oldLevel = accumulation.getLevel();
		int levels = accumulation.getLevels();
		for(int level = 0; level < levels; level++) {
			first.setLevel(level);
			second.setLevel(level);
			accumulation.setLevel(level);
			geometry.setLevel(level);
			for(int x = 0; x < w >> level; x++) {
				for(int y = 0; y < h >> level; y++) {
					for(int z = 0; z < d >> level; z++) {
						propagator.propagate(first, second, accumulation, geometry, new Point3i(x, y, z), iteration);
					}
				}
			}
		}
		first.setLevel(oldLevel);
		second.setLevel(oldLevel);
		accumulation.setLevel(oldLevel);
		geometry.setLevel(oldLevel);
		setChanged();
		notifyObservers(this);
	}
	
	public void setLevel(int level) {
		first.setLevel(level);
		second.setLevel(level);
		accumulation.setLevel(level);
		geometry.setLevel(level);
		
		setChanged();
		notifyObservers(this);
	}
	
	public void clear() {
		first.clear();
		second.clear();
		accumulation.clear();
		geometry.clear();
		errors.clear();
		setChanged();
		notifyObservers(this);
		iteration = 0;
	}

	public LPV getFirst() {
		return first;
	}

	public LPV getSecond() {
		return second;
	}

	public LPV getAccumulation() {
		return accumulation;
	}

	public LPV getGeometry() {
		return geometry;
	}

	public void downsample() {
		first.downsample();
		second.downsample();
		accumulation.downsample();
		geometry.downsample();
		setChanged();
		notifyObservers(this);
	}
	
	public void sampleErrors() {
		//errors.add("first", first.getError());
		//errors.add("second", second.getError());
		errors.add("accumulation", accumulation.getError());
		//errors.add("geometry", geometry.getError());
	}

	public void dumpErrors() {
		/*System.out.println("Error dump [");
		System.out.println("\t       First: " + first.getError());
		System.out.println("\t      Second: " + second.getError());
		System.out.println("\tAccumulation: " + accumulation.getError());
		System.out.println("\t    Geometry: " + geometry.getError());
		System.out.println("]");*/
		errors.dump();
	}

	public ErrorStore getErrors() {
		return errors;
	}

}
