/*
Copyright (C) 2009  Diego Darriba

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 2 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.prottest.util.collection;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import pal.alignment.Alignment;
import pal.tree.Tree;
import es.uvigo.darwin.prottest.model.Model;
import es.uvigo.darwin.prottest.util.comparator.ModelWeightComparator;

/**
 * A complex implementation of model collection, which is able to
 * distribute whole set of substitution models amongst a certain
 * number of groups, generally amongst the number of processors.
 * 
 * This class is a composition of model collections, that will have
 * disjunction and completeness properties.
 */
public class ParallelModelCollection extends ModelCollection {

	/** The serialVersionUID. */
	private static final long serialVersionUID = 20090903L;
	
	/** The list of model collections. */
	private List<ModelCollection> modelCollections;
	
	/** The number of groups. */
	private int groups;
	
	/** Defines if the current distribution is valid. */
	private boolean validDistribution = false;
	
	/** The heuristic algorithm to compare models. */
	private ModelWeightComparator comparator;
	
	/**
	 * Instantiates a new parallel model collection.
	 * 
	 * @param allModels the whole set of models
	 * @param groups the number of groups, typically the size of the MPJ communicator
	 */
	public ParallelModelCollection(Model[] allModels, int groups, ModelWeightComparator comparator, Alignment alignment) {
                super(alignment);
		ArrayList<Model> models = new ArrayList<Model>(Arrays.asList(allModels));
		this.comparator = comparator;
		Collections.sort(models, comparator);
		initialize(models, groups);
	}
	
	/**
	 * Instantiates a new parallel model collection.
	 * 
	 * @param allModels the whole set of models.
	 * @param groups the number of groups, typically the size of the MPJ communicator
	 */
	public ParallelModelCollection(ModelCollection allModels, int groups, ModelWeightComparator comparator) {
                super(allModels.getAlignment());
		List<Model> models = new ArrayList<Model>(allModels);
		this.comparator = comparator;
		Collections.sort(models, comparator);
		initialize(models, groups);
	}
	
	/**
	 * Initialize.
	 * 
	 * @param allModels the whole set of models
	 * @param groups the number of groups, typically the size of the MPJ communicator
	 */
	private void initialize(List<Model> allModels, int groups) {
		this.allModels = allModels;
		this.groups = groups;
		modelCollections = new ArrayList<ModelCollection>();
		distributeModels(this.allModels, this.groups);		
	}
	
	/**
	 * Distribute models amongst groups. This method grants completeness
	 * and disjunction of single collections.
	 * 
	 * @param allModels the whole set of models.
	 * @param groups the number of groups to distribute models into
	 */
	private void distributeModels(List<Model> allModels, int groups) {
		
		modelCollections.clear();
		
		// initialize
		for (int i = 0; i < groups; i++)
			modelCollections.add(new SingleModelCollection(getAlignment()));
		
		int maxWeight = 1;
		int currentGroup = 0;
		boolean modelAdded = false;
		
		for (Model model : allModels) {
			modelAdded = false;
			ModelCollection minModelGroup = modelCollections.get(0);
			for (currentGroup = 0; currentGroup < groups; currentGroup++) {
				ModelCollection currentModelGroup = modelCollections.get(currentGroup);
				if (currentModelGroup.getWeight(comparator) + comparator.getWeight(model) <= maxWeight)
				{
					modelAdded = currentModelGroup.addModel(model);
					maxWeight = currentModelGroup.getWeight(comparator);
					break;
				} else if (currentModelGroup.getWeight(comparator) < minModelGroup.getWeight(comparator))
					minModelGroup = currentModelGroup;
			}
			if (!modelAdded) {
				minModelGroup.addModel(model);
				maxWeight = minModelGroup.getWeight(comparator);
			}
		}
		
		validDistribution = true;
	}
	
	/**
	 * Gets a single model collection. This method will return null
	 * if the group parameter is out of rank.
	 * 
	 * @param group the group
	 * 
	 * @return the model iterator
	 */
	public ModelCollection getModelCollection(int group) {
		
		if (!validDistribution)
			distributeModels(this.allModels, this.groups);
		
		if (group < 0 || group > groups)
			return null;
		
		return modelCollections.get(group);
	}

	/* (non-Javadoc)
	 * @see java.util.List#add(int, java.lang.Object)
	 */
        @Override
	public void add(int index, Model element) {
		validDistribution = false;
		super.add(index, element);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#add(es.uvigo.darwin.prottest.model.Model)
	 */
	@Override
	public boolean add(Model e) {
		validDistribution = false;
		return super.add(e);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#addModel(java.lang.String, int, java.util.Properties)
	 */
	@Override
	public boolean addModel(String matrix, int distribution, Properties modelProperties,
			Alignment alignment, Tree tree, int ncat) {
		validDistribution = false;
		return super.addModel(matrix, distribution, modelProperties,
				alignment, tree, ncat);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#addModel(es.uvigo.darwin.prottest.model.Model)
	 */
	@Override
	public boolean addModel(Model model) {
		validDistribution = false;
		return super.addModel(model);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#addModelCartesian(java.util.Collection, java.util.Collection, java.util.Properties)
	 */
	@Override
	public boolean addModelCartesian(Collection<String> matrices, Collection<Integer> distributions, 
			Properties modelProperties, Alignment alignment, Tree tree, int ncat) {
		validDistribution = false;
		return super.addModelCartesian(matrices, distributions, modelProperties,
				alignment, tree, ncat);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#addAll(java.util.Collection)
	 */
	@Override
	public boolean addAll(Collection<? extends Model> c) {
		validDistribution = false;
		return super.addAll(c);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#addAll(int, java.util.Collection)
	 */
	@Override
	public boolean addAll(int index, Collection<? extends Model> c) {
		validDistribution = false;
		return super.addAll(index, c);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#clear()
	 */
	@Override
	public void clear() {
		validDistribution = false;
		super.clear();
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#remove(java.lang.Object)
	 */
	@Override
	public boolean remove(Object o) {
		validDistribution = false;
		return super.remove(o);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#remove(int)
	 */
	@Override
	public Model remove(int index) {
		validDistribution = false;
		return super.remove(index);
	}

	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#removeAll(java.util.Collection)
	 */
	@Override
	public boolean removeAll(Collection<?> c) {
		validDistribution = false;
		return super.removeAll(c);
	}
	
	/* (non-Javadoc)
	 * @see es.uvigo.darwin.prottest.util.collection.ModelCollection#set(int, es.uvigo.darwin.prottest.model.Model)
	 */
	@Override
	public Model set(int index, Model element) {
		validDistribution = false;
		return super.set(index, element);
	}
}
