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

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

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

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: ActChanger.java,v $
 * <br>Revision 1.4  2013-03-22 10:27:03  ferring
 * <br>UnionActs added and billing bugs fixed (since last not released commit)
 * <br>
 * <br>Revision 1.3  2013-03-14 10:40:20  ferring
 * <br>Billing rules corrected
 * <br>
 * <br>Revision 1.2  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.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 ActChanger implements Serializable
{
	/* ======================================== */
	// 		CONSTANTS
	/* ======================================== */
	
	private static final long	serialVersionUID	= 1L;
	
	/** the logger Object for this class */
	private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ActChanger.class.getName());
	
	
	
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	private static HashMap<String, List<ActChangeCondition>>	actsToChange	= new HashMap<String, List<ActChangeCondition>>();
	
	
	
	/* ======================================== */
	// 		INITIALIZATION
	/* ======================================== */
	
	static 
	{
		refillMap();
	}
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	/**
	 * This is the initialization method. All the conditions are defined here.
	 */
	public static void refillMap ()
	{
		actsToChange.clear();
		
		// ADD ACTS TO CHANGE BY DATE
		
		// General acts:
		// normal consultations
		addToMap(20, "C1", RulesObjectsHolder.getRateIndex("G_1"), null, "C53", "C52", "C54", 
				// excludes: 
				RulesObjectsHolder.generateCodeArray("C6", "C7", 	// exclude the normal pediatric consultations
				RulesObjectsHolder.getRateIndex("G_1_4_2"),			// exclude the majorated pediatric consultations
				RulesObjectsHolder.getRateIndex("G_1_5")));			// exclude the pre-anesthetic examinations
		// pediatric consultations
		addToMap("C6", new DateChangeCondition(new String[] { "C6", "C7", "C55" }, null, "C57", "C56", "C58"));
		// general anesthetic acts
		addToMap(20, "C61", RulesObjectsHolder.getRateIndex("G_1_5"), null, "C63", "C62", "C64", RulesObjectsHolder.generateCodeArray("C71", "C74"));
		// visits out of hospital
		addToMap(18, "V1", RulesObjectsHolder.getRateIndex("G_2_1_1"), "V5", "V6", "V4", "V7");
		// paediatrist's visits out of hospital 
		addToMap(18, "V10", RulesObjectsHolder.getRateIndex("G_2_1_2"), "V14", "V15", "V13", "V16");
		// PREMIERE PARTIE : ACTES GENERAUX Chapitre 2 - Visites Section 1 - Visites en milieu extra-hospitalier
		addToMap(18, "V101", RulesObjectsHolder.getRateIndex("G_2_1_3"), "V501", "V601", "V401", "V701");
		// visits in hospital
		addToMap(18, "V20", RulesObjectsHolder.getRateIndex("G_2_2_1"), "V24", "V25", "V23", "V26");
		// paediatrist's visits in hospital
		addToMap(18, "V30", RulesObjectsHolder.getRateIndex("G_2_2_2"), "V34", "V35", "V33", "V36");

		
		// hospitalisation codes (the FXX1-codes were introduced at 2015-01-01)
		addHospitalizationSunAndHolidayCodes("F11", "F12", "F20", "F21", "F22", "F25", "F26", "F27", "F28", "F31", "F32", "F51", 
				"F52", "F61", "F62", "F65", "F66", "F68", "F69", "F71", "F72", "F73", "F75", "F76", "F77", "F80", "F85", "F90");
		
		// Technical acts:
		// gynaecology birth acts
		addToMap("6A14", new DateChangeCondition("6A14", "6A15", DateChangeCondition.SUNDAY_HOLIDAY_NIGHT));	// see nomenclature T 6.1.1 remark 3)
		addToMap("6A11", new DateChangeCondition("6A11", "6A21", DateChangeCondition.SUNDAY_HOLIDAY_NIGHT));	// see nomenclature T 6.1.1 remark 3)
		addToMap("6A12", new DateChangeCondition("6A12", "6A22", DateChangeCondition.SUNDAY_HOLIDAY_NIGHT));	// see nomenclature T 6.1.1 remark 3)
		addToMap("6A13", new DateChangeCondition("6A13", "6A23", DateChangeCondition.SUNDAY_HOLIDAY_NIGHT));	// see nomenclature T 6.1.1 remark 3)
		// anesthetic acts
		addToMap("7A95", new DateChangeCondition("7A95", "7A96", DateChangeCondition.SUNDAY_HOLIDAY_NIGHT));	// see nomenclature T 7.5
		addToMap("7A41", new DateChangeCondition("7A41", "7A42", DateChangeCondition.LATE_NIGHT));				//  see nomenclature T 7.3
		
		
		// ADD ACTS TO CHANGE BY AGE
		// paediatrist's default consultation on 
		addToMap("C7", new AgeChangeCondition("C7", "C6"));
	}
	
	
	/**
	 * Checks all acts of the invoice (connected with the RulesObjectHolder), if they need to be changed and changes them, if necessary.
	 * 
	 * @param roh The RulesObjectsHolder, keeping all necessary information (including the invoice with the acts to check).
	 */
	public static void checkAndChange (RulesObjectsHolder roh)
	{
		for (Object act : roh.getInvoice().getActs().toArray())
			// copy it or else there will be a ConcurrentModifierException
			checkAndChange(roh, (Act) act);
	}
	
	
	
	/* ======================================== */
	// 		HELP METHODS
	/* ======================================== */
	
	/**
	 * Maps a ActChangeCondition for all its (concerning) act codes.  
	 * 
	 * @param defaultCode The code to revert to, if it's a special code 
	 * 	(only applied, if the condition matches), but the condition doesn't matches.
	 * @param changeCondition The condition to check and that changes the act accordingly, if it matches.
	 */
	private static void addToMap (String defaultCode, ActChangeCondition changeCondition)
	{
		List<ActChangeCondition>	changeConditionList;
		
		changeCondition.setDefaultCode(defaultCode);
		for (String code : changeCondition.getCodesToChange())
		{
			if (changeCondition instanceof DateChangeCondition)
				SuffixHandler.excludeFromDayAndTimeSuffixes(code);
			
			changeConditionList = actsToChange.get(code);
			if (changeConditionList == null)
			{
				changeConditionList	= new LinkedList<ActChangeCondition>();
				actsToChange.put(code, changeConditionList);
			}
			changeConditionList.add(changeCondition);
		}
	}
	
	/**
	 * Creates a DateChangeCondition out of the given information, which maps it for all its (concerning) act codes.
	 * 
	 * @param defaultCode The code to revert to, if it's a special code 
	 * 	(only applied, if the condition matches), but the condition doesn't matches.
	 * @param index The index containing all the codes, that are changed to the special codes on the given dates
	 * @param saturday The code to change to on Saturday afternoon
	 * @param sunAndHoliday The code to change to on Sun- and holidays
	 * @param evening The code to change to after 8pm
	 * @param night The code to change to after 10pm and before 7am
	 * @param excludes The codes of the given index that are not changed by this condition
	 */
	private static void addToMap (int eveningStart, String defaultCode, RateIndex index, String saturday, 
			String sunAndHoliday, String evening, String night, String ... excludes)
	{
		Set<String>		codes			= new HashSet<String>(RulesObjectsHolder.getAllCodesOfRateIndex(index));
		
		
		codes.add(saturday);
		codes.add(sunAndHoliday);
		codes.add(evening);
		codes.add(night);
		
		if (excludes != null)
		{
			for (String code : excludes)
				codes.remove(code);
		}
		
		addToMap(defaultCode, new DateChangeCondition(eveningStart, codes, saturday, sunAndHoliday, evening, night));
	}
	
	
	/**
	 * 2015-01-01 new F-code ("forfait hospitalisation") were introduced, that are to be used on Sun- and Holidays.<br>
	 * The code are the normal code with an additional '1' at the end (e.g. F20 becomes F20<b>1</b>
	 * 
	 * @param codes F-codes, that have a FXX1 code to be used on Sun- and holidays
	 */
	private static void addHospitalizationSunAndHolidayCodes (String ... codes)
	{
		for (String code : codes)
			addToMap(code, new DateChangeCondition(code, code+"1", DateChangeCondition.SUN_AND_HOLIDAY));
	}
	
	
	/**
	 * Checks, if the code of the given at is on of the act codes, that has
	 * to be changed under certain circumstances.
	 * 
	 * @param roh The RulesObjectsHolder, keeping all necessary information
	 * @param act The act, to be checked and eventually be changed
	 */
	private static void checkAndChange (RulesObjectsHolder roh, Act act)
	{
		String						newCode;
		List<ActChangeCondition>	changeConditionList;
		String						code;
		boolean						codeHasChanged		= true;
		Collection<String>			checkedCodes		= new HashSet<String>();
		
		
		while (codeHasChanged)
		{
			// do this, while nothing changes anymore
			codeHasChanged		= false;
			code				= act.getCode();
			if (!checkedCodes.add(code))
			{
				logger.error("Improper definition of ActChanger.actsToChange. Loop found for codes: " + checkedCodes.toString());
				return;
			}
			
			changeConditionList	= actsToChange.get(code);
			
			if (changeConditionList != null)
			{
				for (ActChangeCondition changeCondition : changeConditionList)
				{
					newCode	= changeCondition.getCodeToChangeTo(roh, act);
					if (newCode != null && !newCode.equals(code)) 
					{
						act				= RulesObjectsHolder.updateAct(newCode, act, true);
						roh.getInvoice().updateAct(act);
						
						// if the code was changed, check the new code for changes
						codeHasChanged	= true;
						break;
					}
				}
			}
		}
	}
}
