package lu.tudor.santec.gecamed.billing.utils.rules;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Act;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.RateIndex;
import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: ActRemover.java,v $
 * <br>Revision 1.8  2013-06-24 08:21:36  ferring
 * <br>wrong chapter / sous section was defined as mutually exclusive
 * <br>
 * <br>Revision 1.7  2013-06-21 07:14:19  ferring
 * <br>X-codes of T8_3_1 have been set to zero
 * <br>
 * <br>Revision 1.6  2013-04-02 12:20:29  ferring
 * <br>debug logging removed
 * <br>
 * <br>Revision 1.5  2013-03-22 10:27:03  ferring
 * <br>UnionActs added and billing bugs fixed (since last not released commit)
 * <br>
 * <br>Revision 1.4  2013-03-14 10:40:20  ferring
 * <br>Billing rules corrected
 * <br>
 * <br>Revision 1.3  2013-03-01 11:15:12  ferring
 * <br>Parts of billing rule logic moved to helper classes.
 * <br>Special cumulation rule for gynaecologists implemented.
 * <br>
 * <br>Revision 1.2  2013-02-28 13:23:25  ferring
 * <br>commentation added
 * <br>
 * <br>Revision 1.1  2013-02-27 08:04:29  ferring
 * <br>Rule system "outsourced" to helper classes, that can be converted to drools functions, if needed
 * <br>
 */
public class ActRemover
{
	/* ======================================== */
	// 		CONSTANTS
	/* ======================================== */
	
	/** the logger Object for this class */
	private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ActRemover.class.getName());
	
	private static int lastGroupKey	= 0;
	
	/**
	 * Here are all codes mapped to group IDs, where only one of the codes of a group is allowed
	 */
	private static final HashMap<String, Collection<Integer>>	illegalCodeGroups		= new HashMap<String, Collection<Integer>>();
	
	
	/** 
	 * Here are groups of codes defined that exclude each other.<br>
	 * The map incompatibleCodeGroup defines the group (act mapped to a list of group IDs).
	 */
	private static final HashMap<String, Collection<Integer>>	incompatibleCodeGroup	= new HashMap<String, Collection<Integer>>();
	/**
	 * Here are groups of codes defined that exclude each other.<br>
	 * The map incompatibleGroups defines the groups, that exclude each other (group ID to a list of group IDs).
	 */
	private static final HashMap<Integer, Collection<Integer>>	incompatibleGroups		= new HashMap<Integer, Collection<Integer>>();
	
	/** 
	 * <p>Here are groups of codes defined where the codes of one group depends on the existence of at least one code of the other group.</p>
	 * <p>This part is where the codes are stored, that have dependencies on other codes. They are mapped to their depending codes.</p>
	 * <b>The dependency of a code can only be defined once, although several codes or code groups can depend on a code.
	 */
	private static final HashMap<String, Set<String>>			dependentCodes			= new HashMap<String, Set<String>>();
	
	/**
	 * <p>Here are all codes defined that can be cumulated exclusively with those codes, they're mapped to.</p>
	 * <p>Finding a code here means, that this code or all other codes of this session must be removed, depending on which is worth more.</p>
	 */
	private static final HashMap<String, Set<String>>			exclusivelyValidCodes	= new HashMap<String, Set<String>>();
	
	// the incompatible groups
	private static final int	INCOMPATIBLE_GROUP_T_6_1_1	= 0;
	private static final int	INCOMPATIBLE_GROUP_T_6_1_2	= 1;
	private static final int	INCOMPATIBLE_GROUP_G_2_2_1	= 2;
	
	
	
	/* ======================================== */
	// 		INITIALIZATION
	/* ======================================== */
	
	static
	{
		/* **************************************** */
		// 		FILL THE MAPs
		/* **************************************** */
		
		// define acts that cannot be cumulated with each other here:
		// see nomenclature T 6.1.1 remark 2)
		defineIllegalRateIndexGroup("T_6_1_1");
		
		// see nomenclature T 6.1.3 remark 3)
		defineIllegalGroup("6A51", "6A52", "6A53", "6A54", "6A55");
		
		// see nomenclature Art. 17 §4  T 8.3.1)
		defineIllegalRateIndexGroup("T_8_3_1");
		// see nomenclature T 8.3.2 remark
		defineIllegalRateIndexGroup("T_8_3_2");
		
		// only one consultation, visit and hospitalization code per day / session is allowed for medical and dental invoices
		defineIllegalRateIndexGroup("G_1", "G_2", "G_4", "G_6", "G_7", "G_8", "G_10", "DG_1", "DG_4", "DG_6");
		
		
		// define acts that can be cumulated with others in the same group, but not with acts of other groups
		defineIncompatibleCodeGroup(INCOMPATIBLE_GROUP_T_6_1_1, "6A11", "6A12", "6A13", "6A14", "6A15", "6A21", "6A22", "6A23");
		defineIncompatibleCodeGroup(INCOMPATIBLE_GROUP_T_6_1_2, "6A31", "6A32", "6A33", "6A34", "6A35");
		defineIncompatibleCodeGroup(INCOMPATIBLE_GROUP_G_2_2_1, "V20", "V21", "V22", "V23", "V24", "V25", "V26");
		
		// see nomenclature T 6.1.2 (headline)
		defineIncompatibleGroups(INCOMPATIBLE_GROUP_T_6_1_1, INCOMPATIBLE_GROUP_T_6_1_2);
		// see nomenclature T 6.1.1 remark 4)
		defineIncompatibleGroups(INCOMPATIBLE_GROUP_T_6_1_1, INCOMPATIBLE_GROUP_G_2_2_1);
		
		
		// see nomenclature art. 6 §1 - kilometers
		defineDependentCodeGroup(getCodesOfRateIndexes("G_3"), GECAMedUtils.add(getCodesOfRateIndexes("G_2_1"), "1F11"));
		
		
		// define acts, that can be cumulated only with those they're mapped to
		// see nomenclature G_1_9 Remark 1)
		defineExclusivelyValidCodeGroup(getCodesOfRateIndexes("G_9"), getCodesOfRateIndexes("G_1_1", "G_1_2", "G_2_1", "G_3"));
	}
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	/**
	 * Removes all illegal act combinations from the invoice, according to the entries
	 * in the map illegalCodeGroups.
	 */
	public static void removeIllegalActCombinationsOfInvoice (RulesObjectsHolder roh)
	{
//		Invoice invoice	= RulesObjectsHolder.getInvoiceForRules();
		
		
		for (List<Act> acts : roh.getSortedActs())
		{
			if (acts != null && !acts.isEmpty() && roh.isDayRuleOption(acts.get(0).getSessionDate(), 
					RulesObjectsHolder.OPTION_PERFORM_ELIMINATION))
			{
				// check for all acts of each day
				removeIllegalActCombinations(acts);
			}
		}
		roh.printActList("After: ActREmover.removeIllegalActCombinationsOfInvoice", "Removes all illegal act combinations from the invoice, according to the entries in the map illegalCodeGroups.");
	}


	/**
	 * Removes all illegal act combinations from the given acts, according to the entries
	 * in the map illegalCodeGroups.
	 */
	private static void removeIllegalActCombinations (Collection<Act> acts)
	{
		HashMap<Integer, Act>	illegalFounds	= new HashMap<Integer, Act>();
		Collection<Integer>		groupKeys;
		
		String	code;
		Act		foundAct;
		Act		actToRemove;
		
		
		// check each act ...
		for (Act currentAct : acts)
		{
			code		= currentAct.getCode();
			
			// ... if it belongs to an illegal group
			groupKeys	= illegalCodeGroups.get(code);
			if (groupKeys != null && groupKeys.size() > 0)
			{
				// this act belongs to one or more illegal groups
				for (Integer groupKey : groupKeys)
				{
//					hash		= hashGroupOfDay(groupKey, currentAct.getPerformedCalendar());
					foundAct	= illegalFounds.get(groupKey);
					// keep only the one with the highest amount:
					if (foundAct == null)
					{
						// no act of this group for this day found
						actToRemove	= illegalFounds.put(groupKey, currentAct);
					}
					else if (currentAct.getAmount() > foundAct.getAmount()
							// just to have a definite order, in case the amount is equals
							|| (currentAct.getAmount() == foundAct.getAmount()
							&& currentAct.getCode().compareToIgnoreCase(foundAct.getCode()) > 0))
					{
						// the current act has a higher amount, than the stored act 
						// -> replace it and set the found act to 0
						actToRemove	= illegalFounds.put(groupKey, currentAct);
					}
					else
					{
						// the found act has a higher or equal value, compared to the current act
						// -> set the current act to 0
						actToRemove	= currentAct;
					}
					
					if (actToRemove != null)
					{
						removeAct(actToRemove);
					}
				}
			}
		}
	}
	

	public static void removeIncompatibleActCombinationsOfInvoice (RulesObjectsHolder roh)
	{
//		Invoice invoice	= RulesObjectsHolder.getInvoiceForRules();
		
		
		for (List<Act> acts : roh.getSortedActs())
		{
			if (acts != null && !acts.isEmpty() && roh.isDayRuleOption(acts.get(0).getSessionDate(), 
					RulesObjectsHolder.OPTION_PERFORM_ELIMINATION))
			{
				// check for all acts of each day
				removeIncompatibleActCombinations(acts);
			}
		}
		roh.printActList("After: ActRemover.removeIncompatibleActCombinationsOfInvoice", "This method removes incompatible groups of acts and just let's the group with the highest estimated amount in the invoice.");
	}
	


	/**
	 * This method removes incompatible groups of acts and just let's 
	 * the group with the highest estimated amount in the invoice. 
	 */
	private static void removeIncompatibleActCombinations (Collection<Act> acts)
	{
		Map<Integer, List<Act>>		incompatibleFounds;
		TreeMap<Double, List<Act>>	valuedActs;
		
		Collection<Integer>	groupKeys;
		List<Act>			groupedActs;
		List<Act>			incompatibleActs;
		Collection<Integer>	incompatibleKeys;
		
		double	amount;
		String	code;
		
		
		incompatibleFounds	= new HashMap<Integer,List<Act>>();
		// check each act ...
		for (Act currentAct : acts)
		{
			code		= currentAct.getCode();
			// ... if its code is in a group of acts, that is incompatible with other acts
			groupKeys	= incompatibleCodeGroup.get(code);
			if (groupKeys != null && !groupKeys.isEmpty())
			{
				// act is part of one or more groups
				for (Integer groupKey : groupKeys)
				{
					// store the act for each group
					groupedActs	= incompatibleFounds.get(groupKey);
					if (groupedActs == null)
					{
						groupedActs = new LinkedList<Act>();
						incompatibleFounds.put(groupKey, groupedActs);
					}
					// add the act for the group of this day
					groupedActs.add(currentAct);
				}
			}
		}
		
		
		/* Now all acts are in the map incompatibleFounds.
		 * Look for each day, if there are incompatible groups,
		 * estimate the most profitable group and eliminate the 
		 * acts of the other groups.
		 * IMPORTANT: This is just an estimation of which is the
		 * 	most profitable group. It is most likely the most 
		 * 	profitable group, but NOT with guaranty. 
		 * 	-> If you like to define an algorithm that guarantees 
		 * 	that, have fun ...
		 */
		
		valuedActs	= new TreeMap<Double, List<Act>>();
		for (Integer incompatibleKey : incompatibleFounds.keySet())
		{
			// for each key found, get all incompatible groups
			incompatibleKeys = incompatibleGroups.get(incompatibleKey);
			// stores the acts and the amount of the acts
			valuedActs.clear();
			for (Integer key : incompatibleKeys)
			{
				// for each group incompatible with the found group
				incompatibleActs	= incompatibleFounds.get(key);
				if (incompatibleActs == null)
					continue;
				
				// calculate / estimate the amount
				amount				= estimateValueOfActs(incompatibleActs);
				if (amount > 0)
				{
					incompatibleActs	= valuedActs.put(amount, incompatibleActs);
					if (incompatibleActs != null)
						removeActs(incompatibleActs);
				}
			}
			
			// remove the group with the highest amount ...
			if (!valuedActs.isEmpty())
				valuedActs.remove(valuedActs.lastKey());
			
			// ... and set the rest to 0
			for (List<Act> actsToRemove : valuedActs.values())
			{
				removeActs(actsToRemove);
			}
		}
	}
	
	
	public static void removeActsWithoutDependencies (RulesObjectsHolder roh)
	{
		for (List<Act> acts : roh.getSortedActs())
		{
			if (acts != null && !acts.isEmpty() && roh.isDayRuleOption(acts.get(0).getSessionDate(), 
					RulesObjectsHolder.OPTION_PERFORM_ELIMINATION))
			{
				removeActsWithoutDependencies(acts);
			}
		}
		roh.printActList("After: ActRemover.removeActsWithoutDependencies", "Removes all Acts that depend on other acts, but these are not present");
	}
	
	
	private static boolean removeActsWithoutDependencies (List<Act> acts)
	{
		HashMap<Act, Set<String>>	actDependencyMap	= new HashMap<Act, Set<String>>();
		List<Act>					dependencyFoundActs;
		Set<Act>					dependencyMissingActs;
		
		
		for (Act act : acts)
		{
			if (act.getQuantity() <= 0)
				// take only acts into consideration, whose quantity is higher than 0
				continue;
			
			// find acts that have dependencies
			if (dependentCodes.containsKey(act.getCode()))
			{
				actDependencyMap.put(act, dependentCodes.get(act.getCode()));
			}
		}
		
		if (actDependencyMap.isEmpty())
			// no acts with dependencies found
			return false;
		
		dependencyFoundActs	= new LinkedList<Act>();
		for (Act act : acts)
		{
			if (act.getQuantity() <= 0)
				// take only acts into consideration, whose quantity is higher than 0
				continue;
			
			// walk through the acts
			dependencyFoundActs.clear();
			for (Act dependingAct : actDependencyMap.keySet())
			{
				// for each act found with dependencies, check if it depends on this act.
				if (actDependencyMap.get(dependingAct).contains(act.getCode()))
				{
					// if it does depend on this act, mark it
					dependencyFoundActs.add(dependingAct);
				}
			}
			
			for (Act dependingAct : dependencyFoundActs)
			{
				// remove the marked acts
				actDependencyMap.remove(dependingAct);
			}
		}
		
		// the acts, that are left, have are missing their dependencies
		dependencyMissingActs	= actDependencyMap.keySet();
		if (dependencyMissingActs != null && !dependencyMissingActs.isEmpty())
		{
			// set their quantity to 0
			for (Act act : dependencyMissingActs)
			{
				Cumulations.removeAct(act);
			}
			
			return true;
		}
		return false;
	}
	
	
	public static void removeActsWithExclusivelyValidCodes (RulesObjectsHolder roh)
	{
		for (List<Act> acts : roh.getSortedActs())
		{
			if (acts != null && !acts.isEmpty() 
					&& roh.isDayRuleOption(acts.get(0).getSessionDate(), RulesObjectsHolder.OPTION_PERFORM_ELIMINATION))
			{
				removeActsWithExclusivelyValidCodes(acts);
			}
		}
		roh.printActList("After: ActRemover.removeActsWithExclusivelyValidCodes", "see nomenclature G_1_9 Remark 1");
	}
	
	
	private static void removeActsWithExclusivelyValidCodes (List<Act> acts)
	{
		String		code;
		Set<String>	validCodes;
		List<Act>	validActs	= new LinkedList<Act>();
		List<Act>	invalidActs	= new LinkedList<Act>();
		double		validActsValue;
		double		invalidActsValue;
		
		
		for (Act currentAct : acts)
		{
			if (currentAct.getQuantity().intValue() == 0)
				continue;
			
			code		= currentAct.getCode();
			validCodes	= exclusivelyValidCodes.get(code);
			
			if (validCodes == null)
				// this code has no exclusive codes
				continue;
			
			validActs.clear();
			validActs.add(currentAct);
			invalidActs.clear();
			validActsValue		= currentAct.getAmount();
			invalidActsValue	= 0.0;
			
			for (Act act : acts)
			{
				if (act.equals(currentAct)
						|| act.getQuantity() == 0)
				{
					continue;
				}
				else if (validCodes.contains(act.getCode()))
				{
					validActs.add(act);
					validActsValue	+= act.getAmount();
				}
				else
				{
					invalidActs.add(act);
					invalidActsValue+= act.getAmount();
				}
			}
			
			if (validActsValue > invalidActsValue)
			{
				for (Act act : invalidActs)
					Cumulations.removeAct(act);
			}
			else
			{
				Cumulations.removeAct(currentAct);
			}
		}
	}
	
	
	
	/* ======================================== */
	// 		HELP METHODS
	/* ======================================== */
	
	private static void defineIllegalRateIndexGroup (String ... rateIndices)
	{
		defineIllegalRateIndexGroup(true, rateIndices);
	}
	
	
	private static void defineIllegalRateIndexGroup (boolean excludeMAndXCodes, String ... rateIndices)
	{
		RateIndex 		index;
		List<String>	codes		= new LinkedList<String>();
		int				groupKey	= getNewGroupKey();
		
		
		for (String rateIndex : rateIndices)
		{
			index	= RulesObjectsHolder.getRateIndex(rateIndex);
			codes.addAll(RulesObjectsHolder.getAllCodesOfRateIndex(index));
		}
		
		for (String code : codes)
		{
			if (code == null)
				continue;
			
			code = code.trim().toUpperCase();
			if (excludeMAndXCodes
					&& (code.endsWith("X")
					|| code.endsWith("M")))
				continue;
			
			addIllegalCode(groupKey, code);
		}
	}
	
	
	/**
	 * Adds all the codes to a illegal group.
	 * 
	 * @param codes All codes that exclude each other
	 */
	private static void defineIllegalGroup (String ... codes)
	{
		// define a unique group key
		int	groupKey	= getNewGroupKey();
		
		
		for (String code : codes)
		{
			addIllegalCode(groupKey, code);
		}
	}
	
	
	private static void addIllegalCode (int groupKey, String code)
	{
		Collection<Integer>	groupKeys;
		
		
		// for each code, check whether there are already any existing groups ...
		groupKeys	= illegalCodeGroups.get(code);
		if (groupKeys == null)
		{
			// ... if not, define a new one ...
			groupKeys	= new HashSet<Integer>();
			illegalCodeGroups.put(code, groupKeys);
		}
		// ... and add this group key
		groupKeys.add(groupKey);
	}
	
	
	private static synchronized int getNewGroupKey ()
	{
		return ++lastGroupKey;
	}
	
	
	/**
	 * Adds all the codes to the incompatible group, defined by the group key
	 * 
	 * @param groupKey The key, that defines the incompatible group
	 * @param codes All codes that belong to the incompatible group
	 */
	private static void defineIncompatibleCodeGroup (int groupKey, String ... codes)
	{
		for (String code : codes)
			addIncompatibleCode(groupKey, code);
	}
	
	
//	private static void defineIncompatibleCodeGroup (int groupKey, Iterable<String> codes)
//	{
//		for (String code : codes)
//			addIncompatibleCode(groupKey, code);
//	}
	
	
	private static void addIncompatibleCode (int groupKey, String code)
	{
		// for each code check whether there are already any existing groups ...
		Collection<Integer>	groupKeys	= incompatibleCodeGroup.get(code);
		if (groupKeys == null)
		{
			// ... if not define a new one ...
			groupKeys	= new HashSet<Integer>();
			incompatibleCodeGroup.put(code, groupKeys);
		}
		// ... and add this group key
		groupKeys.add(groupKey);
	}
	
	
	/**
	 * Defines which groups exclude which other group
	 * 
	 * @param groupKeys All the groups, that exclude each other
	 */
	private static void defineIncompatibleGroups (int ... groupKeys)
	{
		Set<Integer>		keys	= new HashSet<Integer>(groupKeys.length);
		Collection<Integer>	groups; 
		
		
		// add all codes from the array into the HashSet (duplicates will be removed)
		for (Integer key : groupKeys)
			keys.add(key);
		
		for (int key : groupKeys)
		{
			// for each group key, check whether are already existing groups
			groups	= incompatibleGroups.get(key);
			if (groups == null)
				// ... if not, simply put this group for the current key
				incompatibleGroups.put(key, keys);
			else
				// ... else add them
				groups.addAll(keys);
		}
	}
	
	
	/**
	 * Defines which codes depend on at least one code of a group.
	 * 
	 * @param codesWithDependency The codes that depend on dependingCodes.
	 * @param dependeningCodes The codes, the codesWithDependency depend on.
	 */
	private static void defineDependentCodeGroup (Collection<String> codesWithDependency, Collection<String> dependeningCodes)
	{
		Set<String>	oldDependencies;
		Set<String>	addedDependencies;
		Set<String>	dependingSet;
		
		
		// make sure to map a set
		if (dependeningCodes instanceof Set)
			dependingSet = (Set<String>) dependeningCodes;
		else
			dependingSet = new HashSet<String>(dependeningCodes);
		
		for (String code : codesWithDependency)
		{
			// map each code with the dependencies of the group
			oldDependencies	= dependentCodes.put(code, dependingSet);
			if (oldDependencies != null)
			{
				// this code already had dependencies ...
				// ... log a warning
				logger.warn("Duplicate dependencies for code '"+code+"'. The dependencies will be added.\n"
						+ "Please define all dependencies in one set, to avoid errors.");
				
				// ... and add both dependency groups (or the one, that completely contains the other group)
				if (oldDependencies.containsAll(dependingSet))
				{
					addedDependencies	= oldDependencies;
				}
				else if (dependingSet.containsAll(oldDependencies))
				{
					addedDependencies	= dependingSet;
				}
				else
				{
					addedDependencies	= new HashSet<String>(oldDependencies.size() + dependingSet.size());
					addedDependencies.addAll(oldDependencies);
					addedDependencies.addAll(dependingSet);
				}
				dependentCodes.put(code, addedDependencies);
			}
		}
	}
	
	
	private static void defineExclusivelyValidCodeGroup (Set<String> codesToCheck, Collection<String> validCodes)
	{
		Set<String> storedValidCodes;
		
		for (String code : codesToCheck)
		{
			storedValidCodes = exclusivelyValidCodes.get(code);
			if (storedValidCodes == null)
			{
				storedValidCodes = new HashSet<String>();
				exclusivelyValidCodes.put(code, storedValidCodes);
			}
			storedValidCodes.addAll(validCodes);
		}
	}
	
	
	/**
	 * This method estimates the final amount of the acts in an invoice.
	 * Doing so, the highest value will be calculated with 100%, the 
	 * second and third highest will be calculated with 50% and all 
	 * the others simply won't be taken into consideration. 
	 * 
	 * @param acts All acts to estimate the value of
	 * 
	 * @return The estimated value
	 */
	private static double estimateValueOfActs (List<Act> acts)
	{
		double amount	= 0.0;
		
		try
		{
			Collections.sort(acts, ActSorter.DEFAULT_VALUE_COMPARATOR);
		}
		catch (NullPointerException e)
		{
			e.printStackTrace();
		}
		for (int i = 0; i < acts.size(); i++)
		{
			if (acts.get(i).getQuantity() == 0)
				return amount;
			
			if (i == 0)
				amount += acts.get(i).monetize();
			else if (i < 3)
				amount += acts.get(i).monetize() / 2;
			else
				break;
		}
		
		return amount;
	}
	
	
	private static void removeActs (Collection<Act> acts)
	{
		for (Act act : acts)
		{
			removeAct(act);
		}
	}
	
	
	private static void removeAct (Act actToRemove)
	{
		// sets the value of this act
		actToRemove.setQuantity(0);
		actToRemove.clearSuffix('R');
		actToRemove.monetize();
	}
	
	
	private static Set<String> getCodesOfRateIndexes (String ... rateIndexes)
	{
		if (rateIndexes == null || rateIndexes.length == 0)
			return new HashSet<String>(0);
		else if (rateIndexes.length == 1)
			return new HashSet<String>(RulesObjectsHolder.getAllCodesOfRateIndex(RulesObjectsHolder.getRateIndex(rateIndexes[0])));
		else
		{
			Set<String> codes	= new HashSet<String>();
			
			for (String rate : rateIndexes)
				codes.addAll(RulesObjectsHolder.getAllCodesOfRateIndex(RulesObjectsHolder.getRateIndex(rate)));
			
			return codes;
		}
	}
	
	
//	/**
//	 * Creates a unique hash, to identify a group on a specific day
//	 * 
//	 * @param groupId The id of the group to identify
//	 * @param cal The 
//	 * @return
//	 */
//	private static int hashGroupOfDay (int groupId, Calendar cal)
//	{
//		return (groupId + "|" + 
//				cal.get(Calendar.YEAR) + "-" + 
//				cal.get(Calendar.DAY_OF_YEAR))
//				.hashCode();
//	}
}
