/*
Copyright (C) 2011  Diego Darriba, David Posada

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package es.uvigo.darwin.jmodeltest.exe;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Observable;

import javax.management.RuntimeErrorException;

import mpi.MPI;
import mpi.Request;
import mpi.Status;
import es.uvigo.darwin.jmodeltest.exception.InternalException;
import es.uvigo.darwin.jmodeltest.model.Model;
import es.uvigo.darwin.jmodeltest.observer.ProgressInfo;
import es.uvigo.darwin.jmodeltest.utilities.Utilities;

public class Distributor extends Observable implements Runnable {

	/** MPJ Tag for requesting a new model. */
	public static final int TAG_SEND_REQUEST = 1;

	/** MPJ Tag for sending a new model. */
	public static final int TAG_SEND_MODEL = 2;

	private List<Model> models;
	private RunPhymlMPJ caller;
	/** MPJ Rank of the processor. */
	private int mpjMe;

	/** MPJ Size of the communicator. */
	private int mpjSize;

	/**
	 * The number of models per processor. It will be necessary by the root
	 * process to do the non-uniform gathering
	 */
	private int[] itemsPerProc;

	/**
	 * The array of displacements after the distribution. It will be necessary
	 * by the root process to do the non-uniform gathering
	 */
	private int[] displs;

	public Distributor(List<Model> models, RunPhymlMPJ caller, int mpjMe,
			int mpjSize) {
		this.mpjMe = mpjMe;
		this.mpjSize = mpjSize;
		this.models = models;
		this.caller = caller;
		this.itemsPerProc = new int[mpjSize];
		this.displs = new int[mpjSize];

		Collections.sort(this.models, new ModelComparator());
	}

	public void distribute(List<Model> models) throws InterruptedException {
		int index = 0;
		for (Model model : models) {
			// check root processor
			//
			// This strategy is an easy way to avoid the problem of
			// thread-safety in MPJ-Express. It works correctly, but
			// it also causes to introduce coupling between this class
			// and RunPhymlMPJ having to define two volatile attributes:
			// rootModelRequest and rootModel.
			if (caller.rootModelRequest || mpjSize == 1) {
				while (!caller.rootModelRequest) {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						throw new InternalException(
								"Thread interrupted");
					}
				}
				if (caller.rootModel != null) {
					// finalized model optimization
					notifyObservers(ProgressInfo.SINGLE_OPTIMIZATION_COMPLETED,
							index, caller.rootModel,
							Utilities.displayRuntime(caller.rootModel
									.getComputationTime()));
				}
				notifyObservers(ProgressInfo.SINGLE_OPTIMIZATION_INIT, index,
						model, null);

				caller.rootModel = model;
				caller.rootModelRequest = false;
				itemsPerProc[mpjMe]++;
			} else {
				Model[] computedModel = new Model[1];
				// getModel request
				Request modelRequest = MPI.COMM_WORLD.Irecv(computedModel, 0,
						1, MPI.OBJECT, MPI.ANY_SOURCE, TAG_SEND_REQUEST);
				// prepare model
				Model[] modelToSend = new Model[1];
				modelToSend[0] = model;
				// wait for request
				Status requestStatus = modelRequest.Wait();
				if (computedModel[0] != null) {
					// finalized model optimization
					int recvIndex = models.indexOf(computedModel[0]);
					notifyObservers(ProgressInfo.SINGLE_OPTIMIZATION_COMPLETED,
							index, computedModel[0],
							Utilities.displayRuntime(computedModel[0]
									.getComputationTime()));
					models.set(recvIndex, computedModel[0]);
				}
				// send model
				Request modelSend = MPI.COMM_WORLD.Isend(modelToSend, 0, 1,
						MPI.OBJECT, requestStatus.source, TAG_SEND_MODEL);
				notifyObservers(ProgressInfo.SINGLE_OPTIMIZATION_INIT, index,
						model, null);
				// update structures
				itemsPerProc[requestStatus.source]++;
				// wait for send
				modelSend.Wait();
			}
			index++;
		}
		displs[0] = 0;
		for (int i = 1; i < mpjSize; i++)
			displs[i] = displs[i - 1] + itemsPerProc[i - 1];

		// finalize
		for (int i = 1; i < mpjSize; i++) {
			Model[] computedModel = new Model[1];
			// getModel request
			Request modelRequest = MPI.COMM_WORLD.Irecv(computedModel, 0, 1,
					MPI.OBJECT, MPI.ANY_SOURCE, TAG_SEND_REQUEST);
			Model[] modelToSend = { null };
			// wait for request
			Status requestStatus = modelRequest.Wait();
			if (computedModel[0] != null) {
				int recvIndex = models.indexOf(computedModel[0]);
				notifyObservers(ProgressInfo.SINGLE_OPTIMIZATION_COMPLETED,
						index, computedModel[0],
						Utilities.displayRuntime(computedModel[0]
								.getComputationTime()));
				models.set(recvIndex, computedModel[0]);
			}
			// send null model
			Request modelSend = MPI.COMM_WORLD.Isend(modelToSend, 0, 1,
					MPI.OBJECT, requestStatus.source, TAG_SEND_MODEL);

			modelSend.Wait();
		}
		// check root
		while (!caller.rootModelRequest) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				throw e;
			}
		}
		if (caller.rootModel != null) {
			notifyObservers(ProgressInfo.SINGLE_OPTIMIZATION_COMPLETED, index,
					caller.rootModel, Utilities.displayRuntime(caller.rootModel
							.getComputationTime()));
		}
		caller.rootModel = null;
		caller.rootModelRequest = false;
	}

	public int[] getItemsPerProc() {
		return itemsPerProc;
	}

	public int[] getDispls() {
		return displs;
	}

	@Override
	public void run() {
		try {
			distribute(models);
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw new RuntimeErrorException(new Error(e));
		}
	}

	private class ModelComparator implements Comparator<Model> {

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Model model1, Model model2) {
			int value = 0;
			if (model1 != null && model2 != null)
				value = getWeight(model2) - getWeight(model1);
			return value;
		}

		private int getWeight(Model model) {
			int weight = 0;
			if (model.ispG())
				weight += 4;
			if (model.ispI())
				weight += 2;
			if (model.ispF())
				weight += 1;
			return weight;
		}
	}

	private void notifyObservers(int type, int value, Model model,
			String message) {
		setChanged();
		notifyObservers(new ProgressInfo(type, value, model, message));
	}
}
