package org.openpixi.pixi.physics.movement.boundary;

import org.openpixi.pixi.physics.force.Force;
import org.openpixi.pixi.physics.particles.Particle;
import org.openpixi.pixi.physics.solver.Solver;
import org.openpixi.pixi.physics.util.DoubleBox;

/**
 * Maps the 8 possible boundary regions in 2D to actual boundaries.
 */
public class SimpleParticleBoundaries implements ParticleBoundaries {

	private BoundaryRegions boundaryRegions;
	private ParticleBoundary[] regionBoundaryMap =
			new ParticleBoundary[BoundaryRegions.NUM_OF_REGIONS];

	private ParticleBoundaryType boundaryType;
	private DoubleBox simulationArea;

	/**
	 * Box around the particle which is used to determine
	 * whether the particle lies outside of the simulation area or not.
	 */
	private DoubleBox particleBox = new DoubleBox(0,0,0,0);


	public ParticleBoundaryType getType() {
		return boundaryType;
	}


	public SimpleParticleBoundaries(DoubleBox simulationArea, ParticleBoundaryType boundaryType) {
		this.boundaryType = boundaryType;
		this.simulationArea = simulationArea;
		boundaryRegions = new BoundaryRegions(simulationArea);
		createBoundaryMap();
	}


	/**
	 * For run-time boundary type switching in interactive version.
	 */
	public void changeType(ParticleBoundaryType boundaryType) {
		this.boundaryType = boundaryType;
		createBoundaryMap();
	}


	/*
	 * In 3D case it might be easier to use for cycles for the mapping.
	 */
	private void createBoundaryMap() {
		regionBoundaryMap[BoundaryRegions.X_MIN + BoundaryRegions.Y_MIN] =
				boundaryType.createBoundary(-simulationArea.xsize(), -simulationArea.ysize());
		regionBoundaryMap[BoundaryRegions.X_CENTER + BoundaryRegions.Y_MIN] =
				boundaryType.createBoundary(0, -simulationArea.ysize());
		regionBoundaryMap[BoundaryRegions.X_MAX + BoundaryRegions.Y_MIN] =
				boundaryType.createBoundary(simulationArea.xsize(), -simulationArea.ysize());
		regionBoundaryMap[BoundaryRegions.X_MIN + BoundaryRegions.Y_CENTER] =
				boundaryType.createBoundary(-simulationArea.xsize(), 0);
		regionBoundaryMap[BoundaryRegions.X_CENTER + BoundaryRegions.Y_CENTER] =
				new EmptyBoundary(0, 0);
		regionBoundaryMap[BoundaryRegions.X_MAX + BoundaryRegions.Y_CENTER] =
				boundaryType.createBoundary(simulationArea.xsize(), 0);
		regionBoundaryMap[BoundaryRegions.X_MIN + BoundaryRegions.Y_MAX] =
				boundaryType.createBoundary(-simulationArea.xsize(), simulationArea.ysize());
		regionBoundaryMap[BoundaryRegions.X_CENTER + BoundaryRegions.Y_MAX] =
				boundaryType.createBoundary(0, simulationArea.ysize());
		regionBoundaryMap[BoundaryRegions.X_MAX + BoundaryRegions.Y_MAX] =
				boundaryType.createBoundary(simulationArea.xsize(), simulationArea.ysize());
	}


	public void applyOnParticleBoundingBox(
			Solver solver, Force force, Particle particle, double timeStep) {

		/*
		 * Since there can be a large number of particles,
		 * it is costly to create new bounding box for each particle in each time step;
		 * thus, we reuse single bounding box.
		 */
		boundaryType.getParticleBox(particle, particleBox);

		int region = boundaryRegions.getRegion(particleBox);
		regionBoundaryMap[region].apply(solver, force, particle, timeStep);
	}


	public void applyOnParticleCenter(
			Solver solver, Force force, Particle particle, double timeStep) {
		int region = boundaryRegions.getRegion(particle.getX(), particle.getY());
		regionBoundaryMap[region].apply(solver, force, particle, timeStep);
	}
}
