package org.openpixi.pixi.distributed;

import org.openpixi.pixi.distributed.ibis.WorkerToWorker;
import org.openpixi.pixi.distributed.util.BooleanLock;
import org.openpixi.pixi.distributed.util.IncomingCellsHandler;
import org.openpixi.pixi.distributed.util.IncomingParticlesHandler;
import org.openpixi.pixi.distributed.util.IncomingPointsHandler;
import org.openpixi.pixi.physics.particles.Particle;
import org.openpixi.pixi.physics.grid.Cell;
import org.openpixi.pixi.physics.grid.Grid;
import org.openpixi.pixi.physics.movement.boundary.ParticleBoundaries;
import org.openpixi.pixi.physics.util.Point;

import java.util.ArrayList;
import java.util.List;

/**
 * Handles the data which needs to be shared between two nodes.
 * Specifically, sends and receives ghost and border particles and cells.
 */
public class SharedData {

	/** Handles the network communication with neighbor. */
	private WorkerToWorker communicator;

	/* Locks to wait for the data to be received. */
	private BooleanLock arrivingParticlesLock = new BooleanLock();
	private BooleanLock ghostParticlesLock = new BooleanLock();
	private BooleanLock ghostCellsLock = new BooleanLock();
	private BooleanLock ghostCellsIndexesLock = new BooleanLock();

	/** Maps the list of outgoing border cells to remote cells (needs to be sent out at the beginning). */
	private List<Point> borderCellsMap = new ArrayList<Point>();

	/** Border cells which need to be send out to neighbor. */
	private List<Cell> borderCells = new ArrayList<Cell>();
	/** Ghost cells references which are shared with the grid class and thus are global. */
	private List<Cell> globalGhostCells = new ArrayList<Cell>();
	/** Ghost cells which are received from neighbor and local to this class,
	 * not visible from outside.
	 * It can happen that the ghost cells arrive too early (in the solve fields phase).
	 * In such case they can not be copied to the grid cells; hence, the local list. */
	private List<Cell> localGhostCells;

	/** Ghost particles of neighbors - particles to be send to neighbors. */
	private List<Particle> borderParticles = new ArrayList<Particle>();
	/** Particles leaving this node - particles to be send to neighbors. */
	private List<Particle> leavingParticles = new ArrayList<Particle>();
	/** Ghost particles of this node - particles to be received from neighbors. */
	private List<Particle> ghostParticles = new ArrayList<Particle>();
	/** Particles arriving to this node - particles to be received from neighbors. */
	private List<Particle> arrivingParticles = new ArrayList<Particle>();

	/**
	 * The arriving particles need to be checked
	 * (they become border particles +
	 * they might need to be reflected in the case of hardwall boundaries)
	 */
	private ParticleBoundaries particleBoundaries;

	/** Required for registering ghost cells. */
	private Grid grid;


	public void setParticleBoundaries(ParticleBoundaries particleBoundaries) {
		this.particleBoundaries = particleBoundaries;
	}


	public void setGrid(Grid grid) {
		this.grid = grid;
	}


	public SharedData(WorkerToWorker communicator) {
		this.communicator = communicator;

		communicator.setGhostCellsHandler(new GhostCellsHandler());
		communicator.setGhostParticlesHandler(new GhostParticlesHandler());
		communicator.setArrivingParticlesHandler(new ArrivingParticlesHandler());
		communicator.setGhostCellsIndexesHandler(new GhostCellsIndexesHandler());
	}


	/**
	 * Initializes the connection +
	 * exchanges the order of ghost cells.
	 */
	public void initializeCommunication() {
		communicator.initializeConnection();
		communicator.sendBorderCellsMap(borderCellsMap);
		ghostCellsIndexesLock.waitForTrue();
	}


	public void registerBorderCell(Cell borderCell, Point remoteCellIndex) {
		borderCells.add(borderCell);
		borderCellsMap.add(remoteCellIndex);
	}


	public void registerBorderParticle(Particle p) {
		borderParticles.add(p);
	}


	public void registerLeavingParticle(Particle p) {
		leavingParticles.add(p);
	}


	public void sendLeavingParticles() {
		communicator.sendLeavingParticles(leavingParticles);
	}


	public void sendBorderParticles() {
		communicator.sendBorderParticles(borderParticles);
	}


	public void sendBorderCells() {
		communicator.sendBorderCells(borderCells);
	}


	public void waitForArrivingParticles() {
		arrivingParticlesLock.waitForTrue();
	}


	/**
	 * Has a twofold function:
	 * 1) Waits for the ghost cells to arrive.
	 * 2) Makes the ghost cells visible to simulation and grid classes.
	 */
	public void waitForGhostCells() {
		ghostCellsLock.waitForTrue();
		for (int i = 0; i < localGhostCells.size(); ++i) {
			globalGhostCells.get(i).copyFrom(localGhostCells.get(i));
		}
	}


	/**
	 * Blocks until the arriving particles are received.
	 */
	public List<Particle> getArrivingParticles() {
		waitForArrivingParticles();
		return arrivingParticles;
	}


	/**
	 * Blocks until the ghost particles are received.
	 */
	public List<Particle> getGhostParticles() {
		ghostParticlesLock.waitForTrue();
		return ghostParticles;
	}


	public List<Particle> getLeavingParticles() {
		return leavingParticles;
	}


	/**
	 * Resets the particle lists waiting locks.
	 * Resets the particle lists which are send to neighbors.
	 * => Should be called at the end of particle communication.
	 */
	public void cleanUpParticleCommunication() {
		ghostParticlesLock.reset();
		arrivingParticlesLock.reset();
		leavingParticles.clear();
		borderParticles.clear();
	}


	/**
	 * Resets the cell list waiting lock.
	 * => Should be called at the end of cell communication.
	 */
	public void cleanUpCellCommunication() {
		ghostCellsLock.reset();
	}


	public void closeSendPorts() {
		communicator.closeSendPorts();
	}


	public void closeReceivePorts() {
		communicator.closeReceivePorts();
	}


	private class GhostCellsIndexesHandler implements IncomingPointsHandler {

		/**
		 * Registers the ghost cells in the same order
		 * as the corresponding border cells at the neighbor.
		 */
		public void handle(List<Point> ghostCellsMap) {
			assert grid != null;
			for (Point cellIndex: ghostCellsMap) {
				globalGhostCells.add(grid.getCell(cellIndex.x, cellIndex.y));
			}
			ghostCellsIndexesLock.setToTrue();
		}
	}


	private class GhostCellsHandler implements IncomingCellsHandler {
		public void handle(List<Cell> cells) {
			assert cells.size() == globalGhostCells.size();
			localGhostCells = cells;
			ghostCellsLock.setToTrue();
		}
	}


	private class GhostParticlesHandler implements IncomingParticlesHandler {
		public void handle(List<Particle> particles) {
			ghostParticles = particles;
			ghostParticlesLock.setToTrue();
		}
	}


	private class ArrivingParticlesHandler implements  IncomingParticlesHandler {

		/**
		 * All arriving particles have to be checked with boundary classes.
		 * All arriving particle become right away also border particles.
		 * Furthermore, they might also lie outside of the simulation area.
		 */
		public void handle(List<Particle> particles) {
			arrivingParticles = particles;
			for (Particle particle: particles) {
				/**
				 * The nulls won't work with hardwall boundaries!!!
				 * TODO inject the solver, force and time step into shared data from classes above
				 */
				particleBoundaries.applyOnParticleCenter(null, null, particle, 0);
			}
			arrivingParticlesLock.setToTrue();
		}
	}
}
