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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.persistence.Transient;

import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Act;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Invoice;
import lu.tudor.santec.gecamed.billing.utils.ActComparator;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: ActSorter.java,v $
 * <br>Revision 1.3  2013-07-23 08:12:54  ferring
 * <br>Rule system only applies on actes techniques and actes generaux
 * <br>
 * <br>Revision 1.2  2013-04-23 14:17:05  ferring
 * <br>Constructor accepts Collection instead of List
 * <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>
 * <br>Revision 1.1  2013-02-22 15:36:00  ferring
 * <br>Cumulation rules improved and rules extended for gynaecologists
 * <br>
 */

public class ActSorter implements Collection<Act>, PropertyChangeListener, Serializable
{
	/* ======================================== */
	// 		CONSTANTS
	/* ======================================== */
	
	private static final long	serialVersionUID	= 1L;
	
	public static transient final ActComparator	DEFAULT_VALUE_COMPARATOR	= getDefaultValueComparator();
	public static transient final ActComparator	RULE_VALUE_COMPARATOR		= getRuleValueComparator();
	
	
	
	/* ======================================== */
	// 		MEMBERS
	/* ======================================== */
	
	private LinkedList<Act>	data;
	
	private boolean			sorted	= false;
	
	
	
	/* ======================================== */
	// 		CONSTRUCTORS
	/* ======================================== */
	
	public ActSorter (Collection<Act> all)
	{
		this.data	= new LinkedList<Act>(all);
	}
	
	
	public ActSorter ()
	{
		this.data	= new LinkedList<Act>();
	}
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	public LinkedList<Act> getData ()
	{
		return data;
	}
	
	
	public int size ()
	{
		return data.size();
	}
	
	
	public boolean isEmpty ()
	{
		return data.isEmpty();
	}
	
	
	public boolean contains (Object o)
	{
		return data.contains(o);
	}
	
	
	public boolean containsAll (Collection<?> c)
	{
		return data.containsAll(c);
	}
	
	
	public Iterator<Act> iterator ()
	{
		if (!isSorted())
			sort();
		
		return data.iterator();
	}
	
	
	public Object[] toArray ()
	{
		if (!isSorted())
			sort();
		
		return data.toArray();
	}
	
	
	public <T> T[] toArray (T[] a)
	{
		if (!isSorted())
			sort();
		
		return data.toArray(a);
	}
	
	
	public boolean remove (Object o)
	{
		if (o instanceof Act)
			((Act) o).removeValueListener(this);
		return data.remove(o);
	}
	
	
	public boolean removeAll (Collection<?> c)
	{
		for (Object o : c)
		{
			if (o instanceof Act)
				((Act) o).removeValueListener(this);
		}
		return data.removeAll(c);
	}
	
	
	public void clear ()
	{
		for (Act act : data)
			act.removeValueListener(this);
		data.clear();
	}
	
	
	public boolean retainAll (Collection<?> c)
	{
		// not needed and not supported
		throw new RuntimeException("This method is not needed and therefore not supported by this class");
	}
	
	
	public boolean add (Act e)
	{
		invalidate();
		e.updateValueListener(this);
		return data.add(e);
	}
	
	
	public boolean addAll (Collection<? extends Act> c)
	{
		invalidate();
		for (Act e : c)
			e.updateValueListener(this);
		return data.addAll(c);
	}
	
	
	@Transient
	public void propertyChange (PropertyChangeEvent evt)
	{
		invalidate();
	}
	
	
	public static void filterActs (RulesObjectsHolder roh, Pattern indexFilterPattern)
	{
		Invoice			invoice		= roh.getInvoice();
		Set<Act>		ruleActs	= new HashSet<Act>();
		Collection<Act>	allActs		= roh.unfilterActs();
		
		
		allActs.addAll(invoice.getActs());
		for (Act act : allActs)
		{
			if (indexFilterPattern.matcher(
					RulesObjectsHolder.getIndexLabelOfCode(act.getCode())).matches())
			{
				// apply the rules on this act
				ruleActs.add(act);
			}
			else
			{
				// do not apply any rule on this act
				roh.filterAct(act);
			}
		}
		
		invoice.setActs(ruleActs);
		invoice.monetize();
	}
	
	
	public static void unfilterActs (RulesObjectsHolder roh)
	{
		Invoice		invoice		= roh.getInvoice();
		Set<Act>	acts		= invoice.getActs();
		
		
		acts.addAll(roh.unfilterActs());
		invoice.setActs(acts);
		invoice.monetize();
	}
	
	
	private void sort ()
	{
		if (!isSorted())
		{
			Collections.sort(data, DEFAULT_VALUE_COMPARATOR);
			sorted	= true;
		}
	}
	
	
	public static <T extends List<Act>> T sort (T data, RulesObjectsHolder roh, double ... percentages)
	{
		LinkedList<Act>	sortedData		= new LinkedList<Act>();
		Double			lastPercentage	= null;
		double			percentage;
		
		
		for (int index = 0; index < percentages.length && !data.isEmpty(); index++)
		{
			percentage	= percentages[index];
			if (lastPercentage != null && lastPercentage.doubleValue() == percentage)
			{
				sortedData.add(data.remove(0));
			}
			else
			{
				// calculate and set the rule value
				if (percentage == 1.0)
				{
					for (Act a : data)
						a.setRuleValue(roh.calculateFullRankValue(a));
				}
				else
				{
					for (Act a : data)
						a.setRuleValue(roh.calculateReducedRankValue(a, percentage));
				}
				// sort by rule value
				Collections.sort(data, RULE_VALUE_COMPARATOR);
				// add the highest value
				sortedData.add(data.remove(0));
				// save the current percentage as last percentage
				lastPercentage	= Double.valueOf(percentage);
			}
		}
		
		for (Act a : data) {
//			a.setRuleValue(a.getValue());			
			a.setRuleValue(0.0);
		}
		
		// sort the left acts with their default value
		Collections.sort(data, RULE_VALUE_COMPARATOR);
		while (!data.isEmpty())
			sortedData.add(data.remove(0));
		
		data.clear();
		data.addAll(sortedData);
		
		return data;
	}
	
	
	
	/* ======================================== */
	// 		HELP METHODS
	/* ======================================== */
	
	private void invalidate ()
	{
		sorted	= false;
	}
	
	
	private boolean isSorted ()
	{
		return sorted;
	}


	private static ActComparator getDefaultValueComparator ()
	{
		ActComparator comparators = new ActComparator ();
		
		comparators.addSortCriterion(ActComparator.c_Value, ActComparator.c_Descending);
		// add this, to have a clear sorting, even if the value is the equal
		comparators.addSortCriterion(ActComparator.c_Code, ActComparator.c_Ascending);
		comparators.addSortCriterion(ActComparator.c_Id, ActComparator.c_Ascending);
		
		return comparators;
	}
	
	
	private static ActComparator getRuleValueComparator ()
	{
		ActComparator comparators = new ActComparator ();
		
		comparators.addSortCriterion(ActComparator.c_RuleValue, ActComparator.c_Descending);
		// add this, to have a clear sorting, even if the value is the equal
		comparators.addSortCriterion(ActComparator.c_Code, ActComparator.c_Ascending);
		comparators.addSortCriterion(ActComparator.c_Id, ActComparator.c_Ascending);
		
		return comparators;
	}
	
	
	
	/* ======================================== */
	// CLASS: NotSortedException
	/* ======================================== */
	
//	public class NotSortedException extends RuntimeException
//	{
//		private static final long	serialVersionUID	= 1L;
//
//		public NotSortedException ()
//		{
//			super("The data isn't sorted! You need to call sort(RulesObjectHolder, double[]) first.");
//		}
//	}
}
