/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.agenda.gui.widgets;

import static lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager.BACKWARDS;
import static lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager.FORWARD;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import javax.swing.JOptionPane;

import lu.tudor.santec.gecamed.agenda.ejb.entity.beans.Appointment;
import lu.tudor.santec.gecamed.agenda.ejb.session.beans.AppointmentManagerBean;
import lu.tudor.santec.gecamed.agenda.ejb.session.helper.TimeInterval;
import lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager;
import lu.tudor.santec.gecamed.agenda.gui.AgendaAdminSettingsPlugin;
import lu.tudor.santec.gecamed.agenda.gui.AgendaModule;
import lu.tudor.santec.gecamed.agenda.gui.AgendaSettingsPlugin;
import lu.tudor.santec.gecamed.core.gui.GECAMedOptionPane;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Logger;

import bizcal.util.DateUtil;

/**
 * 
 * Class to find free appointments. used by the FreeAppointmentFinderDialog.
 * 
 * @author martin.heinemann@tudor.lu
 * 13.07.2007
 * 15:05:37
 *
 *
 * @version
 * <br>$Log: FreeAppointmentFinder.java,v $
 * <br>Revision 1.24  2013-12-27 18:08:17  donak
 * <br>Cleanup of imports
 * <br>
 * <br>Revision 1.23  2013-07-15 06:18:35  ferring
 * <br>logging changed
 * <br>
 * <br>Revision 1.22  2013-07-10 16:41:08  troth
 * <br>Fix ticket #1108.
 * <br>
 * <br>Revision 1.21  2012-08-09 14:20:42  troth
 * <br>Ticket #1021 - Unpredictable NAF results and long search delai when no time slot left.
 * <br>
 * <br>Revision 1.20  2012-08-09 14:18:49  troth
 * <br>Ticket #1021 - Unpredictable NAF results and long search delai when no time slot left.
 * <br>
 * <br>Revision 1.19  2012-08-07 15:51:58  troth
 * <br>Fix Ticket #1016.
 * <br>
 * <br>Revision 1.18  2012-07-27 13:26:27  troth
 * <br>Disable the automatic movement of the appointment if it overlays other appointments after dragging or resizing.
 * <br>
 * <br>Revision 1.17  2012-07-25 16:20:25  troth
 * <br>fix Ticket #873
 * <br>
 * <br>Revision 1.16  2012-07-23 15:41:24  troth
 * <br>Ticket #873 Point 6 and 7 fixed.
 * <br>
 * <br>Revision 1.15  2012-07-17 14:36:01  troth
 * <br>Ticket #873 - Constraints are now static and the dialog is redesigned, add a today and 6 week button.
 * <br>
 * <br>Revision 1.14  2012-07-10 16:16:11  troth
 * <br>Ticket #873 Point 1,3 and 13.
 * <br>
 * <br>Revision 1.13  2012-06-28 15:57:19  troth
 * <br>fix bug: Ticket #878  Existing recurring appointments not taken into account by NAF (first test version).
 * <br>
 * <br>Revision 1.12  2008-09-25 09:42:27  heinemann
 * <br>fixed copyrights
 * <br>
 * <br>Revision 1.11  2008-01-18 16:09:05  heinemann
 * <br>code cleanup and java doc
 * <br>
 * <br>Revision 1.10  2007-11-20 08:58:54  hermen
 * <br>moved Managerfactory to core.utils and refactured code to use ManagerFactory instead of context.lookup
 * <br>
 *   
 */
public class FreeAppointmentFinder {
	
	
	// predefined ranges to search for free appointments
	public static final int WEEK_1 = 7;
	public static final int WEEK_2 = 14;
	public static final int WEEK_3 = 21;
	public static final int WEEK_4 = 28;
	public static final int WEEK_5 = 35;
	
	// times of day
	public static final String MORNING   = "morning";
	public static final String FORENOON  = "forenoon"; 
	public static final String LUNCH 	 = "lunch";
	public static final String AFTERNOON = "afternoon";
	public static final String EVENING   = "evening"; 
	
	
	/**
	 * 
	 */
	protected static String[] timeConstraint = new String[] {MORNING,
															 FORENOON, 
															 LUNCH,
															 AFTERNOON,
															 EVENING
														};
	
	/**
	 * Offset for appointments that starts inside a valid time constraint
	 * but lasts longer than the end of a constraint.
	 */
//	private static final int CONSTRAINT_OFFSET = 30;
	
	/**
	 * 
	 */
	public static final String PROPOSAL_FADED = "proposalFaded";
	
	
	/**
	 * Maxmim iterations for the free appointment search.
	 * Is used as fallback if the loop seems to be infinite.
	 */
//	private static final int MAX_ITERATION = 50;
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(FreeAppointmentFinder.class.getName());
	
	
	/**
	 * 
	 */
	public HashMap<String, TimeInterval> timeTable = new HashMap<String, TimeInterval>();
	
	private Appointment lastProposal = null;
	

	private List<String> usedTimeConstraints;

	private Integer dayRangeStart;
	private Integer dayRangeStop;
	private Integer praxisOpen;
	private Integer praxisClose;
	private FreeAppointmentFindDialog freeAppointmentFindDialog;
	
	/**
	 * 
	 */
	public FreeAppointmentFinder (FreeAppointmentFindDialog freeAppointmentFindDialog) {
		/* ================================================== */
		// compute timetable
		// get information from the settings
//		Date dayStart  = (Date) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.START_HOUR);
//		Date dayStop   = (Date) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.STOP_HOUR);
		
//		int lunch      = 100 *(Integer) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.DAY_BREAK);
		/* ------------------------------------------------------- */
//		TimeInterval dayStartStop = new TimeInterval(dayStart, dayStop);
//		TimeInterval lunchInterval = new TimeInterval(lunchDate, lunchDate);
		/* ------------------------------------------------------- */
		// compute forenoon and afternoon as middle value of daystart and lunch respectivly dayend and lunch
//		int morning   = dayStartStop.getStarttime();
//		int lunch     = lunchInterval.getStarttime();
//		int evening   = dayStartStop.getEndtime();
		
		
		/* ------------------------------------------------------- */
		// morning is morning plus 2 h
//		timeTable.put(MORNING,   new TimeInterval(morning      , morning + 200));
//		// forenoon is morning + 1h till lunch - 1h
//		timeTable.put(FORENOON,  new TimeInterval(morning + 100, lunch   - 100));
//		// lunch is round about 1 h
//		timeTable.put(LUNCH,     new TimeInterval(lunch   - 100, lunch   + 100));
//		// afternoon is lunch + 1h till evening - 1 h
//		timeTable.put(AFTERNOON, new TimeInterval(lunch   + 100, evening - 100));
//		// evening is evening - 200 till evening
//		timeTable.put(EVENING,   new TimeInterval(evening - 200, evening));
		
		
		// save reference to dialog
		this.freeAppointmentFindDialog = freeAppointmentFindDialog;
		// we now have static times for the constrains
		// Morning 00:00 - 10:00
		timeTable.put(MORNING,   new TimeInterval(0 ,1000));
		// Forenoon 10:00 - 12:00
		timeTable.put(FORENOON,  new TimeInterval(1000, 1200));
		// Lunch 12:00 - 13:00
		timeTable.put(LUNCH,     new TimeInterval(1200, 1300));
		// Afternoon 13:00 - 17:00
		timeTable.put(AFTERNOON, new TimeInterval(1300, 1700));
		// Evening 17:00 - 23:59
		timeTable.put(EVENING,   new TimeInterval(1700, 2359));
		initSettings();
		/* ================================================== */
	}
	
	/**
	 * Checks if the timespace needed by the appointment is free for use.</br>
	 * If not, it will return the next possible start time and a duration that is
	 * at least the size of the requested. Information is stored in a new 
	 * Appointment object.</br>
	 * If the space is free for use, it will return the same Appointment.</br>
	 * If nothing can be found, null is returned. Should propably not happen =)
	 * 
	 * @param appToCheck the appointment to check
	 * @param direction past or future
	 * @return
	 */
	public Appointment getFreeAppointment(Appointment appToCheck, int direction) {
		/* ================================================== */
		if (appToCheck == null)
			return null;
		/* ------------------------------------------------------- */
		Locale locale = (Locale)AgendaModule.getInstance().getAgendaAdminSettingsPlugin().getValue(AgendaAdminSettingsPlugin.CALENDAR_LOCALE);	
		if(locale.equals(null) || locale.equals("")) locale = Locale.FRANCE;
		
		try {
			/* ------------------------------------------------------- */
			// get the session bean
			AppointmentManager aManager = (AppointmentManager) 
						ManagerFactory.getRemote(AppointmentManagerBean.class);
			/* ------------------------------------------------------- */
			
			Appointment result = aManager.checkFreeAppointment(appToCheck, direction, locale);
			
			if (result != null)
				lastProposal = result;
			return lastProposal;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		/* ================================================== */
	}
	
	/**
	 * Returns next appointment and moves the pointer to the next
	 * @return
	 */ 
	public Appointment next() {
		/* ================================================== */
		if (lastProposal == null)
			return null;
		/* ------------------------------------------------------- */
		Appointment app = shiftNextAppointment(lastProposal);
		
		return lastProposal;
		/* ================================================== */
	}
	
	/**
	 * Method to switch to the next available day
	 */
	public Appointment nextDay() {
		/* ================================================== */
		
		Appointment appMove = lastProposal.copy();
		
		Locale locale = (Locale)AgendaModule.getInstance().getAgendaAdminSettingsPlugin().getValue(AgendaAdminSettingsPlugin.CALENDAR_LOCALE);	
		if(locale.equals(null) || locale.equals("")) locale = Locale.FRANCE;
		
		Date dayStart  = (Date) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.STOP_HOUR);
		
		Calendar dayStartCal = Calendar.getInstance(locale);
		
		dayStartCal.setTime(dayStart);
						
		Calendar newStartCal = Calendar.getInstance(locale);
        
		long duration = DateUtil.getDiffDay(appMove.getStartDate(), appMove.getEndDate());
		
		Date newStart = appMove.getStartDate();
        
		newStartCal.setTime(newStart);
        
		newStartCal.set(Calendar.HOUR_OF_DAY, dayStartCal.get(Calendar.HOUR_OF_DAY));
		newStartCal.set(Calendar.MINUTE,      dayStartCal.get(Calendar.MINUTE));
		newStartCal.set(Calendar.SECOND, 	  dayStartCal.get(Calendar.SECOND));
		newStartCal.set(Calendar.MILLISECOND, dayStartCal.get(Calendar.MILLISECOND));
		newStart = newStartCal.getTime();
		Date newEnd  = new Date(newStart.getTime() + duration);
		appMove.setStartDate(newStart);
		appMove.setEndDate(newEnd);
	
		lastProposal = appMove.copy();
		
		return next();
		/* ================================================== */
	}
	
	/**
	 * Switch to the day in distance
	 * @param days
	 * @return
	 */
	public Appointment nextDays(int days) {
		/* ================================================== */
		if (lastProposal == null)
			return null;
		
		Appointment app = lastProposal.copy();
		// move +x day
		app.setStartDate(DateUtil.move(app.getStartDate(), days));
		app.setEndDate(	 DateUtil.move(app.getEndDate(),   days));
		lastProposal = app;
		return nextDay();
		/* ================================================== */
	}
	
	/**
	 * Returns previous appointment and moves the pointer to the previous
	 * @return
	 */
	public Appointment previous() {
		/* ================================================== */
		if (lastProposal == null)
			return null;
		/* ------------------------------------------------------- */
		Appointment app = shiftPreviousAppointment(lastProposal);
		// do a quuery for the next space
		
		return lastProposal;
		/* ================================================== */
	}
	
	
	/**
	 * Method to switch to the previous available day
	 */
	public Appointment previousDay() {
		/* ================================================== */
//		return previousDays(1);
		/* ================================================== */
		
		Appointment appMove = lastProposal.copy();
				
		Locale locale = (Locale)AgendaModule.getInstance().getAgendaAdminSettingsPlugin().getValue(AgendaAdminSettingsPlugin.CALENDAR_LOCALE);	
		if(locale.equals(null) || locale.equals("")) locale = Locale.FRANCE;
		
		Date dayStart  = (Date) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.START_HOUR);
		
		Calendar dayStartCal = Calendar.getInstance(locale);
		
		dayStartCal.setTime(dayStart);
						
		Calendar newStartCal = Calendar.getInstance(locale);
        
		long duration = DateUtil.getDiffDay(appMove.getStartDate(), appMove.getEndDate());
		
		Date newStart = appMove.getStartDate();
        
		newStartCal.setTime(newStart);
        
		newStartCal.set(Calendar.HOUR_OF_DAY, dayStartCal.get(Calendar.HOUR_OF_DAY));
		newStartCal.set(Calendar.MINUTE,      dayStartCal.get(Calendar.MINUTE));
		newStartCal.set(Calendar.SECOND, 	  dayStartCal.get(Calendar.SECOND));
		newStartCal.set(Calendar.MILLISECOND, dayStartCal.get(Calendar.MILLISECOND));
//		newStartCal.add(Calendar.DAY_OF_MONTH, 1);
		newStart = newStartCal.getTime();
		Date newEnd  = new Date(newStart.getTime() + duration);
		appMove.setStartDate(newStart);
		appMove.setEndDate(newEnd);
	
		lastProposal = appMove.copy();
		
		return previous();
		/* ================================================== */
	}
	
	/**
	 * Switch to previous days in distance
	 * @param days the switch days, positive values!!!
	 * @return
	 */
	public Appointment previousDays(int days) {
		/* ================================================== */
		if (lastProposal == null)
			return null;
		
		Appointment app = lastProposal.copy();
		// move -x day
		app.setStartDate(DateUtil.move(app.getStartDate(), -days));
		app.setEndDate(	 DateUtil.move(app.getEndDate(),   -days));
		lastProposal = app;
		return previousDay();
		/* ================================================== */
	}
	
	/**
	 * Creates a new appointment with startdate the enddate of formerDate
	 * 
	 * @param formerApp
	 * @param duration
	 * @return shifted appointment (Attention, new object!!!)
	 */
	private Appointment shiftNextAppointment(Appointment formerApp)
	{
		/* ================================================== */
		boolean isOk 		= false;
		Appointment shiftAp = formerApp;
		
		int maxSearchDays = (Integer) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.MAX_SEARCH_DAYS);
		Appointment breakApp = lastProposal.copy();
		breakApp.setStartDate(DateUtil.move(breakApp.getStartDate(), maxSearchDays));
		
		while (!isOk)
		{
			/* ------------------------------------------------------- */
			// move the appointment to the end of the used appointment
			long duration = DateUtil.getDiffDay(shiftAp.getStartDate(), shiftAp.getEndDate());
			Date newStart = shiftAp.getEndDate();
			Date newEnd   = new Date(shiftAp.getEndDate().getTime() + duration);
			
			// set the starttime of appointment to the start time of the office if the appointment moving to the next day
			TimeInterval ti = new TimeInterval(newStart, newEnd);
			if (ti.getStarttime() > praxisClose)
			{
				Date dayStart  = (Date) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.START_HOUR);
				
				Calendar dayStartCal = Calendar.getInstance();
				
				dayStartCal.setTime(dayStart);
				
				Locale locale = (Locale)AgendaModule.getInstance().getAgendaAdminSettingsPlugin().getValue(AgendaAdminSettingsPlugin.CALENDAR_LOCALE);	
				if(locale.equals(null) || locale.equals("")) locale = Locale.FRANCE;
				
				Calendar newStartCal = Calendar.getInstance(locale);
		        
				newStartCal.setTime(newStart);
		        
				newStartCal.set(Calendar.HOUR_OF_DAY, dayStartCal.get(Calendar.HOUR_OF_DAY));
				newStartCal.set(Calendar.MINUTE,      dayStartCal.get(Calendar.MINUTE));
				newStartCal.set(Calendar.SECOND, 	  dayStartCal.get(Calendar.SECOND));
				newStartCal.set(Calendar.MILLISECOND, dayStartCal.get(Calendar.MILLISECOND));
				newStartCal.add(Calendar.DAY_OF_MONTH, 1);
				newStart = newStartCal.getTime();
				newEnd   = new Date(newStart.getTime() + duration);
			}
			
			shiftAp = shiftAp.copy();
			shiftAp.setStartDate(newStart);
			shiftAp.setEndDate(  newEnd);
			
			// do a query for the next space
			// check if there are any overlapping with other appointments
			Appointment result = this.getFreeAppointment(shiftAp, FORWARD);
			if (result != null)
				shiftAp = result;
			/* ------------------------------------------------------- */
			// check if there are overlappings with timeconstraints
			isOk = checkConstraint(shiftAp);

			if (breakApp.getStartDate().before(shiftAp.getStartDate()))
			{
				freeAppointmentFindDialog.setAppointmentToView(shiftAp);
				String[] vars = {""+ maxSearchDays};
				int reply = GECAMedOptionPane.showOptionDialog(
						MainFrame.getInstance(),
						Translatrix.getTranslationString("Agenda.findfree.maxSearchDaysDialogTitle"),
						Translatrix.getTranslationString("Agenda.findfree.maxSearchDaysDialogText", vars),
						JOptionPane.YES_NO_OPTION);
						// GECAMedOptionPane.ICON_QUESTION);
				/* ------------------------------------------------------ */
				if (reply == JOptionPane.NO_OPTION)
					break;
				
				/* ------------------------------------------------------ */
				if (reply == JOptionPane.YES_OPTION)
				{
					breakApp = lastProposal.copy();
					breakApp.setStartDate(DateUtil.move(breakApp.getStartDate(), maxSearchDays));
				}
			}
				
		/* ------------------------------------------------------- */
		}
		/* ------------------------------------------------------- */
		return shiftAp;
		/* ================================================== */
	}
	
	/**
	 * Creates a new appointment shifted one step backwards
	 * 
	 * @param formerApp
	 * @param duration
	 * @return shifted appointment (Attention, new object!!!)
	 */
	private Appointment shiftPreviousAppointment(Appointment formerApp)
	{
		/* ================================================== */
		boolean isOk 		= false;
		Appointment shiftAp = formerApp;
		
		int maxSearchDays = (Integer) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.MAX_SEARCH_DAYS);
		Appointment breakApp = lastProposal.copy();
		breakApp.setStartDate(DateUtil.move(breakApp.getStartDate(), -maxSearchDays));
		
		
		
		while (!isOk)
		{
			/* ------------------------------------------------------- */
			// move the appointment to the end of the used appointment
			long duration = DateUtil.getDiffDay(shiftAp.getStartDate(), shiftAp.getEndDate());
			
			//Date newStart = DateUtil.moveByMinute(new Date(shiftAp.getStartDate().getTime() - duration), -1);
			Date newStart = new Date(shiftAp.getStartDate().getTime() - duration);
			
			//Date newEnd   = DateUtil.moveByMinute(shiftAp.getStartDate(), -1);
			Date newEnd   = shiftAp.getStartDate();
			
			
			// set the starttime of appointment to the end time of the office if the appointment moving to the next day
			TimeInterval ti = new TimeInterval(newStart, newEnd);
			if (ti.getStarttime() < praxisOpen)
			{
				Date dayStart  = (Date) AgendaModule.getInstance().getAgendaSettingsPlugin().getValue(AgendaSettingsPlugin.STOP_HOUR);
				
				Calendar dayStartCal = Calendar.getInstance();
				
				dayStartCal.setTime(dayStart);
				
				Locale locale = (Locale)AgendaModule.getInstance().getAgendaAdminSettingsPlugin().getValue(AgendaAdminSettingsPlugin.CALENDAR_LOCALE);	
				if(locale.equals(null) || locale.equals("")) locale = Locale.FRANCE;
				
				Calendar newStartCal = Calendar.getInstance(locale);
		        
				newStartCal.setTime(newStart);
		        
				newStartCal.set(Calendar.HOUR_OF_DAY, dayStartCal.get(Calendar.HOUR_OF_DAY));
				newStartCal.set(Calendar.MINUTE,      dayStartCal.get(Calendar.MINUTE));
				newStartCal.set(Calendar.SECOND, 	  dayStartCal.get(Calendar.SECOND));
				newStartCal.set(Calendar.MILLISECOND, dayStartCal.get(Calendar.MILLISECOND));
				newStartCal.add(Calendar.DAY_OF_MONTH, -1);
				newStart = newStartCal.getTime();
				newEnd   = new Date(newStart.getTime() + duration);
			}
			
			shiftAp = shiftAp.copy();
			shiftAp.setStartDate(newStart);
			shiftAp.setEndDate(  newEnd);
			Appointment result = this.getFreeAppointment(shiftAp, BACKWARDS);
			if (result != null)
				shiftAp = result;
			/* ------------------------------------------------------- */
			// check if there are overlappings with timeconstraints
			isOk = checkConstraint(shiftAp);
			if (breakApp.getStartDate().after(shiftAp.getStartDate()))
			{
				freeAppointmentFindDialog.setAppointmentToView(shiftAp);
				String[] vars = {""+ maxSearchDays};
				int reply = GECAMedOptionPane.showOptionDialog(
						MainFrame.getInstance(),
						Translatrix.getTranslationString("Agenda.findfree.maxSearchDaysDialogTitle"),
						Translatrix.getTranslationString("Agenda.findfree.maxSearchDaysDialogText", vars),
						JOptionPane.YES_NO_OPTION);
						// GECAMedOptionPane.ICON_QUESTION);
				/* ------------------------------------------------------ */
				if (reply == JOptionPane.NO_OPTION)
					break;
				
				/* ------------------------------------------------------ */
				if (reply == JOptionPane.YES_OPTION)
				{
					breakApp = lastProposal.copy();
					breakApp.setStartDate(DateUtil.move(breakApp.getStartDate(), -maxSearchDays));
				}
			}
			/* ------------------------------------------------------- */
		}
		return shiftAp;
		/* ================================================== */
	}
	
	
	/**
	 * Rounds the appointment to a "human-readable" time.
	 * Dates like 9:38 will be rounded to 9:40
	 * @param app
	 */
	private Appointment roundAppointment(Appointment app) {
		/* ================================================== */
		int minutes = DateUtil.getMinuteOfHour(app.getStartDate());
		int hours   = DateUtil.getHourOfDay(app.getStartDate());
		
		long duration = DateUtil.getDiffDay(app.getStartDate(), app.getEndDate());
		 
		/* ------------------------------------------------------- */
		int base = minutes / 15;
		int mant = minutes % 15;
		if (mant > 7)
			base++;
		/* ------------------------------------------------------- */
		minutes = base * 15;
		if (minutes >= 60) {
			minutes = 0;
			hours++;
			if (hours > 24)
				hours = 24 - hours;
		}
			
		app.setStartDate(DateUtil.round2Hour(app.getStartDate(), hours));
		app.setStartDate(DateUtil.round2Minute(app.getStartDate(), minutes));
		/* ------------------------------------------------------- */
		// adjust the duration
		Date newEnd = new Date(app.getStartDate().getTime()+duration);
		app.setEndDate(newEnd);
		return app;
		/* ================================================== */
	}
	
	
	private void initSettings () {
		/* ================================================== */
		AgendaSettingsPlugin asp = AgendaModule.getInstance().getAgendaSettingsPlugin();
		/* ------------------------------------------------------- */
		// first check if the day is correct
		//
		this.dayRangeStart = (Integer) asp.getValue(AgendaSettingsPlugin.WEEK_DAY_START);
		this.dayRangeStop  = (Integer) asp.getValue(AgendaSettingsPlugin.WEEK_DAY_STOP);
		// compute the right day range
		if (dayRangeStart > dayRangeStop) {
			dayRangeStop += 7;
		} 
		/* ------------------------------------------------------- */
		/* ------------------------------------------------------- */
		// check if the time is valid
		this.praxisOpen  = null;
		this.praxisClose = null;
		try{
			/* ------------------------------------------------------- */
			// praxis open
			int hour = DateUtil.getHourOfDay(   (Date) asp.getValue(AgendaSettingsPlugin.START_HOUR));
			int min  = DateUtil.getMinuteOfHour((Date) asp.getValue(AgendaSettingsPlugin.START_HOUR));
			praxisOpen = (hour*100)+min;
			
			// praxis close
			hour = DateUtil.getHourOfDay((   Date) asp.getValue(AgendaSettingsPlugin.STOP_HOUR));
			min  = DateUtil.getMinuteOfHour((Date) asp.getValue(AgendaSettingsPlugin.STOP_HOUR));
			praxisClose = (hour*100)+min;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			/* ------------------------------------------------------- */
			praxisOpen  = 759;
			praxisClose = 1800;
			e.printStackTrace();
			/* ------------------------------------------------------- */
		}
		/* ================================================== */
	}
	

	public void reset() {
		/* ================================================== */
		this.lastProposal = null;
		/* ================================================== */
	}
	/**
	 * @return
	 */
	public Appointment getLastProposal() {
		/* ================================================== */
		return this.lastProposal;
		/* ================================================== */
	}
	
	public void setLastProposal(Appointment prop) {
		/* ================================================== */
		this.lastProposal = prop;
		/* ================================================== */
	}
	
	
	/**
	 * Set optional timeconstraints. The settings will override the rules defined in the session bean
	 * 
	 * @param timeConstraints
	 */
	public void setTimeConstraint(List<String> timeCons) {
		/* ================================================== */
		if (timeCons != null && timeCons.size() > 0) {
			/* ------------------------------------------------------- */
			this.usedTimeConstraints = timeCons;
		}
		else
			this.usedTimeConstraints = null;
			// first get a new list, without the constraints of the patient
//			this.tempPatientId = currentPatientId;
//			currentPatientId = null;
//			freeAppointments.clear();
//			iterator = null;
//			fireSearch();
//			/* ------------------------------------------------------- */
//		} else {
//			// reset
//			try {
////				this.currentPatientId = tempPatientId;
////				freeAppointments.clear();
////				iterator = null;
////				fireSearch();
//			} catch (Exception e) {
//			}
//		}
		/* ================================================== */
	}
//	
//	
//	/**
//	 * Applies the timeconstraints on the proposal list
//	 * @param direction true -> next, false -> previous
//	 */
//	private void applyFilter(boolean direction) {
//		/* ================================================== */
//		// no filters, no work
//		if (this.usedTimeConstraints == null || this.usedTimeConstraints.size() < 1)
//			return;
//		/* ------------------------------------------------------- */
//		if (direction) { // next
//			/* ------------------------------------------------------- */
//			while (iterator.hasNext()) {
//				/* ------------------------------------------------------- */
//				Appointment next = iterator.next();
//				if (checkConstraint(next)) {
//					// one step back
//					iterator.previous();
//					return;
//				}
//				/* ------------------------------------------------------- */
//			}
//			/* ------------------------------------------------------- */
//		} else { // previous
//			/* ------------------------------------------------------- */
//			while (iterator.hasPrevious()) {
//				/* ------------------------------------------------------- */
//				Appointment previous = iterator.previous();
//				if (checkConstraint(previous)) {
//					// one step back
//					iterator.next();
//					return;
//				}
//				/* ------------------------------------------------------- */
//			}
//			/* ------------------------------------------------------- */
//		}
//		/* ================================================== */
//	}
//	
//	
//	
	/**
	 * Checks if the appointment is covered by a timeconstraint
	 * 
	 * @param app
	 * @return true if appointment is valid
	 */
	private boolean checkConstraint(Appointment app) {
		/* ================================================== */
		// check if the appointment is a valid time (opening times, weekend etc)
		if (!checkOpeningDays(app))
			return false;
		
		if (!checkOpeningTime(app))
			return false;
		
		/* ------------------------------------------------------- */
		// if there are no constraints, any appointment is valid.
		if (usedTimeConstraints == null)
			return true;
		
		// create a TimeInterval for the Appointment, for ease of use only
		TimeInterval appInt = new TimeInterval(app.getStartDate(), app.getEndDate());
		/* ------------------------------------------------------- */
		for (String key : usedTimeConstraints) {
			/* ------------------------------------------------------- */
			TimeInterval inv = timeTable.get(key);
			
			if (appInt.getStarttime() >= inv.getStarttime()
					&& appInt.getEndtime() <= inv.getEndtime())
				return true;
			/* ------------------------------------------------------- */
		}
		return false;
		/* ================================================== */
	}
	
	
	/**
	 * Checks if the appointment is in conflict with the 
	 * opening times of the practise
	 * 
	 * @param app
	 * @return
	 */
	private boolean checkOpeningDays(Appointment app) {
		/* ================================================== */
		// each day that now is in range of start and stop is valid.
		int appDay = DateUtil.getDayOfWeek(app.getStartDate());
		if (appDay < dayRangeStart || appDay > dayRangeStop)
			return false;
		/* ------------------------------------------------------- */
		// passes check, valid!
		return true;
		/* ================================================== */
	}
	
	
	/**
	 * Checks if the appointment is in conflict with the 
	 * opening times of the practise
	 * 
	 * @param app
	 * @return
	 */
	private boolean checkOpeningTime(Appointment app) {
		/* ================================================== */
		// check the times
		// we check only the start date!
		TimeInterval ti = new TimeInterval(app.getStartDate(), app.getEndDate());
		if (ti.getStarttime() < praxisOpen)
			return false;
		if (ti.getStarttime() >= praxisClose)
			return false;
		/* ------------------------------------------------------- */
		// passes check, valid!
		return true;
		/* ================================================== */
	}
}
