/*******************************************************************************
 * 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.ejb.session.beans;

import java.awt.Color;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import lu.tudor.santec.gecamed.agenda.ejb.entity.beans.AgendaCalendar;
import lu.tudor.santec.gecamed.agenda.ejb.entity.beans.Appointment;
import lu.tudor.santec.gecamed.agenda.ejb.entity.beans.AppointmentType;
import lu.tudor.santec.gecamed.agenda.ejb.entity.beans.UserCalendarSelection;
import lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager;
import lu.tudor.santec.gecamed.agenda.utils.AgendaAdminSettingsConstants;
import lu.tudor.santec.gecamed.agenda.utils.RRule;
import lu.tudor.santec.gecamed.core.utils.querybuilder.Condition;
import lu.tudor.santec.gecamed.core.utils.querybuilder.Group;
import lu.tudor.santec.gecamed.core.utils.querybuilder.HibernateCondition;
import lu.tudor.santec.gecamed.core.utils.querybuilder.HibernateOperator;
import lu.tudor.santec.gecamed.core.utils.querybuilder.HibernateQueryFactory;
import lu.tudor.santec.gecamed.core.utils.querybuilder.WhereClause;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.PatientAdminInterface;
import lu.tudor.santec.gecamed.usermanagement.ejb.session.interfaces.LoginInterface;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.Before;

import bizcal.util.DateUtil;

/**
 * @author martin.heinemann@tudor.lu 15.03.2007 14:56:41
 * 
 * 
 * @version <br>
 *          $Log: AppointmentManagerBean.java,v $
 *          Revision 1.56  2013-11-08 08:39:33  ferring
 *          exceptions logged instead of printStackTrace
 *
 *          Revision 1.55  2013-07-10 16:41:04  troth
 *          Fix ticket #1108.
 *
 *          Revision 1.54  2013-02-22 08:46:50  kutscheid
 *          remove the tupel classes (please redeploy)
 *
 *          Revision 1.53  2013-01-15 14:55:40  ferring
 *          query corrected
 *
 *          Revision 1.52  2013-01-15 14:31:03  ferring
 *          warnings removed and so on
 *
 *          Revision 1.51  2013-01-15 13:11:56  troth
 *          *** empty log message ***
 *
 *          Revision 1.50  2012-07-24 16:09:41  troth
 *          Code clear up.
 *
 *          Revision 1.49  2012-07-12 15:48:48  troth
 *          NAF clear system.out.println.
 *
 *          Revision 1.48  2012-07-10 16:16:11  troth
 *          Ticket #873 Point 1,3 and 13.
 *
 *          Revision 1.47  2012-07-05 16:30:10  troth
 *          fix bug: Ticket #878  Existing recurring appointments not taken into account by NAF (test version final ?).
 *
 *          Revision 1.46  2012-07-04 16:22:48  troth
 *          fix bug: Ticket #878  Existing recurring appointments not taken into account by NAF (test version 3).
 *
 *          Revision 1.45  2012-07-03 15:57:43  troth
 *          fix bug: Ticket #878  Existing recurring appointments not taken into account by NAF (second test version).
 *
 *          Revision 1.44  2012-06-28 15:57:19  troth
 *          fix bug: Ticket #878  Existing recurring appointments not taken into account by NAF (first test version).
 *
 *          Revision 1.43  2012-06-22 15:00:13  troth
 *          The Appointment create by the appointment finder now not longer  make 1 minute free space to an other appointment when search a new position in the agenda.
 *
 *          Revision 1.42  2012-04-24 14:02:13  troth
 *          Remove same warnings.
 *
 *          Revision 1.41  2011-07-14 12:40:15  troth
 *          Fix Ticket #862 | Show future appointment of one patient.
 *
 *          Revision 1.40  2011-06-28 13:48:27  troth
 *          testing agenda
 *
 *          Revision 1.37  2011-02-16 10:47:40  troth
 *          check in new bizcal jar and add the new bizcal view layout to the agenda
 *
 *          Revision 1.36  2010-09-23 13:10:05  hermen
 *          add userId to physicianCalendar
 *
 *          Revision 1.35  2010-07-08 08:24:20  hermen
 *          *** empty log message ***
 *
 *          Revision 1.34  2010-03-03 15:45:53  troth
 *          fixed nullpointer in the appointment searching
 *
 *          Revision 1.33  2009-04-03 13:22:05  heinemann
 *          fix for: Ticket #266 (new enhancement)
 *
 *          If Color of a physician is changed, it will not change the color of his Calendar
 *
 *          Revision 1.32  2008-09-30 14:53:48  heinemann
 *          *** empty log message ***
 *
 *          Revision 1.31  2008-09-25 09:42:27  heinemann
 *          fixed copyrights
 *
 *          Revision 1.30  2008-09-05 16:53:20  heinemann
 *          a new calendar now gets automatically new default appointment types.
 *
 *          Revision 1.29  2008-04-14 13:11:36  heinemann
 *          *** empty log message ***
 *
 *          Revision 1.28  2008-04-08 09:52:33  heinemann
 *          moved creation of new calendar from OfficeBean to AgendaModule
 *
 *          Revision 1.27  2008-03-28 08:51:34  heinemann
 *          *** empty log message ***
 *
 *          Revision 1.26  2008-02-27 09:14:28  heinemann
 *          *** empty log message ***
 *
 *          Revision 1.25  2008-02-27 08:21:24  heinemann
 *          *** empty log message ***
 *
 *          Revision 1.24  2008-02-11 16:45:52  heinemann
 *          removed waitingroom methods
 *
 *          Revision 1.23  2008-01-18 16:09:05  heinemann
 *          code cleanup and java doc
 *
 *          Revision 1.22  2007-10-22 09:58:14  heinemann
 *          *** empty log message ***
 *
 *          Revision 1.16  2007/08/23 08:15:24  hermen
 *          added calendarId to appointment_type
 *
 *          Revision 1.3 2007/06/04 12:03:34 hermen <br>
 *          load and store selected calendars <br>
 *          <br>
 *          Revision 1.2 2007/05/25 13:50:25 heinemann <br>
 *          pres-weekend checkin <br>
 *          <br>
 *          Revision 1.1 2007/03/27 05:55:22 heinemann <br>
 *          initial checkin <br>
 * 
 */
@Stateless
public class AppointmentManagerBean implements AppointmentManager {

	/**
	 * static logger for this class
	 */
//	private static Logger logger = Logger
//		.getLogger(AppointmentManagerBean.class.getName());
	
	private static DateFormat dayFormatter = new SimpleDateFormat("yyyy-MM-dd");
	private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	@PersistenceContext(unitName = "gecam")
	EntityManager em;

	@EJB
	LoginInterface login;

//	@EJB
//	AppointmentRuleInterface ruleBean;

	@EJB
	PatientAdminInterface patientBean;

	@Resource
	SessionContext sessionContext;

	@Resource(mappedName = TOPIC_NAME)
	private Topic calendarUpdateTopic;

	@Resource(mappedName = "ConnectionFactory")
	private TopicConnectionFactory factory;
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(AppointmentManagerBean.class.getName());

	
	// ~ Instance fields
	// ========================================================

	private TopicConnection conn = null;

	private TopicSession session = null;

//	private static int instanceNr;

	/*
	 * #################################################################################
	 * 
	 * Methods
	 * 
	 * ---------------------------------------------------------------------------------
	 */

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentInterface#getAppointmentTypes()
	 */
	@SuppressWarnings("unchecked")
	public Collection<AppointmentType> getAppointmentTypes(Integer calendarId, boolean deprecated) {
		/* ====================================================== */
		try {
			if (calendarId == null) {
				/* ------------------------------------------------------- */
				List<AppointmentType> result = em.createNamedQuery(
						AppointmentType.FIND_ALL_APPOINTMENT_TYPES)
						.setParameter("deprecated", deprecated)
						.getResultList();
				return result;
			} else {
			/* ------------------------------------------------------- */
				List<AppointmentType> result = em.createNamedQuery(
						AppointmentType.FIND_ALL_APPOINTMENT_TYPES_BY_CALENDAR_ID)
						.setParameter("calendarId", calendarId)
						.setParameter("deprecated", deprecated)
						.getResultList();
				return result;
			}
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			/* ------------------------------------------------------- */
			return null;
			/* ------------------------------------------------------- */
		}
		/* ====================================================== */
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentInterface#getAppointmentTypes()
	 */
	@SuppressWarnings("unchecked")
	public Collection<AppointmentType> getAppointmentTypesByID(Integer type, Integer calendarId, boolean deprecated) {
		/* ====================================================== */
		try {
			if (calendarId == null) {
				/* ------------------------------------------------------- */
				List<AppointmentType> result = em.createNamedQuery(
						AppointmentType.FIND_ALL_GLOBAL_APPOINTMENT_TYPES_BY_TYPE)
						.setParameter("type", type)
						.setParameter("deprecated", deprecated)
						.getResultList();
				return result;
			} else {
				/* ------------------------------------------------------- */
				List<AppointmentType> result = em.createNamedQuery(
						AppointmentType.FIND_ALL_APPOINTMENT_TYPES_BY_TYPE_AND_CALENDAR_ID)
						.setParameter("type", type)
						.setParameter("calendarId", calendarId)
						.setParameter("deprecated", deprecated)
						.getResultList();
				return result;
			}
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			logger.error("Error while trying to get AppointmentType with type "+type+" and calendar ID "+calendarId, e);
			/* ------------------------------------------------------- */
			return null;
			/* ------------------------------------------------------- */
		}
		/* ====================================================== */
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#saveCalendar(lu.tudor.santec.gecamed.agenda.ejb.entity.beans.AgendaCalendar)
	 */
	public AgendaCalendar saveCalendar(String clientId, AgendaCalendar calendar) {
		/* ====================================================== */
		if (calendar == null)
			return null;
		/* ------------------------------------------------------- */
		if (! calendar.isPersistent()) {
			calendar.setCreatedBy(login.getCurrentUserID());
			calendar.setCreated(new Date());			
		}
		calendar.setModifiedBy(login.getCurrentUserID());
		calendar.setModified(new Date());
		/* ------------------------------------------------------- */
		AgendaCalendar ac = null;
		try {
			/* ------------------------------------------------------- */
			ac = em.merge(calendar);
//			sendCalendarUpdateMessage(clientId, AppointmentManager.MSG_ADDED_CALENDAR, ac.getId());
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			logger.error("Error while trying to save the AgendaCalendar", e);
			return null;
		}
		return ac;
		/* ====================================================== */
	}
	
	
	
	/**
	 * Create a default calendar
	 */
	public AgendaCalendar createDefaultCalendar() {
		/* ================================================== */
		AgendaCalendar defaultCal = new AgendaCalendar();
		defaultCal.setColor(Color.RED.getRGB());
		defaultCal.setTitle("Default");

		defaultCal = saveCalendar("", defaultCal);
		/* ------------------------------------------------------- */
		// add default appointment types to the calendar
		/* ------------------------------------------------------- */
		Collection<AppointmentType> types = getAppointmentTypesByID(AppointmentType.GENERAL, null, false);
		for(AppointmentType at : types) {
			/* ------------------------------------------------------- */
			// create a copy
			/* ------------------------------------------------------- */
			AppointmentType newType = (AppointmentType) at.clone();
			newType.setId(null);
			newType.setCalendarId(defaultCal.getId());
			// save
			em.merge(newType);
			/* ------------------------------------------------------- */
		}
		
		return defaultCal;
		/* ================================================== */
	}
	
	/**
	 * Create a new calendar for the physician, but only if there is no one at the moment
	 * 
	 * @param physician
	 */
	public Boolean createNewCalendar(Physician physician) {
		/* ================================================== */
		/* ------------------------------------------------------- */
    	// create a calendar for the physician
    	/* ------------------------------------------------------- */
    	Collection<AgendaCalendar> cals = getCalendarsByPhysician(physician.getId());
    	if (cals == null || cals.size() < 1) {
    		/* ------------------------------------------------------- */
    		// create a new calendar.
    		/* ------------------------------------------------------- */
    		AgendaCalendar ag = new AgendaCalendar();
    		
    		ag.setCreated(new Date());
    		ag.setCreatedBy(login.getCurrentUserID());
    		
    		ag.setPhysicianId(physician.getId());
    		ag.setTitle(physician.toString());
    		
    		ag.setUserId(physician.getUserId());
    		
    		ag.setColor(physician.getColor());
    		/* ------------------------------------------------------- */
    		ag = saveCalendar("", ag);
    		if (ag == null)
    			return false;
    		/* ------------------------------------------------------- */
    		// add default appointment types to the calendar
    		/* ------------------------------------------------------- */
    		
    		Collection<AppointmentType> types = getAppointmentTypesByID(AppointmentType.GENERAL, null, false);
    		for(AppointmentType at : types) {
    			/* ------------------------------------------------------- */
    			// create a copy
    			/* ------------------------------------------------------- */
    			AppointmentType newType = (AppointmentType) at.clone();
    			newType.setId(null);
    			newType.setCalendarId(ag.getId());
    			// save
    			try {
					/* --------------------------------------------- */
    				em.merge(newType);
					/* --------------------------------------------- */
				} catch (Exception e) {
					/* --------------------------------------------- */
					logger.error("Error while trying to create new AppointmentType", e);
					/* --------------------------------------------- */
				}
    			/* ------------------------------------------------------- */
    		}
    		/* ------------------------------------------------------- */
    		try {
				sendCalendarUpdateMessage("", AppointmentManager.MSG_ADDED_CALENDAR, ag.getId(), null);
			} catch (JMSException e) {
				logger.error("Error while sending update message", e);
			}
			return true;
    		/* ------------------------------------------------------- */
    	} else {
    		/* ------------------------------------------------------- */
    		// change the color of the physician calendars to the one from the physician
    		if (physician.getColor() != null) {
    			/* ------------------------------------------------------- */
    			for (AgendaCalendar c : cals) {
    				c.setColor(physician.getColor());
    				try {
						/* --------------------------------------------- */
						em.merge(c);
						try {
		    				sendCalendarUpdateMessage("", AppointmentManager.MSG_UPDATE_CALENDAR, c.getId(), null);
		    			} catch (JMSException e) {
		    				logger.error("Error while sending update message", e);
		    			}
						/* --------------------------------------------- */
					} catch (Exception e) {
						/* --------------------------------------------- */
						logger.error("error while saving AgendaCalendar", e);
						/* --------------------------------------------- */
					}
    			}
    			/* ------------------------------------------------------- */
    		}
    		/* ------------------------------------------------------- */
    	}
    	return false;
		/* ================================================== */
	}
	
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#removeCalendar(lu.tudor.santec.gecamed.agenda.ejb.entity.beans.AgendaCalendar)
	 */
	@SuppressWarnings("unchecked")
	public void removeCalendar(String clientId, AgendaCalendar calendar) {
		/* ====================================================== */
		if (calendar == null || calendar.getId() == null)
			return;
		/* ------------------------------------------------------- */
		// get all appointments
		Query q = em.createQuery("SELECT Object(o) FROM Appointment o "
				+ "WHERE o.calendarId = " + calendar.getId());
		try {
			/* ------------------------------------------------------- */
			List<Appointment> result = q.getResultList();
			/* ------------------------------------------------------- */
			if (result != null)
				for (Appointment ap : result) {
					em.remove(ap);
				}
			em.flush();
			/* ------------------------------------------------------- */
			// remove the appointment types
			/* ------------------------------------------------------- */
			Query q1 = em.createQuery("DELETE FROM AppointmentType t " +
										"WHERE t.calendarId = "+calendar.getId());
//			q1.setParameter("id", calendar.getId());
			q1.executeUpdate();
			/* ------------------------------------------------------- */
			// delete calendar
			/* ------------------------------------------------------- */
			em.remove(em.find(AgendaCalendar.class, calendar.getId()));
			// inform all calendars
			// sendCalendarUpdateMessage(clientId, MSG_REMOVED_CALENDAR,
			// calendar.getId());
		} catch (Exception e) {
			logger.error("Error while removing AgendaCalendar", e);
		}
		/* ====================================================== */
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentInterface#getCalendars()
	 */
	@SuppressWarnings("unchecked")
	public Collection<AgendaCalendar> getCalendars() {
		/* ====================================================== */
		try {
			/* ------------------------------------------------------- */
			List<AgendaCalendar> result = em.createNamedQuery(
					AgendaCalendar.FIND_ALL_CALENDARS).getResultList();
			return result;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			/* ------------------------------------------------------- */
			return null;
			/* ------------------------------------------------------- */
		}
		/* ====================================================== */
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getCalendar(java.lang.Integer)
	 */
	public AgendaCalendar getCalendar(Integer calendarId) {
		/* ====================================================== */
		if (calendarId == null)
			return null;
		/* ------------------------------------------------------- */
		Query q = em.createQuery("SELECT OBJECT(o) FROM AgendaCalendar o "
				+ "WHERE o.id = " + calendarId);
		try {
			/* ------------------------------------------------------- */
			AgendaCalendar ac = (AgendaCalendar) q.getSingleResult();
			return ac;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			return null;
		}
		/* ====================================================== */
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentInterface#getCalendarsByPhysician(java.lang.Integer)
	 */
	@SuppressWarnings("unchecked")
	public Collection<AgendaCalendar> getCalendarsByPhysician(
			Integer physicianId) {
		/* ====================================================== */
		if (physicianId == null)
			return null;
		/* ------------------------------------------------------- */
		try {
			/* ------------------------------------------------------- */
			List<AgendaCalendar> result = em.createNamedQuery(
					AgendaCalendar.FIND_ALL_CALENDARS_BY_PHYSICIAN)
					.setParameter("physicianId", physicianId).getResultList();
			return result;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			/* ------------------------------------------------------- */
			return null;
			/* ------------------------------------------------------- */
		}
		/* ====================================================== */
	}

	@SuppressWarnings("unchecked")
	public Collection<AgendaCalendar> getCalendarsByUser(Integer userId) {
		/* ====================================================== */
		if (userId == null)
			return null;
		/* ------------------------------------------------------- */
		try {
			/* ------------------------------------------------------- */
			List<AgendaCalendar> result = em.createNamedQuery(
					AgendaCalendar.FIND_ALL_CALENDARS_BY_USER).setParameter(
					"userId", userId).getResultList();
			return result;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			/* ------------------------------------------------------- */
			return null;
			/* ------------------------------------------------------- */
		}
		/* ====================================================== */
	}
	
	/**
	 * Get the office calendar of the Agenda.
	 * If in Database the column 'is_background' is true the calendar is a office calendar.
	 * IMPORTANT: In the current version 1.2.04 of GECAMed there are only a office calendar in GECAMed.
	 * @return the office calendar
	 */
	private AgendaCalendar getOfficeCalendar()
	{
		/* ------------------------------------------------------- */
		Query q = em.createQuery("SELECT OBJECT(o) FROM AgendaCalendar o "
				+ "WHERE o.background = " + true);
		try {
			/* ------------------------------------------------------- */
			AgendaCalendar ac = (AgendaCalendar) q.getSingleResult();
			return ac;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			return null;
		}
	}

	
	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentInterface#saveAppointment(lu.tudor.santec.gecamed.agenda.ejb.entity.beans.Appointment)
	 */
	public Appointment saveAppointment(Appointment app) {
		return saveAppointment(app, true);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentInterface#saveAppointment(lu.tudor.santec.gecamed.agenda.ejb.entity.beans.Appointment)
	 */
	public Appointment saveAppointment(Appointment app, boolean resetNotified) {
		/* ====================================================== */
		if (app == null)
			return null;
		/* ------------------------------------------------------- */
		if (app.getCreatedBy() == null) {
			app.setCreatedBy(login.getCurrentUserID());			
		}
		if (app.getCreated() == null) {
			app.setCreated(new Date());
		}

		app.setModifiedBy(login.getCurrentUserID());
		app.setModified(new Date());
		
		try {
			/* ------------------------------------------------------- */
			Appointment aNew = em.merge(app);
			logger.debug("Appointment Saved: " + aNew);
			
			return aNew;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			logger.warn("Error while saving Appointment " + app.getSummary(), e);
			return null;
		}
		/* ====================================================== */
	}

	/* OLD ONE, STILL IN USE, BUT SHOULD BE REPLACED
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getAppointments(java.lang.Integer, java.util.Date, java.util.Date)
	 */
	@SuppressWarnings("unchecked")
	public List<Appointment> getAppointments(Integer calendarId, Date from, Date to, String client) {
		long time = System.currentTimeMillis();
		/* ====================================================== */
		if (calendarId == null)
			return new ArrayList<Appointment>(0);
		List<Appointment> newResult = new ArrayList<Appointment>();
		/* ------------------------------------------------------- */
		// get all appointment with no recurrence that are in the range.
		Query q = em.createQuery("SELECT Object(o) FROM Appointment o "
				+ "WHERE o.calendarId = :calId " + "AND o.frequency = :noRecur "
				+ "AND (o.startDate >= :from AND o.startDate <= :to  )"
				+ "OR (o.startDate <= :from AND o.endDate >= :from) "
				+ "ORDER BY o.startDate");
		q.setParameter("noRecur", Appointment.NO_RECUR);
		q.setParameter("calId", calendarId);
		q.setParameter("from", from);
		q.setParameter("to", to);

		/* ------------------------------------------------------- */
		try {
			/* ------------------------------------------------------- */
			List<Appointment> resultNormal = q.getResultList();

			newResult.addAll(resultNormal);
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			logger.error("Error while fetching Appointments for calendar with ID "+calendarId, e);
		}
		long took1 = System.currentTimeMillis()-time;
		/* ------------------------------------------------------- */
		// rec appointments
		Query qr = em.createQuery("SELECT Object(o) FROM Appointment o "
				+ "WHERE o.calendarId = :calId " + "AND o.frequency > :noRecur "
				+ "AND ((o.until >=  :from AND o.startDate <= :to) "
				+ "OR (o.startDate <= :to AND o.until = null))");
		qr.setParameter("noRecur", Appointment.NO_RECUR);
		qr.setParameter("calId", calendarId);
		qr.setParameter("from", from);
		qr.setParameter("to", to);

		try {
			/* ------------------------------------------------------- */
			List<Appointment> resultRecur = qr.getResultList();

			newResult.addAll(resultRecur);
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			logger.error("Error while fetching Appointments", e);
		}
		long took = System.currentTimeMillis()-time;
		
		String msg = "Client: "  + client + " Fetching " +newResult.size()+ " Events for Calendar: " +calendarId+ " [" + dayFormatter.format(from) + "-" + dayFormatter.format(to) + "] took " + took + "[" + took1 + "/" +(took-took1) + "]";
		if (took > 3000) {
			logger.warn(msg);			
		} else {
			logger.debug(msg);		
		}

		return newResult;
		/* ====================================================== */
	}
	
	
	/*
	 * This is the CURRENT ONE - PLEASE USE THIS METHOD FOR LOADING APPOINTMENTS
	 * 
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getAppointments(java.lang.Integer, java.util.Date, java.util.Date, java.util.Date, boolean, java.util.Locale)
	 */
	@SuppressWarnings("unchecked")
	public List<Appointment> getAppointments(Integer calendarId, Date from, Date to, Date modifiedAfter, boolean recurings, boolean debug) {
		
		long start = System.currentTimeMillis();
		
		Level level = Level.DEBUG;
		// write out as Info 
		if (debug) {
			level = Level.INFO;
		}

		logger.log(level, "Fetching Appointmens " + (recurings?"(including Recurings)":"")+ " for Cal "+ calendarId + " from " + df.format(from) + " to " + df.format(to) 
				+ (modifiedAfter!=null?" modified after " + df.format(modifiedAfter):""));
		
		/* ====================================================== */
		if (calendarId == null)
			return new ArrayList<Appointment>(0);
		
		List<Appointment> newResult = new ArrayList<Appointment>();
		/* ------------------------------------------------------- */
		
		// get all appointment with no recurrence that are in the range.
		WhereClause clause = new WhereClause();
		clause.setOperator(HibernateOperator.c_AndOperator);
		clause.addCondition(new HibernateCondition("o", "calendarId", 	HibernateOperator.c_EqualOperator, "calendarId", calendarId));
		clause.addCondition(new HibernateCondition("o", "frequency", HibernateOperator.c_EqualOperator, "frequency", Appointment.NO_RECUR));
		
		Group g0 = new Group(HibernateOperator.c_OrOperator);
		// Appointment starts in the from-to range
		Group g1 = new Group(HibernateOperator.c_AndOperator);
		g1.addCondition(new HibernateCondition("o", "startDate", HibernateOperator.c_GreaterOrEqualOperator, "startDate", from));
		g1.addCondition(new HibernateCondition("o", "startDate", HibernateOperator.c_LowerOrEqualOperator, "end1", to));
		g0.addGroup(g1);
		
		// Appointment ends in the from-to range
		Group g2 = new Group(HibernateOperator.c_AndOperator);
		g2.addCondition(new HibernateCondition("o", "startDate", HibernateOperator.c_LowerOrEqualOperator, "startDate1", from));
		g2.addCondition(new HibernateCondition("o", "endDate", HibernateOperator.c_GreaterOrEqualOperator, "startDate2", from));
		g0.addGroup(g2);
		
		clause.addGroup(g0);

		if (modifiedAfter != null) {
			clause.addCondition(new HibernateCondition("o", "modified", 	HibernateOperator.c_GreaterOrEqualOperator, "modified", modifiedAfter));
		}

		/* ------------------------------------------------------- */

		try {
			/* ------------------------------------------------------- */
			logger.debug("SELECT Object(o) FROM Appointment o " + clause.toString() + " ORDER BY o.startDate");
			
			Query q = HibernateQueryFactory.buildQueryFromWhereClause (em, "SELECT Object(o) FROM Appointment o ", clause, " ORDER BY o.startDate");
			
			List<Appointment> resultNormal = q.getResultList();
			
			newResult.addAll(resultNormal);
			
			logger.log(level, "Fetching " + resultNormal.size() + " Appointments took " + (System.currentTimeMillis()-start) );
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			logger.error("Error while fetching Appointments for calendar with ID "+calendarId, e);
		}
		
		long last = System.currentTimeMillis();
		
		/* ------------------------------------------------------- */
		// rec appointments
		if (recurings) {
			WhereClause recClause = new WhereClause();
			recClause.setOperator(HibernateOperator.c_AndOperator);
			recClause.addCondition(new HibernateCondition("o", "calendarId", 	HibernateOperator.c_EqualOperator, "calendarId", calendarId));
			recClause.addCondition(new HibernateCondition("o", "frequency", HibernateOperator.c_GreaterThanOperator, "frequency", Appointment.NO_RECUR));

			Group g00 = new Group(HibernateOperator.c_OrOperator);
			// Appointment starts in the from-to range
			Group g11 = new Group(HibernateOperator.c_AndOperator);
			g11.addCondition(new HibernateCondition("o", "startDate", HibernateOperator.c_LowerOrEqualOperator, "end1", to));
			g11.addCondition(new HibernateCondition("o", "until", HibernateOperator.c_GreaterOrEqualOperator, "startDate", from));
			g00.addGroup(g11);
			
			// Appointment ends in the from-to range
			Group g22 = new Group(HibernateOperator.c_AndOperator);
			g22.addCondition(new HibernateCondition("o", "startDate", HibernateOperator.c_LowerOrEqualOperator, "end2", to));
			g22.addCondition(new HibernateCondition("until", HibernateOperator.c_IsOperator, Condition.c_Null));
			g00.addGroup(g22);
			
			recClause.addGroup(g00);
			
			// modified will not work for recurring appointments.
			if (modifiedAfter != null) {
				recClause.addCondition(new HibernateCondition("o", "modified", 	HibernateOperator.c_GreaterOrEqualOperator, "modified", modifiedAfter));
			}
			
			try {
				/* ------------------------------------------------------- */
				logger.debug("SELECT Object(o) FROM Appointment o " + recClause.toString() + " ORDER BY o.startDate");
				
				Query qr = HibernateQueryFactory.buildQueryFromWhereClause (em, "SELECT Object(o) FROM Appointment o ", recClause, " ORDER BY o.startDate");
				
				List<Appointment> resultRecur = qr.getResultList();
				
				logger.log(level, "Fetching " + resultRecur.size() + " Recuring Appointments took " + (System.currentTimeMillis()-last));
				
				if (resultRecur != null) {
					last = System.currentTimeMillis();
					
					for (Appointment appointment : resultRecur) {
						newResult.addAll(createRecurrAppointments(appointment, from, to));
					}	
					
					logger.log(level, "Creating Recuring Appointments Instances took " + (System.currentTimeMillis()-last));
				}			
				/* ------------------------------------------------------- */
			} catch (Exception e) {
				logger.error("Error while fetching recurring Appointments", e);
			}
		}

		// sort by Startdate
		Collections.sort(newResult);
		
		logger.log(level, "Total Fetching " + newResult.size() + " for Cal " + calendarId + " took " + (System.currentTimeMillis()-start));
		
		return newResult;
		/* ====================================================== */
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getAppointmentByCalIDandDesc(java.lang.Integer, java.lang.String)
	 */
	public Appointment getAppointmentByCalIDandDesc(Integer calendarId, String description) {
		try {
			Query q = em.createQuery("SELECT OBJECT(o) FROM Appointment o " +
					"WHERE o.calendarId = :calendarId " +
					"AND o.description like :description ");
			q.setParameter("calendarId", calendarId);
			q.setParameter("description", description);
			q.setMaxResults(1);
			@SuppressWarnings("unchecked")
			List<Appointment> l = q.getResultList();
			if (l.size() > 0) {
				return (Appointment) l.get(0);				
			}
		} catch (NoResultException e) {
			return null;
		} catch (NonUniqueResultException e) {
			return null;
		}
		return null;
	}
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getAppointments(java.lang.Integer, java.lang.Integer)
	 */
	@SuppressWarnings("unchecked")
	public List<Appointment> getAppointments(Integer patientId, Integer limit) {
		/* ================================================== */
		if (patientId == null)
			return null;
		/* ------------------------------------------------------- */
		// default limit to avoid infinite lookups
		Integer resultLimit = 20;
		if (limit != null)
			resultLimit = limit;
		/* ------------------------------------------------------- */

		Query q = em.createQuery("SELECT OBJECT(o) FROM Appointment o " +
				"WHERE o.patientId = :patientId " +
				"AND (o.startDate >= :currDate OR o.frequency > :noRecur)");
		//"AND (o.startDate >= :currDate OR o.endDate >= :currDate)");
		q.setParameter("noRecur", Appointment.NO_RECUR);
		q.setParameter("patientId", patientId);
		q.setParameter("currDate", new Date());
		/* ------------------------------------------------------- */
		q.setMaxResults(resultLimit);
		try {
			return q.getResultList();
		} catch (Exception e) {
			return null;
		}
		/* ================================================== */
	}
	

	public void deleteAppointment(String clientId, Appointment ap) {
		/* ====================================================== */
		if (ap == null || !ap.isPersistent())
			return;
		/* ------------------------------------------------------- */		
		try
		{
			ap = em.find(Appointment.class, ap.getId());
			if (ap != null)
			{
				Integer		appointmentId	= ap.getId(); 
				
				try
				{
					em.remove(ap);
					logger.info("Appointment " + appointmentId + " deleted!");
				}
				catch (Exception e)
				{
					logger.error("Error while trying to delete appointment "+appointmentId);
				}
			}
			else
				logger.warn("Trying to delete appointment, but appointment doesn't exist.");
		}
		catch (Exception e)
		{
			logger.warn("Trying to delete appointment, but appointment doesn't exist.");
		}

		/* ====================================================== */
	}

	public void deleteAppointmentType(String clientId, AppointmentType at) {
		/* ====================================================== */
		if (at == null)
			return;
		/* ------------------------------------------------------- */
		// find
		Query q = em.createQuery("SELECT Object(o) FROM AppointmentType o "
				+ "WHERE o.id = " + at.getId());
		try {
			/* ------------------------------------------------------- */
			AppointmentType apRem = (AppointmentType) q.getSingleResult();
			/* ------------------------------------------------------- */
			em.remove(apRem);
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			logger.error("Error while removing AppointmentType", e);
		}

		/* ====================================================== */
	}

	public AppointmentType saveAppointmentType(String clientId,
			AppointmentType type) {
		/* ====================================================== */
		if (type == null)
			return null;
		/* ------------------------------------------------------- */
		try {
			return em.merge(type);
		} catch (Exception e) {
			logger.warn("Error while saving AppointmentType " + type.getName(), e);
			return null;
		}
		/* ====================================================== */
	}


	/**
	 * retrieves all CalendarSelections for this user
	 * 
	 * @param userId
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Collection<AgendaCalendar> getCalendarSelectionsByUser(Integer userId) {
		/* ====================================================== */
		if (userId == null)
			return null;
		/* ------------------------------------------------------- */
		try {
			/* ------------------------------------------------------- */
			Collection<AgendaCalendar> cals = new ArrayList<AgendaCalendar>();
			List<UserCalendarSelection> result = em
					.createNamedQuery(
							UserCalendarSelection.FIND_ALL_USER_CALENDAR_SELECTIONS_BY_USER)
					.setParameter("userId", userId).getResultList();
			for (Iterator<UserCalendarSelection> iter = result.iterator(); iter.hasNext();) {
				UserCalendarSelection element = iter
						.next();
				cals.add(element.getCalendar());
			}
			return cals;
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			/* ------------------------------------------------------- */
			return null;
			/* ------------------------------------------------------- */
		}
		/* ====================================================== */
	}

	/**
	 * 
	 * @param ucs
	 * @return
	 */
	public void addUserCalendarSelection(AgendaCalendar cal) {
		List<?> rl = em
				.createNamedQuery(
						UserCalendarSelection.FIND_ALL_USER_CALENDAR_SELECTIONS_BY_USER_AND_CALENDAR)
				.setParameter("userId", login.getCurrentUserID()).setParameter(
						"calendar", cal).getResultList();
		if (rl == null || rl.size() == 0) {
			UserCalendarSelection element = new UserCalendarSelection(login
					.getCurrentUserID(), cal);
			em.persist(element);
		}
	}

	/**
	 * @param ucs
	 */
	public void deleteUserCalendarSelection(AgendaCalendar cal) {
		List<?> rl = em
				.createNamedQuery(
						UserCalendarSelection.FIND_ALL_USER_CALENDAR_SELECTIONS_BY_USER_AND_CALENDAR)
				.setParameter("userId", login.getCurrentUserID()).setParameter(
						"calendar", cal).getResultList();
		if (rl != null) {
			for (Iterator<?> iter = rl.iterator(); iter.hasNext();) {
				UserCalendarSelection element = (UserCalendarSelection) iter
						.next();
				em.remove(element);
			}
		}
	}

	/**
	 * @param calendarId
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public void sendCalendarUpdateMessage(String clientId, Integer type, Integer calendarId, Date changedDate) throws JMSException {
		
		try {
			Boolean syncEnabled = (Boolean) this.login.getAdminSettingValue(AgendaAdminSettingsConstants.NAME, AgendaAdminSettingsConstants.JMS_ENABLED, AgendaAdminSettingsConstants.DEFAULT_JMS_ENABLED);			
			if (syncEnabled != null && syncEnabled.booleanValue() == false) {
				logger.debug("Calendar Update JMS disabled, Skipping message");
				return;
			}
		} catch (Exception e) {
			logger.warn("Error reading Calendar Update JMS  Setting");
		}
		
		try {
			conn = factory.createTopicConnection();
			conn.start();
        		/* ================================================== */
        		session = conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE);
        		/* ------------------------------------------------------- */
        		TopicPublisher send = session.createPublisher(calendarUpdateTopic);
        		
        		// ==============================================================
        		// create a base tupel containing the id of the calling client
        		// and a tupel of the message type and the calendarid
        		// ==============================================================
        		int[] dataTupel = new int[] {type, calendarId};
        		ArrayList<Object> baseTupel = new ArrayList<Object>(3);
        		baseTupel.add(clientId);
        		baseTupel.add(dataTupel);
        		if (changedDate != null) {
        			baseTupel.add(changedDate);        			
        		}
        		/* ------------------------------------------------------- */
        		ObjectMessage om = session.createObjectMessage(baseTupel);
        		send.publish(om);
        		send.close();
        		/* ------------------------------------------------------- */
        		session.close();
			conn.close();
		} catch (Exception e) {
			logger.error("Error while sending agenda update message", e);
		}
		/* ================================================== */
	}
	
	
	/**
	 * @param app
	 * @param direction
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Appointment checkFreeAppointment(Appointment app, int direction, Locale locale) {
		/* ================================================== */
		if (app == null || app.getStartDate() == null ||
				app.getEndDate() == null || app.getCalendarId() == null)
			return null;
		
		List<Appointment> result = new ArrayList<Appointment>();
		///////////////////////////////////////////
		// check if appointment time is in holidays 
		///////////////////////////////////////////
		/* ------------------------------------------------------- */
		int nationalHolidays;
		try 
		{
			nationalHolidays = ((Long) em.createNamedQuery(Appointment.NO_OF_NATIONAL_HOLIDAYS_FOR_DAY)
					.setParameter("date", app.getStartDate())
					.getSingleResult())
					.intValue();
		}
		catch (Exception e)
		{
			logger.error("Error while fetching NaiontalHoliday", e);
			nationalHolidays = 0;
		}
		
		if (nationalHolidays > 1)
		{
			if (dayFormatter == null)
				dayFormatter = new SimpleDateFormat("yyyy-MM-dd");
			logger.warn("There is more than 1 national holiday entered for the date "+dayFormatter.format(app.getStartDate()));
		}
		
		if(nationalHolidays > 0)
		{
			if (direction == FORWARD)
			{
				Calendar startDateCal = Calendar.getInstance(locale);
				startDateCal.setTime(app.getStartDate());
				startDateCal.add(Calendar.DAY_OF_MONTH, + 1);
				app.setStartDate(startDateCal.getTime());
				Calendar endDateCal = Calendar.getInstance(locale);
				endDateCal.setTime(app.getEndDate());
				endDateCal.add(Calendar.DAY_OF_MONTH, + 1);
				app.setEndDate(endDateCal.getTime());
			}else{
				Calendar startDateCal = Calendar.getInstance(locale);
				startDateCal.setTime(app.getStartDate());
				startDateCal.add(Calendar.DAY_OF_MONTH, - 1);
				app.setStartDate(startDateCal.getTime());
				Calendar endDateCal = Calendar.getInstance(locale);
				endDateCal.setTime(app.getEndDate());
				endDateCal.add(Calendar.DAY_OF_MONTH, - 1);
				app.setEndDate(endDateCal.getTime());
			}
		}

		// get office calendar
		AgendaCalendar officeCalendar = getOfficeCalendar();
		/* ------------------------------------------------------- */
		// make a query to find an appointment that overlaps the given time
		
		String qString = "SELECT OBJECT(o) FROM Appointment o " +
									"WHERE " +
									"(o.calendarId = :calId OR o.calendarId = :officeCalId) " +
									"AND ((o.startDate < :startDate AND o.endDate > :startDate) " +
										"OR (o.startDate >= :startDate AND o.startDate < :endDate " +
												"AND o.endDate >= :startDate)) ORDER BY ";
		/* ------------------------------------------------------- */
		// sort the result depending of the direction
		if (direction == FORWARD)
			qString += "o.endDate";
		else
			qString += "o.startDate";
		/* ------------------------------------------------------- */
		Query q = em.createQuery(qString);
		q.setParameter("calId",	    	app.getCalendarId());
		q.setParameter("officeCalId",	officeCalendar.getId());
		q.setParameter("startDate", 	app.getStartDate());
		q.setParameter("endDate",   	app.getEndDate());
		
		// get the recurrent Appointments
		String qString1 = "SELECT OBJECT(o) FROM Appointment o " +
									"WHERE " +
									"(o.calendarId = :calId OR o.calendarId = :officeCalId) " +
									"AND (" +
										"(o.startDate < :startDate AND o.endDate > :startDate) " +
										"OR " +
										"(o.startDate >= :startDate AND o.startDate < :endDate AND o.endDate >= :startDate)" +
										"OR " +
										"(o.startDate < :startDate AND o.frequency > 0 AND (o.until is null OR o.until >= :startDate))" +
									") ORDER BY ";
		/* ------------------------------------------------------- */
		// sort the result depending of the direction
		if (direction == FORWARD)
			qString1 += "o.endDate";
		else
			qString1 += "o.startDate";
		/* ------------------------------------------------------- */
		Query q1 = em.createQuery(qString1);
		q1.setParameter("calId",	    app.getCalendarId());
		q1.setParameter("officeCalId",	officeCalendar.getId());
		q1.setParameter("startDate", app.getStartDate());
		q1.setParameter("endDate",   app.getEndDate());
		
		
		/* ------------------------------------------------------- */
		// if the query returns no results, the appointment is free
		try {
			/* ------------------------------------------------------- */

			result.addAll(q.getResultList());
			List<Appointment> resultWithRecurrent = q1.getResultList();
			
			// *********************************************************************************
			// create recurrent Appointment entries and add them if they on the current day
//			List<Appointment> appsToCheck = new ArrayList<Appointment>();
			// create a limit data, lets say from to day plus 400 days
			// Date destination = DateUtil.getDiffDay(new Date(), 400);
			// take the current date for create the recurrent app
//			Date destination = new Date();
			
			Date toDayDate = app.getStartDate();
						
			Calendar untilCal = Calendar.getInstance(locale);
			untilCal.setTime(toDayDate);
			untilCal.set(Calendar.HOUR_OF_DAY, 23);
			untilCal.set(Calendar.MINUTE,      59);
			untilCal.set(Calendar.SECOND, 	  59);
			untilCal.set(Calendar.MILLISECOND, 999);
			toDayDate = untilCal.getTime();
			
			
			for (Appointment app1 : resultWithRecurrent)
			{
				// if the app is a recurrent app and a entry is on the current day set the app to the date of this entry.
				/* ------------------------------------------------------- */
				List<Appointment> tList = createRecurrentAppointmets(app1, toDayDate, locale);

				// set the color to the events
//				int i = 1;
				for (Appointment tE : tList)
				{
//					i++;
					
					Calendar calAppRec = Calendar.getInstance(locale);
					calAppRec.setTime(tE.getStartDate());
					calAppRec.set(Calendar.HOUR_OF_DAY, 23);
					calAppRec.set(Calendar.MINUTE,      59);
					calAppRec.set(Calendar.SECOND, 	  59);
					calAppRec.set(Calendar.MILLISECOND, 999);
					Date appDateRec = calAppRec.getTime();

					if(appDateRec.equals(toDayDate))
					{

						Date appStart = app.getStartDate();
						Date appEnd = app.getEndDate();
						Date appRecStart = tE.getStartDate();
						Date appRecEnd = tE.getEndDate();
						// if app is inside or overlapp the tE app then add tE to result
						if(
								(appStart.before(appRecStart) && appEnd.after(appRecStart))
								||
								(appStart.equals(appRecStart))
								||
								(appStart.after(appRecStart) && appStart.before(appRecEnd))
						){
							result.add(tE.clone());
						}
					}
					
//					if(tE.getDate().getTime() >= new Date().getTime())
//					{
//					  tE.setColor(new Color(AgendaModule.getCalendarForId(app1.getCalendarId()).getColor()));
//					  // set Calendar id to Event
//					  tE.set(Event.CALENDAR_ID, app1.getCalendarId());
//					  // set Calendar isBackground to Event
//					  tE.set(Event.CALENDAR_IS_BACKGROUND, AgendaModule.getCalendarForId(app1.getCalendarId()).isBackground());
//					  futureApps.add(tE);
//					}
				}
				/* ------------------------------------------------------- */
				//futureApps.addAll(tList);
				/* ------------------------------------------------------- */
			}
			/* ------------------------------------------------------- */
			// TODO test output
//			for (int i = 0; i < resultWithRecurrent.size(); i++) {
//				System.out.println("##################################################################");
//				System.out.println(resultWithRecurrent.get(i));
//				System.out.println("##################################################################");
//				
//			}
			
			//Collections.sort(futureApps);
			
			if (result == null || result.size() < 1)
				return app;
			else {
				/* ------------------------------------------------------- */
				// get the next free date
				return findNextFree(app, result, direction, locale);
				/* ------------------------------------------------------- */
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return null;
		}
		/* ================================================== */
	}
	

	
	/**
	 * OLD ONE. DO NOT USE ANYMORE!
	 * 
	 * @param recurrentAppointment
	 * @param toDate
	 * @param locale
	 * @return
	 */
	private List<Appointment> createRecurrentAppointmets(Appointment recurrentAppointment, Date toDate, Locale locale)
	{
		// syso
		// TODO clear code ;)
//		System.out.println("************************************************************");
//		System.out.println("toDayDate: " + toDate);
//		System.out.println("locale: " + locale);
//		System.out.println("recurrentAppointment: " + recurrentAppointment.getId());
//		System.out.println("startDate: " + recurrentAppointment.getStartDate());
//		System.out.println("EndDate: " + recurrentAppointment.getEndDate());
//		System.out.println("Frequency: " + recurrentAppointment.getFrequency());
//		System.out.println("Cal id: " + recurrentAppointment.getCalendarId());
//		System.out.println("\n");
		
		// ==========================================================
		// create a appointment list as result list and add the appointment 
		List<Appointment> recurrentList = new ArrayList<Appointment>();
		
		
		Appointment app = recurrentAppointment.clone();

		/* ------------------------------------------------------- */
		Date until = toDate; // app.getStartDate();

		/* ------------------------------------------------------- */
		// move the until date to midnight, because we want to create
		// recurring appointments on the last day
		/* ------------------------------------------------------- */

		Calendar untilCal = Calendar.getInstance(locale);
		untilCal.setTime(until);
		untilCal.set(Calendar.HOUR_OF_DAY, 23);
		untilCal.set(Calendar.MINUTE,      59);
		untilCal.set(Calendar.SECOND, 	  59);
		untilCal.set(Calendar.MILLISECOND, 999);
        until = untilCal.getTime();
				
		// ==========================================================
		// if no recurrence is set for the appointment return the
		// event list
		if (Appointment.NO_RECUR == app.getFrequency())
			return recurrentList;
		
		/* ------------------------------------------------------- */
		// D A I L Y
		// ==========================================================
		if (Appointment.DAILY == app.getFrequency())
		{
			
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			
			//*** New code begin ***//
//			System.out.println("Daily interval: " + rule.getInterval());
			Calendar calToDay = new GregorianCalendar();
			Calendar calRApp = new GregorianCalendar();
			calToDay.setTime(toDate);                      // erster Zeitpunkt
			calRApp.setTime(app.getStartDate());                      // zweiter Zeitpunkt
			calRApp.set(Calendar.HOUR_OF_DAY, 23);
			calRApp.set(Calendar.MINUTE,      59);
			calRApp.set(Calendar.SECOND, 	  59);
			calRApp.set(Calendar.MILLISECOND, 999);
			long time = calToDay.getTime().getTime() -  calRApp.getTime().getTime();  // Differenz in ms // TODO je nachdem wer grösser ist
			long days = Math.round( (double)time / (24. * 60. * 60. * 1000.) );     // Differenz in Tagen
//			System.out.println("Zeit-Differenz in Tagen: " + days );
			long result = days / interval;
			long rest = days % interval;
//			System.out.println("ganze teiler: " + result);
//			System.out.println("rest: " + rest);
			
			if(rest == 0 || interval == 1)
			{
//				System.out.println("+++++++++++++++++++++++++++++++++++++++++++");
//				System.out.println("add app to list: " + app);
				Appointment appR = app.clone();
				calRApp.setTime(app.getStartDate()); 
				calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
				calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
				calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
				appR.setStartDate(calRApp.getTime());
				calRApp.setTime(appR.getEndDate());
				calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
				calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
				calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
				appR.setEndDate(calRApp.getTime());
//				System.out.println("added appR: " + appR);
				// app macht add to list
				recurrentList.add(appR); // add recurrence entry
			}
			//*** New code end *****//
			
//			Date start = new Date(app.getStartDate().getTime());
//			
//			Calendar calTemp = Calendar.getInstance(locale);
//			calTemp.setTime(until);
//
//			calTemp.set(Calendar.DAY_OF_MONTH, calTemp.get(Calendar.DAY_OF_MONTH) - interval);
//
//			Date newUntil = new Date();
//			newUntil = calTemp.getTime();
//			while (start.before(newUntil))
//			{
//				/* ------------------------------------------------------- */
//
//				// compute next date
//				Appointment appDaily = app.clone();
//				Calendar cal = Calendar.getInstance(locale);
//				cal.setTime(start);
//
//				cal.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH) + interval);
//				
//				//////////////////////////////////////////////////////////////////////
//								
//				Date newStartDate = cal.getTime();
//				Date oldStartDate = appDaily.getStartDate();
//
//				long diffTime = 0;
//				// if moved to a later date
//				if (newStartDate.getTime() > oldStartDate.getTime()) {
//					diffTime = newStartDate.getTime() - oldStartDate.getTime();
//				} else {
//					diffTime = (-1)*(oldStartDate.getTime() - newStartDate.getTime());
//				}
//				appDaily.setEndDate(new Date(appDaily.getEndDate().getTime() + diffTime));
//				appDaily.setStartDate(newStartDate);
//				
//				///////////////////////////////////////////////////////////////////////
//				
//				recurrentList.add(appDaily);
//				start = cal.getTime();
//			}
		}
		
		// ==========================================================
		// W E E K L Y
		// ==========================================================
		if (Appointment.WEEKLY == app.getFrequency())
		{
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			int[] weekDays = rule.getWeekDays();
			
			
			//*** New code begin ***//
//			System.out.println("Weekly interval: " + rule.getInterval());
			Calendar calToDay = new GregorianCalendar();
			Calendar calRApp = new GregorianCalendar();
			calToDay.setTime(toDate);                      // erster Zeitpunkt
			calRApp.setTime(app.getStartDate());                      // zweiter Zeitpunkt
			calRApp.set(Calendar.HOUR_OF_DAY, 23);
			calRApp.set(Calendar.MINUTE,      59);
			calRApp.set(Calendar.SECOND, 	  59);
			calRApp.set(Calendar.MILLISECOND, 999);
//			System.out.println("Start day of week: " + calRApp.get(Calendar.DAY_OF_WEEK));
			int daysToSub = 0;
			if(Calendar.MONDAY - calRApp.get(Calendar.DAY_OF_WEEK) == 1)
				daysToSub = 6;
			else
				daysToSub = Calendar.MONDAY - calRApp.get(Calendar.DAY_OF_WEEK);
			calRApp.add(Calendar.DAY_OF_MONTH, -daysToSub);
//			System.out.println("modify Start day of week: " + calRApp.get(Calendar.DAY_OF_WEEK));
//			System.out.println("modify start Date: " + calRApp.getTime());
			long time = calToDay.getTime().getTime() -  calRApp.getTime().getTime();  // Differenz in ms // TODO je nachdem wer grösser ist
			double weeksD = Math.floor(time / (7. * 24. * 60. * 60. * 1000.));
//			System.out.println("Zeit-Differenz in Wochen D: " + weeksD );
			long weeks = (long) weeksD;     // Differenz in Tagen
//			System.out.println("Zeit-Differenz in Wochen: " + weeks );
			long result = weeks / interval;
			long rest = weeks % interval;
//			System.out.println("ganze teiler: " + result);
//			System.out.println("rest: " + rest);
//			System.out.println("weekDays :");
//			for (int i = 0; i < weekDays.length; i++) {
//				System.out.println(i + ". :" + weekDays[i]);
//			}
			if(rest == 0 || interval == 1)
			{
//				calRApp.setTime(app.getStartDate());
//				System.out.println("Start day of week: " + calRApp.get(Calendar.DAY_OF_WEEK));
				for (int i = 0; i < weekDays.length; i++)
				{	
					if(calToDay.get(Calendar.DAY_OF_WEEK) == weekDays[i])
					{
//						System.out.println(i + ". :" + weekDays[i]);
//						System.out.println("+++++++++++++++++++++++++++++++++++++++++++");
//						System.out.println("add app to list: " + app);
						Appointment appR = app.clone();
						calRApp.setTime(app.getStartDate());
						calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
						calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
						calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
						appR.setStartDate(calRApp.getTime());
						calRApp.setTime(appR.getEndDate());
						calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
						calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
						calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
						appR.setEndDate(calRApp.getTime());
//						System.out.println("added appR: " + appR);
						// app macht add to list
						recurrentList.add(appR); // add recurrence entry
					}
				}
			}
			//*** New code end *****//
			
//			Date start = new Date(app.getStartDate().getTime());
//			
//			System.out.println("start date: " + start);
//			System.out.println("util date: " + until);
//			
//			if (weekDays.length > 0) {
//				/* ------------------------------------------------------- */
//				boolean currentWeekTreated = false;
//				Calendar cal = null;
//				int startDay;
//				while(start.before(until)) 
//				{
//
//					/* ------------------------------------------------------- */
//					
//					
//					for (int i = 0; i < weekDays.length; i++) {
//						/* ------------------------------------------------------- */
//						// create an Event object from the appointment
//						/* ------------------------------------------------------- */
//
//						Appointment appWeekly = app.clone();
//						// compute next date. Specifying the Locale is important
//						// because the Locale influences the value for 
//						// minimalDaysInFirstWeek property of calendar, which in turn
//						// is important for WEEK_OF_YEAR property to be handled properly.
//						cal = Calendar.getInstance(locale);
//						cal.setFirstDayOfWeek(Calendar.SUNDAY);
//						cal.setTime(start);
//						/* ------------------------------------------------------- */
//						// set week
//						//
//						// we have to keep an eye on the current week if there are days
//						// in the recurrence cycle that are between the start date and the 
//						// end of the current week. They have be included in the cycle as well.
//						/* ------------------------------------------------------- */
////						System.out.println("appWeekly.getStartDate(): " +  appWeekly.getStartDate());
//						startDay = DateUtil.getDayOfWeek(appWeekly.getStartDate());
//						
//						if (!currentWeekTreated && startDay < weekDays[i])
//							cal.set(Calendar.WEEK_OF_YEAR, cal.get(Calendar.WEEK_OF_YEAR));
//						else{
//							if(currentWeekTreated)
//								cal.set(Calendar.WEEK_OF_YEAR, cal.get(Calendar.WEEK_OF_YEAR) + interval);
//							else
//								continue;
//						}
//
//						/* ------------------------------------------------------- */
//						cal.set(Calendar.DAY_OF_WEEK, weekDays[i]);
//						/* ------------------------------------------------------- */
//						
//						/* ------------------------------------------------------- */
//						// move the event
//						////////////////////////////////////////////////////////
//												
//						Date newStartDate = cal.getTime();
//						Date oldStartDate = appWeekly.getStartDate();
//
//						long diffTime = 0;
//						// if moved to a later date
//						if (newStartDate.getTime() > oldStartDate.getTime()) {
//							diffTime = newStartDate.getTime() - oldStartDate.getTime();
//						} else {
//							diffTime = (-1)*(oldStartDate.getTime() - newStartDate.getTime());
//						}
//						appWeekly.setEndDate(new Date(appWeekly.getEndDate().getTime() + diffTime));
//						appWeekly.setStartDate(newStartDate);
//						
//						////////////////////////////////////////////////////////
//						recurrentList.add(appWeekly);
//					}
//					currentWeekTreated = true;
//					if (cal == null)
//						break;
//					start = cal.getTime();
//				}
//			}
		}
		
		// ==========================================================
		// M O N T H L Y
		// ==========================================================
		if (Appointment.MONTHLY == app.getFrequency())
		{
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			
			//*** New code begin ***//
//			System.out.println("Monthly interval: " + rule.getInterval());
			Calendar calToDay = new GregorianCalendar();
			Calendar calRApp = new GregorianCalendar();
			calToDay.setTime(toDate);                      // erster Zeitpunkt
			calRApp.setTime(app.getStartDate());                      // zweiter Zeitpunkt
			calRApp.set(Calendar.HOUR_OF_DAY, 23);
			calRApp.set(Calendar.MINUTE,      59);
			calRApp.set(Calendar.SECOND, 	  59);
			calRApp.set(Calendar.MILLISECOND, 999);
			
			int monthDiff = 0;
			int toDayMonth = calToDay.get(Calendar.MONTH);
			int rAppMonth = calRApp.get(Calendar.MONTH);
			if(toDayMonth < rAppMonth) monthDiff = rAppMonth - toDayMonth;
			else monthDiff = toDayMonth - rAppMonth;
			int yearDiff = (calToDay.get(Calendar.YEAR) - calRApp.get(Calendar.YEAR)) * 12;
			monthDiff = monthDiff + yearDiff;
			

//			System.out.println("Zeit-Differenz in Monaten: " + monthDiff );
			long result = monthDiff / interval;
			long rest = monthDiff % interval;
//			System.out.println("ganze teiler: " + result);
//			System.out.println("rest: " + rest);
			
			if(rest == 0 || interval == 1)
			{
//				System.out.println("rest und interval passt...");
//				System.out.println("Sonderfall prüfen: ");
//				System.out.println("day of month: " + calRApp.get(Calendar.DAY_OF_MONTH));
//				System.out.println("Month: " + calRApp.get(Calendar.MONTH));
//				System.out.println("diff year: " + yearDiff);
				int rAppDayOfMonth = calRApp.get(Calendar.DAY_OF_MONTH);
				// sonderfall 29 und 31 wird auf den 1 ten gesetzt 
				if(calRApp.get(Calendar.DAY_OF_MONTH) == 29 && calRApp.get(Calendar.MONTH) == 1 && yearDiff > 0) // 29/04
				{
//					System.out.println("29/04: " + recurrentAppointment.getStartDate());
					rAppDayOfMonth = 1;
				}
				
				if(calRApp.get(Calendar.DAY_OF_MONTH) == 31) // 29/04
				{
//					System.out.println("31: " + recurrentAppointment.getStartDate());
					rAppDayOfMonth = 1;
				}
				if(calToDay.get(Calendar.DAY_OF_MONTH) == rAppDayOfMonth)
				{
//					System.out.println("+++++++++++++++++++++++++++++++++++++++++++");
//					System.out.println("add app to list: " + app);
					Appointment appR = app.clone();
					calRApp.setTime(app.getStartDate()); 
					calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
					calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
					calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
					appR.setStartDate(calRApp.getTime());
					calRApp.setTime(appR.getEndDate());
					calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
					calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
					calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
					appR.setEndDate(calRApp.getTime());
//					System.out.println("added appR: " + appR);
					// app macht add to list
					recurrentList.add(appR); // add recurrence entry
				}
			}
			//*** New code end *****//
			
			
//			Date start = new Date(app.getStartDate().getTime());
//			// to compare start an d until date at the same time at midnight
//			Date startDateAtMidnight = new Date(app.getStartDate().getTime());
//			
//			// set time of date to midnight
//			untilCal = Calendar.getInstance(locale);
//			untilCal.setTime(startDateAtMidnight);
//			untilCal.set(Calendar.HOUR_OF_DAY, 23);
//			untilCal.set(Calendar.MINUTE,      59);
//			untilCal.set(Calendar.SECOND, 	  59);
//			untilCal.set(Calendar.MILLISECOND, 999);
//			startDateAtMidnight = untilCal.getTime();
//			
//			while (startDateAtMidnight.before(until))
//			{		
//				/* ------------------------------------------------------- */
//				//Event er = AgendaModule.appointment2Event(app);
//				Appointment appMonthly = app.clone();
//				// compute next date
//				Calendar cal = Calendar.getInstance();
//				cal.setTime(start);
//				if((cal.get(Calendar.MONTH) + interval) <= 12)
//					cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) + interval);
//				else
//				{
//					cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) - 11);
//					cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + 1);
//				}
//				
//				/* ------------------------------------------------------- */
//				
//				Date newStartDate = cal.getTime();
//				Date oldStartDate = appMonthly.getStartDate();
//
//				long diffTime = 0;
//				// if moved to a later date
//				if (newStartDate.getTime() > oldStartDate.getTime()) {
//					diffTime = newStartDate.getTime() - oldStartDate.getTime();
//				} else {
//					diffTime = (-1)*(oldStartDate.getTime() - newStartDate.getTime());
//				}
//				appMonthly.setEndDate(new Date(appMonthly.getEndDate().getTime() + diffTime));
//				appMonthly.setStartDate(newStartDate);
//				
//				/* ------------------------------------------------------- */
//								
//				
////				recurrentList.add(appMonthly);
//				start = cal.getTime();
//				startDateAtMidnight = cal.getTime();
//				// set time of date to midnight
//				untilCal = Calendar.getInstance(locale);
//				untilCal.setTime(startDateAtMidnight);
//				untilCal.set(Calendar.HOUR_OF_DAY, 23);
//				untilCal.set(Calendar.MINUTE,      59);
//				untilCal.set(Calendar.SECOND, 	  59);
//				untilCal.set(Calendar.MILLISECOND, 999);
//				startDateAtMidnight = untilCal.getTime();
//			}
		}
		
		// ==========================================================
		// Y E A R L Y
		// ==========================================================
		if (Appointment.YEARLY == app.getFrequency())
		{
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			
			//*** New code begin ***//
//			System.out.println("Year interval: " + rule.getInterval());
			Calendar calToDay = new GregorianCalendar();
			Calendar calRApp = new GregorianCalendar();
			calToDay.setTime(toDate);                      // erster Zeitpunkt
			calRApp.setTime(app.getStartDate());                      // zweiter Zeitpunkt
			calRApp.set(Calendar.HOUR_OF_DAY, 23);
			calRApp.set(Calendar.MINUTE,      59);
			calRApp.set(Calendar.SECOND, 	  59);
			calRApp.set(Calendar.MILLISECOND, 999);
			
			int monthDiff = 0;
			int toDayMonth = calToDay.get(Calendar.MONTH);
			int rAppMonth = calRApp.get(Calendar.MONTH);
			if(toDayMonth < rAppMonth) monthDiff = rAppMonth - toDayMonth;
			else monthDiff = toDayMonth - rAppMonth;
			int yearDiff = (calToDay.get(Calendar.YEAR) - calRApp.get(Calendar.YEAR));
			//monthDiff = monthDiff + yearDiff;
			

//			System.out.println("Zeit-Differenz in Jahren: " + yearDiff );
			long result = yearDiff / interval;
			long rest = yearDiff % interval;
//			System.out.println("ganze teiler: " + result);
//			System.out.println("rest: " + rest);
			
			if(rest == 0 || interval == 1)
			{
//				System.out.println("rest und interval passt...");
//				System.out.println("Sonderfall prüfen Year: ");
				
//				System.out.println("diff year: " + yearDiff);
				int rAppDayOfMonth = calRApp.get(Calendar.DAY_OF_MONTH);
				// sonderfall 29 und 31 wird auf den 1 ten gesetzt 
				if(calRApp.get(Calendar.DAY_OF_MONTH) == 29 && calRApp.get(Calendar.MONTH) == 1 && interval != 4 && interval != 8) // 29/04
				{
//					System.out.println("29/04: " + recurrentAppointment.getStartDate());
					rAppDayOfMonth = 1;
					rAppMonth = rAppMonth + 1;
					if(rAppMonth >= 12) rAppMonth = 0;
				}
				
//				if(calRApp.get(Calendar.DAY_OF_MONTH) == 31) // 29/04
//				{
//					System.out.println("31: " + recurrentAppointment.getStartDate());
//					rAppDayOfMonth = 1;
//				}
//				System.out.println("to day month: " + calToDay.get(Calendar.MONTH));
//				System.out.println("rapp Month: " + calRApp.get(Calendar.MONTH));
				if(calToDay.get(Calendar.MONTH) == calRApp.get(Calendar.MONTH))
//					System.out.println("jo is gleich ????");
				if((calToDay.get(Calendar.DAY_OF_MONTH) == rAppDayOfMonth) && (calToDay.get(Calendar.MONTH) == rAppMonth))
				{
//					System.out.println("+year++++++++++++++++++++++++++++++++++++++++++");
//					System.out.println("add app to list: " + app);
					Appointment appR = app.clone();
					calRApp.setTime(app.getStartDate()); 
					calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
					calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
					calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
					appR.setStartDate(calRApp.getTime());
					calRApp.setTime(appR.getEndDate());
					calRApp.set(Calendar.DAY_OF_MONTH, calToDay.get(Calendar.DAY_OF_MONTH));
					calRApp.set(Calendar.MONTH, calToDay.get(Calendar.MONTH));
					calRApp.set(Calendar.YEAR, calToDay.get(Calendar.YEAR));
					appR.setEndDate(calRApp.getTime());
//					System.out.println("added appR: " + appR);
					// app macht add to list
					recurrentList.add(appR); // add recurrence entry
				}
			}
			//*** New code end *****//
			
//			Date start = new Date(app.getStartDate().getTime());
//			
//			// to compare start and until date at the same time at midnight
//			Date startDateAtMidnight = new Date(app.getStartDate().getTime());
//			
//			// set time of date to midnight
//			untilCal = Calendar.getInstance(locale);
//			untilCal.setTime(startDateAtMidnight);
//			untilCal.set(Calendar.HOUR_OF_DAY, 23);
//			untilCal.set(Calendar.MINUTE,      59);
//			untilCal.set(Calendar.SECOND, 	  59);
//			untilCal.set(Calendar.MILLISECOND, 999);
//			startDateAtMidnight = untilCal.getTime();
//			
//			while (startDateAtMidnight.before(until))
//			{
//				/* ------------------------------------------------------- */
//				Appointment appYearly = app.clone();
//				// compute next date
//				Calendar cal = Calendar.getInstance(locale);
//				cal.setTime(start);
//				cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + interval);
//				/* ------------------------------------------------------- */
//								
//				Date newStartDate = cal.getTime();
//				Date oldStartDate = appYearly.getStartDate();
//
//				long diffTime = 0;
//				// if moved to a later date
//				if (newStartDate.getTime() > oldStartDate.getTime()) {
//					diffTime = newStartDate.getTime() - oldStartDate.getTime();
//				} else {
//					diffTime = (-1)*(oldStartDate.getTime() - newStartDate.getTime());
//				}
//				appYearly.setEndDate(new Date(appYearly.getEndDate().getTime() + diffTime));
//				appYearly.setStartDate(newStartDate);
//				
//				/* ------------------------------------------------------- */
////				recurrentList.add(appYearly);
//				/* ------------------------------------------------------- */
//				start = cal.getTime();
//				startDateAtMidnight = cal.getTime();
//				startDateAtMidnight = DateUtil.move2Midnight(startDateAtMidnight);
//			}
		}
		// list of events
		
		// TODO test out
//		System.out.println("recurrentList.size(): " + recurrentList.size());
//		for (int i = 0; i < recurrentList.size(); i++) {
//			System.out.println("app " + i + ". : " + recurrentList.get(i));
//		}
		return recurrentList;
		
	}
	
	/**
	 * 
	 * Find next free space according to the appointment's date
	 * and the direction
	 * 
	 * @param app
	 * @param direction
	 * @return
	 */
	private Appointment findNextFree(Appointment app, List<Appointment> storedApps, int direction, Locale locale)
	{
//		if(locale.equals(null) || locale.equals("")) locale = Locale.FRANCE;
		/* ================================================== */
		if (FORWARD == direction) { // search into the future
			/* ------------------------------------------------------- */
			// find the last overlapping
			Appointment lastOverlapping = storedApps.get(storedApps.size()-1);
			// ------------------------------------
			// shift the proposal to the end of the last overlapping appointment
//			long duration = DateUtil.getDiffDay(app.getStartDate(), lastOverlapping.getEndDate());
			
			app = shiftForwardAppointment(app, lastOverlapping);
			
			// check again
			// warning, bad recursion possible!
			return checkFreeAppointment(app, direction, locale);
			/* ------------------------------------------------------- */
		} else 
			if (BACKWARDS == direction) {
				/* ------------------------------------------------------- */
				// find the first overlapping
				Appointment firstOverlapping = storedApps.get(0);
				// ------------------------------------
				// shift the proposal backwards
				app = shiftBackwardAppointment(app, firstOverlapping);
				
				return checkFreeAppointment(app, direction, locale);
				/* ------------------------------------------------------- */
			}
			else
				return null;
		/* ================================================== */
	}
	
	
	
	/**
	 * @param formerApp
	 * @param refApp
	 * @return
	 */
	private Appointment shiftForwardAppointment(Appointment formerApp, Appointment refApp) {
		/* ================================================== */
		Appointment shiftAp = formerApp.copy();
		
		// move the appointment to the end of the used appointment
		long duration = DateUtil.getDiffDay(formerApp.getStartDate(), formerApp.getEndDate());
		shiftAp.setStartDate(refApp.getEndDate());
		shiftAp.setEndDate(  new Date(shiftAp.getStartDate().getTime() + duration));
		
		return shiftAp;
		/* ================================================== */
	}
	
	
	/**
	 * Creates a new appointment that is shifted
	 * 
	 * shiftApp-> end   = refApp.start-1
	 * shiftApp-> start = shiftApp.end - duration
	 * 
	 * @param shiftApp
	 * @param refApp
	 * @return
	 */
	private Appointment shiftBackwardAppointment(Appointment shiftApp, Appointment refApp) {
		/* ================================================== */
		Appointment newShiftAp = shiftApp.copy();
		
		// move the appointment to the end of the used appointment
		long duration = DateUtil.getDiffDay(shiftApp.getStartDate(), shiftApp.getEndDate());		
		newShiftAp.setEndDate(refApp.getStartDate());
		newShiftAp.setStartDate(new Date(newShiftAp.getEndDate().getTime() - duration));
		
		return newShiftAp;
		/* ================================================== */
	}
	

	/**
	 * returns the matching Appointments
	 * @param calendars
	 * @param searchString
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<Appointment> searchAppointments(Collection<AgendaCalendar> calendars, String searchString) {
		/* ================================================== */
		ArrayList<Integer> calIDs = new ArrayList<Integer>();
		for (Iterator<AgendaCalendar> iter = calendars.iterator(); iter.hasNext();) {
			AgendaCalendar element = iter.next();
			if (element != null)
				calIDs.add(element.getId());
		}
		return em.createNamedQuery(Appointment.FIND_APPOINTMENTS)
			.setParameter("calendarIds", calIDs)
			.setParameter("searchString", searchString)
			.getResultList();
		/* ================================================== */
	}
	
	
	/**
	 * Tries to find the next appointment of a patient 
	 * 
	 * @param patientId
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Appointment getNextAppointmentOfPatient(Integer patientId) {
		/* ================================================== */
		Query q = em.createQuery("SELECT OBJECT(o) FROM Appointment o " +
									"WHERE o.patientId = :patientId " +
									"AND o.startDate > :currDate ORDER BY o.startDate ASC");
		q.setParameter("patientId", patientId);
		q.setParameter("currDate", new Date());
		q.setMaxResults(1);
		
		try {
			List<Appointment> result = q.getResultList();
			return result.get(0);
		} catch (Exception e) {
			// e.printStackTrace();
			return null;
		}
		/* ================================================== */
	}

	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getAppointment(java.lang.Integer)
	 */
	public Appointment getAppointment(Integer appointmentId) {
		/* ====================================================== */
		if (appointmentId == null)
			return null;
		/* ------------------------------------------------------- */
		Query q = em.createQuery("SELECT OBJECT(o) FROM Appointment o " +
									"WHERE o.id = :id");
		q.setParameter("id", appointmentId);
		try {
			/* ------------------------------------------------------- */
			return (Appointment) q.getSingleResult();
			/* ------------------------------------------------------- */
		} catch (Exception e) {
			/* ------------------------------------------------------- */
			logger.error("Error while trying to get the appointment with ID "+appointmentId, e);
			return null;
			/* ------------------------------------------------------- */
		}
		/* ====================================================== */
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getCalendarColor(java.lang.Integer)
	 */
	public Integer getCalendarColor(Integer calendarId) {
		/* ================================================== */
		if (calendarId == null)
			return null;
		/* ------------------------------------------------------- */
		AgendaCalendar ac = getCalendar(calendarId);
		if (ac == null)
			return null;
		return ac.getColor();
		/* ================================================== */
	}

	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.agenda.ejb.session.interfaces.AppointmentManager#getCurrentAppointment(java.lang.Integer)
	 */
	@SuppressWarnings("unchecked")
	public Appointment getCurrentAppointment(Integer patientId, Date date) {
		/* ====================================================== */
		if (patientId == null)
			return null;
		/* ------------------------------------------------------- */
		Date startDay = DateUtil.round2Day(date);
		Date endDay = DateUtil.move2Midnight(date);
		/* ------------------------------------------------------- */
		Query q = em.createQuery("SELECT OBJECT(o) FROM Appointment o " +
									"WHERE o.patientId = :patientId " +
									"	AND o.startDate > :startDay AND o.startDate < :endDay");
		q.setParameter("patientId", patientId);
		q.setParameter("startDay", startDay);
		q.setParameter("endDay", endDay);
		/* ------------------------------------------------------- */
		// do the query
		/* ------------------------------------------------------- */
		try {
			/* --------------------------------------------- */
			List<Appointment> result = q.getResultList();
			return result.get(0);
			/* --------------------------------------------- */
		} catch (Exception e) {
			/* --------------------------------------------- */
//			e.printStackTrace();
			return null;
			/* --------------------------------------------- */
		}
		/* ====================================================== */
	}
	
	
	/**
	 * Creates a list of Appointments for each recurring appointment until a given date (toDate).
	 * All recurring Appointments bevor and at the given date (toDate) are collected in the list.
	 * @param app the appointment
	 * @param toDate
	 * @param locale
	 * @return the list of events
	 */
	public static List<Appointment> createRecurrAppointments(Appointment app, Date fromDate, Date toDate)
	{
		
		logger.debug("Creating Recurings ("+ fromDate + "-" + toDate +  ") for: " + app + " [" + app.getFrequency() + " Rule: "+ app.getRRule() + "]");
		// ==========================================================
		// create a event list and add the appointment as a event 
		List<Appointment> recurList = new ArrayList<Appointment>();

		/* ------------------------------------------------------- */
		Date until = toDate;
		if (app.getUntil() != null && app.getUntil().before(toDate)){
			until = app.getUntil();
		}
		
		/* ------------------------------------------------------- */
		// move the until date to midnight, because we want to create
		// recurring appointments on the last day
		/* ------------------------------------------------------- */
		until = DateUtil.move2Midnight(until);
		
		// ==========================================================
		// if no recurrence is set for the appointment return the
		// event list
		if (Appointment.NO_RECUR == app.getFrequency()) {
			logger.debug("\t Created instance: " + app);
			recurList.add(app);
			return recurList;
		}

		/* ------------------------------------------------------- */
		// D A I L Y
		// ==========================================================
		if (Appointment.DAILY == app.getFrequency())
		{
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			
			Date start = new Date(app.getStartDate().getTime());
			
			Calendar calTemp = Calendar.getInstance();
			calTemp.setTime(until);

			calTemp.set(Calendar.DAY_OF_MONTH, calTemp.get(Calendar.DAY_OF_MONTH) - interval);

			Date newUntil = new Date();
			newUntil = calTemp.getTime();
			
			while (start.before(newUntil))
			{
				/* ------------------------------------------------------- */
				// compute next date
				Calendar cal = Calendar.getInstance();
				cal.setTime(start);
				
				int dom = cal.get(Calendar.DAY_OF_MONTH);			
				cal.set(Calendar.DAY_OF_MONTH, dom + interval);
				
				logger.debug("\t\tDay of Month:" + dom + " + " + interval + " = " + cal.get(Calendar.DAY_OF_MONTH));
				
				if (start.after(cal.getTime())) {
					logger.warn("Error calculating next date, new is before old, exiting loop");
					return recurList;
				}
				
				// only if in time range -> create appointment
				if (fromDate == null || cal.getTime().after(fromDate)) {
					// generate recuring instance
					Appointment appGenerated = app.cloneAsRecuringInstance(cal.getTime());
					logger.debug("\t Created DAILY instance: " + appGenerated);
					recurList.add(appGenerated);					
				}
				
				start = cal.getTime();
				
			}
		}
		// ==========================================================
		// W E E K L Y
		// ==========================================================
		else if (Appointment.WEEKLY == app.getFrequency())
		{
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			
			Date start = new Date(app.getStartDate().getTime());
			int[] weekDays = rule.getWeekDays();
			
			if (weekDays.length > 0) {
				/* ------------------------------------------------------- */
				boolean currentWeekTreated = false;
				
				while(start.before(until)) {
					/* ------------------------------------------------------- */
					Calendar cal = null;
					for (int i = 0; i < weekDays.length; i++) {
//						System.out.println("++ start: " + start + " until: " + until + " Weekday: " + weekDays[i]);
						
						cal = Calendar.getInstance();
						cal.setFirstDayOfWeek(cal.getFirstDayOfWeek());
						cal.setTime(start);
						/* ------------------------------------------------------- */
						// set week
						//
						// we have to keep an eye on the current week if there are days
						// in the recurrence cycle that are between the start date and the 
						// end of the current week. They have be included in the cycle as well.
						/* ------------------------------------------------------- */
						int startDay = DateUtil.getDayOfWeek(app.getStartDate());
						
						if (currentWeekTreated || !(startDay < weekDays[i])) {
							if(currentWeekTreated){
								cal.add(Calendar.DAY_OF_YEAR, interval * 7);
							} else {
								continue;								
							}
						}
						/* ------------------------------------------------------- */
						cal.set(Calendar.DAY_OF_WEEK, weekDays[i]);
						/* ------------------------------------------------------- */
						
						if (start.after(cal.getTime())) {
							logger.warn("Error calculating next date, new is before old, exiting loop");
							return recurList;
						}
						
						// only if in time range -> create appointment
						if (fromDate == null || cal.getTime().after(fromDate)) {
							// generate recuring instance
							Appointment appGenerated = app.cloneAsRecuringInstance(cal.getTime());
							logger.debug("\t Created WEEKLY instance: " + appGenerated);
							recurList.add(appGenerated);					
						}
					}
					
					currentWeekTreated = true;
					if (cal == null)
						break;
					
					start = cal.getTime();
				}
			}
		}
		
		// ==========================================================
		// M O N T H L Y
		// ==========================================================
		else if (Appointment.MONTHLY == app.getFrequency())
		{
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			
			Date start = new Date(app.getStartDate().getTime());
			// to compare start an d until date at the same time at midnight
			Date startDateAtMidnight = new Date(app.getStartDate().getTime());
			startDateAtMidnight = DateUtil.move2Midnight(startDateAtMidnight);
			
			while (startDateAtMidnight.before(until))
			{		
				
				// compute next date
				Calendar cal = Calendar.getInstance();
				cal.setTime(start);
				if((cal.get(Calendar.MONTH) + interval) <= 12)
					cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) + interval);
				else
				{
					cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) - 11);
					cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + 1);
				}
				
				/* ------------------------------------------------------- */
				
				if (start.after(cal.getTime())) {
					logger.warn("Error calculating next date, new is before old, exiting loop");
					return recurList;
				}
				
				// only if in time range -> create appointment
				if (fromDate == null || cal.getTime().after(fromDate)) {
					// generate recuring instance
					Appointment appGenerated = app.cloneAsRecuringInstance(cal.getTime());
					logger.debug("\t Created MONTHLY instance: " + appGenerated);
					recurList.add(appGenerated);					
				}
				
				/* ------------------------------------------------------- */
				start = cal.getTime();
				startDateAtMidnight = cal.getTime();
				startDateAtMidnight = DateUtil.move2Midnight(startDateAtMidnight);
			}
		}
		
		// ==========================================================
		// Y E A R L Y
		// ==========================================================
		else if (Appointment.YEARLY == app.getFrequency())
		{
			RRule rule = new RRule(app.getRRule());
			Integer interval = rule.getInterval();
			
			Date start = new Date(app.getStartDate().getTime());
			
			// to compare start an d until date at the same time at midnight
			Date startDateAtMidnight = new Date(app.getStartDate().getTime());
			startDateAtMidnight = DateUtil.move2Midnight(startDateAtMidnight);
			
			while (startDateAtMidnight.before(until))
			{
				/* ------------------------------------------------------- */
				// compute next date
				Calendar cal = Calendar.getInstance();
				cal.setTime(start);
				cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + interval);
				/* ------------------------------------------------------- */
				
				if (start.after(cal.getTime())) {
					logger.warn("Error calculating next date, new is before old, exiting loop");
					return recurList;
				}
				
				// only if in time range -> create appointment
				if (fromDate == null || cal.getTime().after(fromDate)) {
					// generate recuring instance
					Appointment appGenerated = app.cloneAsRecuringInstance(cal.getTime());					
					logger.debug("\t Created YEARLY instance: " + appGenerated);
					recurList.add(appGenerated);					
				}
				
				/* ------------------------------------------------------- */
				start = cal.getTime();
				startDateAtMidnight = cal.getTime();
				startDateAtMidnight = DateUtil.move2Midnight(startDateAtMidnight);
			}
		}
		// list of events
		return recurList;
	}

	
}
