package org.openpixi.pixi.distributed.ibis;

import ibis.ipl.*;
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.util.Point;

import java.io.IOException;
import java.util.List;

/**
 * Handles the exchange of shared data during the simulation.
 */
public class WorkerToWorker {

	/* The following constants identify the incoming message. */
	private static final int ARRIVING_PARTICLES_MSG = 0;
	private static final int GHOST_PARTICLES_MSG = 1;
	private static final int GHOST_CELLS_MSG = 2;
	private static final int GHOST_CELLS_INDEXES_MSG = 3;

	private IbisRegistry registry;

	private ReceivePort recvPort;
	private SendPort sendPort;

	/** ID of the neighbor with whom this communicator communicates. */
	private int neighborID;

	/* Handlers of upcalls for higher level classes. */
	private IncomingPointsHandler ghostCellsIndexesHandler;
	private IncomingCellsHandler ghostCellsHandler;
	private IncomingParticlesHandler ghostParticlesHandler;
	private IncomingParticlesHandler arrivingParticlesHandler;


	public void setGhostCellsHandler(IncomingCellsHandler ghostCellsHandler) {
		this.ghostCellsHandler = ghostCellsHandler;
	}

	public void setGhostParticlesHandler(IncomingParticlesHandler ghostParticlesHandler) {
		this.ghostParticlesHandler = ghostParticlesHandler;
	}

	public void setArrivingParticlesHandler(IncomingParticlesHandler arrivingParticlesHandler) {
		this.arrivingParticlesHandler = arrivingParticlesHandler;
	}

	public void setGhostCellsIndexesHandler(IncomingPointsHandler ghostCellsIndexesHandler) {
		this.ghostCellsIndexesHandler = ghostCellsIndexesHandler;
	}


	public WorkerToWorker(IbisRegistry registry, int neighborID) {
		this.registry = registry;
		this.neighborID = neighborID;

		// One ibis instance can not have 2 receive ports with the same ID; thus,
		// we need to number the receive ports according to the neighbors they are connected to.
		try {
			recvPort = registry.getIbis().createReceivePort(
					PixiPorts.ONE_TO_ONE_PORT,
					PixiPorts.EXCHANGE_PORT_ID + neighborID,
					new IncomingMessageHandler());
			recvPort.enableConnections();
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	/**
	 * To avoid deadlock the send ports should be connected after all the receive ports are created.
	 * Furthermore, we enable upcalls here to avoid upcalls being called to early.
	 */
	public void initializeConnection() {
		recvPort.enableMessageUpcalls();
		try {
			sendPort = registry.getIbis().createSendPort(PixiPorts.ONE_TO_ONE_PORT);
			int myID = registry.convertIbisIDToWorkerID(registry.getIbis().identifier());
			sendPort.connect(
					registry.convertWorkerIDToIbisID(neighborID),
					PixiPorts.EXCHANGE_PORT_ID + myID);
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	public void sendLeavingParticles(List<Particle> leavingParticles) {
		try {
			WriteMessage wm = sendPort.newMessage();
			wm.writeInt(ARRIVING_PARTICLES_MSG);
			wm.writeObject(leavingParticles);
			wm.finish();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	public void sendBorderParticles(List<Particle> borderParticles) {
		try {
			WriteMessage wm = sendPort.newMessage();
			wm.writeInt(GHOST_PARTICLES_MSG);
			wm.writeObject(borderParticles);
			wm.finish();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	public void sendBorderCells(List<Cell> borderCells) {
		try {
			WriteMessage wm = sendPort.newMessage();
			wm.writeInt(GHOST_CELLS_MSG);
			wm.writeObject(borderCells);
			wm.finish();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	public void closeSendPorts() {
		try {
			sendPort.close();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	public void closeReceivePorts() {
		try {
			recvPort.close();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	public void sendBorderCellsMap(List<Point> borderCellsMap) {
		try {
			WriteMessage wm = sendPort.newMessage();
			wm.writeInt(GHOST_CELLS_INDEXES_MSG);
			wm.writeObject(borderCellsMap);
			wm.finish();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	private class IncomingMessageHandler implements MessageUpcall {
		public void upcall(ReadMessage readMessage) throws IOException, ClassNotFoundException {
			int msgType = readMessage.readInt();
			switch (msgType) {

				case GHOST_CELLS_INDEXES_MSG:
					List<Point> indexes = (List<Point>)readMessage.readObject();
					ghostCellsIndexesHandler.handle(indexes);
					return;

				case ARRIVING_PARTICLES_MSG:
					List<Particle> particles = (List<Particle>)readMessage.readObject();
					arrivingParticlesHandler.handle(particles);
					return;

				case GHOST_PARTICLES_MSG:
					particles = (List<Particle>)readMessage.readObject();
					ghostParticlesHandler.handle(particles);
					return;

				case GHOST_CELLS_MSG:
					List<Cell> cells = (List<Cell>)readMessage.readObject();
					ghostCellsHandler.handle(cells);
					return;

				default:
					assert false: "Unreachable code detected!";
			}
		}
	}
}
