/**
 * Copyright (C) 2003-2007 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 **/
package org.exoplatform.calendar.service.impl;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jcr.Node;

import org.apache.commons.compress.utils.IOUtils;
import org.exoplatform.calendar.service.Attachment;
import org.exoplatform.calendar.service.Calendar;
import org.exoplatform.calendar.service.CalendarEvent;
import org.exoplatform.calendar.service.CalendarImportExport;
import org.exoplatform.calendar.service.CalendarService;
import org.exoplatform.calendar.service.EventQuery;
import org.exoplatform.calendar.service.ImportCalendarJob;
import org.exoplatform.calendar.service.ModifiedInputStream;
import org.exoplatform.calendar.service.Reminder;
import org.exoplatform.calendar.service.Utils;
import org.exoplatform.calendar.util.Constants;
import org.exoplatform.commons.utils.DateUtils;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.scheduler.JobSchedulerService;
import org.exoplatform.services.scheduler.impl.JobSchedulerServiceImpl;

import org.quartz.JobDetail;
import org.quartz.impl.triggers.SimpleTriggerImpl;

import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.Parameter;
import net.fortuna.ical4j.model.ParameterList;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.PropertyList;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.WeekDay;
import net.fortuna.ical4j.model.component.CalendarComponent;
import net.fortuna.ical4j.model.component.VAlarm;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VFreeBusy;
import net.fortuna.ical4j.model.component.VToDo;
import net.fortuna.ical4j.model.parameter.Encoding;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.parameter.XParameter;
import net.fortuna.ical4j.model.property.Action;
import net.fortuna.ical4j.model.property.Attach;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Categories;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.Completed;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.Due;
import net.fortuna.ical4j.model.property.Duration;
import net.fortuna.ical4j.model.property.ExDate;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.Method;
import net.fortuna.ical4j.model.property.Priority;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.RRule;
import net.fortuna.ical4j.model.property.RecurrenceId;
import net.fortuna.ical4j.model.property.Repeat;
import net.fortuna.ical4j.model.property.Status;
import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.model.property.XProperty;
import net.fortuna.ical4j.util.CompatibilityHints;

/**
 * Created by The eXo Platform SARL
 * Author : Hung Nguyen
 *          hung.nguyen@exoplatform.com
 * Jul 2, 2007  
 */
public class ICalendarImportExport implements CalendarImportExport {
  
  private JCRDataStorage      storage_;

  private static final Log    logger       = ExoLogger.getLogger(ICalendarImportExport.class);

  public ICalendarImportExport(JCRDataStorage storage) throws Exception {
    storage_ = storage;
  }

  public net.fortuna.ical4j.model.Calendar getCalendarComponent(net.fortuna.ical4j.model.Calendar calendar, CalendarEvent exoEvent, String componentType) throws Exception {
    Uid id = new Uid(exoEvent.getId());
    long start = exoEvent.getFromDateTime().getTime();
    long end = exoEvent.getToDateTime().getTime();
    String summary = exoEvent.getSummary();
    CalendarComponent event = null;

    if (end > 0) {
      if (CalendarComponent.VEVENT.equals(componentType)) {
        event = new VEvent(new DateTime(start), new DateTime(end), summary);
      } else if (CalendarComponent.VTODO.equals(componentType)) {
        event = new VToDo(new DateTime(start), new DateTime(end), summary);
      }
    } else {
      if (CalendarComponent.VEVENT.equals(componentType)) {
        event = new VEvent(new DateTime(start), summary);
      } else if (CalendarComponent.VTODO.equals(componentType)) {
        event = new VToDo(new DateTime(start), summary);
      }
    }

    if (event == null)
      return null;

    event.getProperties().getProperty(Property.DTSTART).getParameters().add(net.fortuna.ical4j.model.parameter.Value.DATE_TIME);

    event.getProperties().add(new Description(exoEvent.getDescription()));
    event.getProperties().getProperty(Property.DESCRIPTION).getParameters().add(net.fortuna.ical4j.model.parameter.Value.TEXT);

    event.getProperties().add(new Location(exoEvent.getLocation()));
    event.getProperties().getProperty(Property.LOCATION).getParameters().add(net.fortuna.ical4j.model.parameter.Value.TEXT);

    if (exoEvent.getEventCategoryName() != null) {
      event.getProperties().add(new Categories(exoEvent.getEventCategoryName()));
      event.getProperties().getProperty(Property.CATEGORIES).getParameters().add(net.fortuna.ical4j.model.parameter.Value.TEXT);
    }
    setPriorityCalEvent(event.getProperties(), exoEvent);

    if (CalendarComponent.VTODO.equals(componentType)) {
      if (exoEvent.getCompletedDateTime() != null) {
        long completed = exoEvent.getCompletedDateTime().getTime();
        event.getProperties().add(new Completed(new DateTime(completed)));
        event.getProperties().getProperty(Property.COMPLETED).getParameters().add(net.fortuna.ical4j.model.parameter.Value.DATE_TIME);
      }
      event.getProperties().add(new Due(new DateTime(end)));
      event.getProperties().getProperty(Property.DUE).getParameters().add(net.fortuna.ical4j.model.parameter.Value.DATE_TIME);
      if (!Utils.isEmpty(exoEvent.getStatus())) {
        event.getProperties().add(new Status(exoEvent.getStatus()));
        event.getProperties().getProperty(Property.STATUS).getParameters().add(net.fortuna.ical4j.model.parameter.Value.TEXT);
      }
    }

    if (exoEvent.getAttachment() != null && !exoEvent.getAttachment().isEmpty()) {
      for (Attachment att : exoEvent.getAttachment()) {
        byte bytes[] = new byte[att.getInputStream().available()];
        att.getInputStream().read(bytes);
        ParameterList plist = new ParameterList();
        plist.add(new XParameter(Parameter.CN, att.getName()));
        plist.add(new XParameter(Parameter.FMTTYPE, att.getMimeType()));
        plist.add(Encoding.BASE64);
        plist.add(Value.BINARY);
        Attach attach = new Attach(plist, bytes);
        event.getProperties().add(attach);
      }
    }

    if (exoEvent.getReminders() != null && !exoEvent.getReminders().isEmpty()) {
      for (Reminder r : exoEvent.getReminders()) {
        VAlarm reminder = new VAlarm(new DateTime(r.getFromDateTime()));
        Long times = new Long(1);
        if (r.isRepeat())
          times = (r.getAlarmBefore() / r.getRepeatInterval());
        reminder.getProperties().add(new Repeat(times.intValue()));
        reminder.getProperties().add(new Duration(new Dur(new Long(r.getAlarmBefore()).intValue())));
        if (Reminder.TYPE_POPUP.equals(r.getReminderType())) {
          for (String n : r.getReminderOwner().split(Utils.COMMA)) {
            Attendee a = new Attendee(n);
            reminder.getProperties().add(a);
          }
          reminder.getProperties().add(Action.DISPLAY);
        } else {
          for (String m : r.getEmailAddress().split(Utils.COMMA)) {
            Attendee a = new Attendee(m);
            reminder.getProperties().add(a);
          }
          reminder.getProperties().add(Action.EMAIL);
        }
        reminder.getProperties().add(new Summary(exoEvent.getSummary()));
        reminder.getProperties().add(new Description(r.getDescription()));
        reminder.getProperties().add(id);
        calendar.getComponents().add(reminder);
      }
    }
    if (exoEvent.isPrivate())
      event.getProperties().add(new Clazz(Clazz.PRIVATE.getValue()));
    else
      event.getProperties().add(new Clazz(Clazz.PUBLIC.getValue()));
    event.getProperties().getProperty(Property.CLASS).getParameters().add(net.fortuna.ical4j.model.parameter.Value.TEXT);
    String[] attendees = exoEvent.getInvitation();
    if (attendees != null && attendees.length > 0) {
      for (int i = 0; i < attendees.length; i++) {
        if (attendees[i] != null) {
          event.getProperties().add(new Attendee(attendees[i]));
        }
      }
      event.getProperties().getProperty(Property.ATTENDEE).getParameters().add(net.fortuna.ical4j.model.parameter.Value.TEXT);
    }
    if (!Utils.isEmpty(exoEvent.getRepeatType())) {
      Recur rc = null;
      if (CalendarEvent.RP_NOREPEAT.equalsIgnoreCase(exoEvent.getRepeatType())) {
      } else if (CalendarEvent.RP_WEEKEND.equalsIgnoreCase(exoEvent.getRepeatType())) {
        rc = new Recur(Recur.WEEKLY, 1);
        rc.getDayList().add(WeekDay.SU);
        rc.getDayList().add(WeekDay.SA);
        rc.setInterval(1);
      } else if (CalendarEvent.RP_WORKINGDAYS.equalsIgnoreCase(exoEvent.getRepeatType())) {
        rc = new Recur(Recur.WEEKLY, 1);
        rc.getDayList().add(WeekDay.MO);
        rc.getDayList().add(WeekDay.TU);
        rc.getDayList().add(WeekDay.WE);
        rc.getDayList().add(WeekDay.TH);
        rc.getDayList().add(WeekDay.FR);
        rc.setInterval(1);
      } else if (CalendarEvent.RP_WEEKLY.equalsIgnoreCase(exoEvent.getRepeatType())) {
        rc = new Recur(Recur.WEEKLY, 1);
        rc.getDayList().add(WeekDay.SU);
        rc.getDayList().add(WeekDay.MO);
        rc.getDayList().add(WeekDay.TU);
        rc.getDayList().add(WeekDay.WE);
        rc.getDayList().add(WeekDay.TH);
        rc.getDayList().add(WeekDay.FR);
        rc.getDayList().add(WeekDay.SA);
        rc.setInterval(1);
      } else {
        rc = new Recur(exoEvent.getRepeatType().toUpperCase(), 1);
        rc.setInterval(1);
      }
      if (rc != null) {
        rc.setWeekStartDay(WeekDay.SU.getDay());
        RRule r = new RRule(rc);
        event.getProperties().add(r);
      }
    }
    if (!Utils.isEmpty(exoEvent.getEventState())) {
      XProperty xProperty = new XProperty(Utils.X_STATUS, exoEvent.getEventState());
      event.getProperties().add(xProperty);
    }
    event.getProperties().add(id);
    calendar.getComponents().add(event);
    return calendar;
  }

  public OutputStream exportCalendar(String username, List<String> calendarIds, String type, int limited) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    if (type.equals(PRIVATE_TYPE)) {
      if (limited > 0) {
        EventQuery eventQuery = new EventQuery();
        eventQuery.setCalendarId(calendarIds.toArray(new String[] {}));
        eventQuery.setOrderBy(new String[] { Utils.JCR_LASTMODIFIED });
        eventQuery.setLimitedItems(limited);
        events = storage_.getEvents(username, eventQuery, null);
      } else {
        events = storage_.getUserEventByCalendar(username, calendarIds);
      }
    } else if (type.equals(SHARED_TYPE)) {
      events = storage_.getSharedEventByCalendars(username, calendarIds);
    } else if (type.equals(PUBLIC_TYPE)) {
      events = storage_.getGroupEventByCalendar(calendarIds);
    }
    if (events.isEmpty())
      return null;
    net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar();
    calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
    calendar.getProperties().add(Version.VERSION_2_0);
    calendar.getProperties().add(CalScale.GREGORIAN);
    calendar.getProperties().add(Method.REQUEST);
    for (CalendarEvent exoEvent : events) {
      if (exoEvent.getEventType().equals(CalendarEvent.TYPE_EVENT)) {
        calendar = getCalendarComponent(calendar, exoEvent, CalendarComponent.VEVENT);
      } else { // task
        calendar = getCalendarComponent(calendar, exoEvent, CalendarComponent.VTODO);
      }
    }
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    CalendarOutputter output = new CalendarOutputter();
    try {
      output.output(calendar, bout);
    } catch (ValidationException e) {
      if (logger.isDebugEnabled()) {
        logger.debug("Validate error", e);
      }
      return null;
    }
    return bout;
  }

  public OutputStream exportEventCalendar(String username, String calendarId, String type, String eventId) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    List<String> calendarIds = Arrays.asList(new String[] { calendarId });

    if (type.equals(PRIVATE_TYPE)) {
      events = storage_.getUserEventByCalendar(username, calendarIds);
    } else if (type.equals(SHARED_TYPE)) {
      events = storage_.getSharedEventByCalendars(username, calendarIds);
    } else if (type.equals(PUBLIC_TYPE)) {
      events = storage_.getGroupEventByCalendar(calendarIds);
    }
    net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar();
    calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
    calendar.getProperties().add(Version.VERSION_2_0);
    calendar.getProperties().add(CalScale.GREGORIAN);
    calendar.getProperties().add(Method.REQUEST);
    for (CalendarEvent exoEvent : events) {
      if (exoEvent.getId().equals(eventId)) {
        if (exoEvent.getEventType().equals(CalendarEvent.TYPE_EVENT)) {
          calendar = getCalendarComponent(calendar, exoEvent, CalendarComponent.VEVENT);
        } else {
          calendar = getCalendarComponent(calendar, exoEvent, CalendarComponent.VTODO);
        }
        break;
      }
    }
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    CalendarOutputter output = new CalendarOutputter();
    try {
      output.output(calendar, bout);
    } catch (ValidationException e) {
      if (logger.isDebugEnabled()) {
        logger.debug("Validate error", e);
      }
      return null;
    }
    return bout;
  }


  public List<CalendarEvent> getEventObjects(InputStream icalInputStream) throws Exception {
    CalendarBuilder calendarBuilder = new CalendarBuilder();
    net.fortuna.ical4j.model.Calendar iCalendar = calendarBuilder.build(icalInputStream);
    ComponentList componentList = iCalendar.getComponents();
    List<CalendarEvent> eventList = new ArrayList<CalendarEvent>();
    VEvent event;
    for (Object obj : componentList) {
      if (obj instanceof VEvent) {
        CalendarEvent exoEvent = new CalendarEvent();
        event = (VEvent) obj;
        if (event.getProperty(Property.UID) != null) {
          exoEvent.setId(event.getProperty(Property.UID).getValue());
        }
        if (event.getProperty(Property.CATEGORIES) != null) {
          exoEvent.setEventCategoryName(event.getProperty(Property.CATEGORIES).getValue().trim());
        }
        if (event.getSummary() != null)
          exoEvent.setSummary(event.getSummary().getValue());
        if (event.getDescription() != null)
          exoEvent.setDescription(event.getDescription().getValue());
        if (event.getStatus() != null)
          exoEvent.setStatus(event.getStatus().getValue());
        exoEvent.setEventType(CalendarEvent.TYPE_EVENT);
        if (event.getStartDate() != null)
          exoEvent.setFromDateTime(event.getStartDate().getDate());
        if (event.getEndDate() != null)
          exoEvent.setToDateTime(event.getEndDate().getDate());
        if (event.getLocation() != null)
          exoEvent.setLocation(event.getLocation().getValue());
        logger.info(event.getPriority());
        setPriorityExoEvent(event.getPriority(), exoEvent);
        try {
          RRule r = (RRule) event.getProperty(Property.RRULE);
          if (r != null && r.getRecur() != null) {
            Recur rc = r.getRecur();
            rc.getFrequency();
            if (Recur.WEEKLY.equalsIgnoreCase(rc.getFrequency())) {
              if (rc.getDayList().size() == 2) {
                exoEvent.setRepeatType(CalendarEvent.RP_WEEKEND);
              } else if (rc.getDayList().size() == 5) {
                exoEvent.setRepeatType(CalendarEvent.RP_WORKINGDAYS);
              }
              if (rc.getDayList().size() == 7) {
                exoEvent.setRepeatType(CalendarEvent.RP_WEEKLY);
              }
            } else {
              exoEvent.setRepeatType(rc.getFrequency().toLowerCase());
            }
          }
        } catch (Exception e) {
          if (logger.isDebugEnabled()) {
            logger.debug("Exception in method getEventObjects", e);
          }
        }
        exoEvent.setPrivate(true);
        PropertyList attendees = event.getProperties(Property.ATTENDEE);
        if (attendees.size() < 1) {
          exoEvent.setInvitation(new String[] {});
        } else {
          String[] invitation = new String[attendees.size()];
          for (int i = 0; i < attendees.size(); i++) {
            invitation[i] = ((Attendee) attendees.get(i)).getValue();
          }
          exoEvent.setInvitation(invitation);
        }
        eventList.add(exoEvent);
      }
    }
    return eventList;
  }

  public boolean isValidate(InputStream icalInputStream) {
    try {
      CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_UNFOLDING, true);
      CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING, true);
      CalendarBuilder calendarBuilder = new CalendarBuilder();
      calendarBuilder.build(icalInputStream);
      return true;
    } catch (ParserException pe) {
      if (logger.isDebugEnabled()) 
        logger.debug("Can not parsing input stream to calendar under ICal format", pe);
    } catch (IOException ioe) {
      if (logger.isDebugEnabled()) 
        logger.debug("IO Error occurs when parsing input stream to calendar under ICal format", ioe);
    }
    return false;
  }

  public void importCalendar(String username,
                              InputStream icalInputStream,
                              String calendarId,
                              String calendarName,
                              java.util.Calendar from,
                              java.util.Calendar to,
                              boolean isNew) throws Exception {
    CalendarBuilder calendarBuilder = new CalendarBuilder();
    CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_UNFOLDING, true);
    CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING, true);
    net.fortuna.ical4j.model.Calendar iCalendar;
    try {
      InputStream modStream = ModifiedInputStream.getIcsModifiedStream(icalInputStream);
      iCalendar = calendarBuilder.build(modStream);
    } catch (ParserException e) {
      if (logger.isDebugEnabled()) {
        logger.debug("ParserException occurs when building iCalendar object", e);
      }
      throw new ParserException("Cannot parsed the input stream to iCalendar object", e.getLineNo());
    } catch (IOException e) {
      if (logger.isDebugEnabled()) {
        logger.debug("IOException occurs when building iCalendar object", e);
      }
      throw new IOException("IOException when parsing input stream to iCalendar object");
    } finally {
      icalInputStream.close();
    }

    CalendarService calService = (CalendarService) ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(CalendarService.class);


    if (isNew) {
      Calendar exoCalendar = new Calendar();
      exoCalendar.setName(calendarName);
      exoCalendar.setCalendarColor(Constants.COLORS[0]);
      exoCalendar.setDescription(iCalendar.getProductId().getValue());
      exoCalendar.setPublic(false);
      exoCalendar.setCalendarOwner(username);
      calService.saveUserCalendar(username, exoCalendar, true);
      calendarId = exoCalendar.getId();
    }
    CalendarEvent exoEvent;
    ComponentList componentList = iCalendar.getComponents();

    Map<String, VFreeBusy> vFreeBusyData = new HashMap<String, VFreeBusy>();
    Map<String, VAlarm> vAlarmData = new HashMap<String, VAlarm>();

    Map<String, String> originalRecurrenceEvents = new HashMap<String, String>();
    Map<String, List<CalendarEvent>> exceptionOccurrences = new HashMap<String, List<CalendarEvent>>();

    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
    format.setTimeZone(DateUtils.getTimeZone("GMT"));
    int calType = storage_.getTypeOfCalendar(username, calendarId);

    for (Object obj : componentList) {
      if (obj instanceof VEvent) {
        VEvent v = (VEvent) obj;
        if (!v.getAlarms().isEmpty()) {
          for (Object o : v.getAlarms()) {
            if (o instanceof VAlarm) {
              VAlarm va = (VAlarm) o;
              vAlarmData.put(v.getUid().getValue() + ":" + va.getProperty(Property.ACTION).getName(), va);
            }
          }
        }
      }
      if (obj instanceof VFreeBusy)
        vFreeBusyData.put(((VFreeBusy) obj).getUid().getValue(), (VFreeBusy) obj);
    }

    for (Object obj : componentList) {
      if (obj instanceof VEvent) {
        VEvent event = (VEvent) obj;
        exoEvent = new CalendarEvent();
        try {
          exoEvent = RemoteCalendarServiceImpl.generateEvent(event, exoEvent, username, calendarId);

          String sValue = Utils.EMPTY_STR;
          String eValue = Utils.EMPTY_STR;
          if (event.getStartDate() != null) {
            if (to != null && event.getStartDate().getDate().getTime() > to.getTimeInMillis()) {
              continue;
            }
            sValue = event.getStartDate().getValue();
            exoEvent.setFromDateTime(event.getStartDate().getDate());
          }
          if (event.getEndDate() != null) {
            if (from != null && event.getEndDate().getDate().getTime() < from.getTimeInMillis()) {
              continue;
            }
            eValue = event.getEndDate().getValue();
            exoEvent.setToDateTime(event.getEndDate().getDate());
          }
          if (sValue.length() == 8 && eValue.length() == 8) {
            exoEvent.setToDateTime(new Date(event.getEndDate().getDate().getTime() - 1));
          }
          if (vFreeBusyData.get(event.getUid().getValue()) != null) {
            exoEvent.setEventState(CalendarEvent.ST_BUSY);
          }
          exoEvent = RemoteCalendarServiceImpl.setEventAttachment(event, exoEvent,eValue,sValue);
          exoEvent.setRepeatType(CalendarEvent.RP_NOREPEAT);
          if (event.getProperty(Property.RECURRENCE_ID) != null) {
            RecurrenceId recurId = (RecurrenceId) event.getProperty(Property.RECURRENCE_ID);
            exoEvent.setRecurrenceId(format.format(new Date(recurId.getDate().getTime())));
            String originalId = originalRecurrenceEvents.get(event.getUid().getValue());
            if (originalId != null) {
              Node originalNode = storage_.getCalendarEventNode(username, String.valueOf(calType), calendarId, originalId);
              CalendarEvent original = storage_.getEvent(originalNode);
              String uuid = originalNode.getUUID();
              exoEvent.setOriginalReference(uuid);

              List<String> excludeId;
              if (original.getExcludeId() != null && original.getExcludeId().length > 0) {
                excludeId = new ArrayList<String>(Arrays.asList(original.getExcludeId()));
              } else {
                excludeId = new ArrayList<String>();
              }
              excludeId.add(exoEvent.getRecurrenceId());
              original.setExcludeId(excludeId.toArray(new String[0]));
              if (calType == Calendar.TYPE_PRIVATE)
                storage_.saveUserEvent(username, calendarId, original, false);
              else if (calType == Calendar.TYPE_PUBLIC)
                storage_.savePublicEvent(calendarId, original, false);
              else if (calType == Calendar.TYPE_SHARED)
                storage_.saveEventToSharedCalendar(username, calendarId, original, false);

            } else {
              if (exceptionOccurrences.get(event.getUid().getValue()) == null) {
                List<CalendarEvent> exceptions = new ArrayList<CalendarEvent>();
                exceptions.add(exoEvent);
                exceptionOccurrences.put(event.getUid().getValue(), exceptions);
              } else {
                exceptionOccurrences.get(event.getUid().getValue()).add(exoEvent);
              }
            }
            storage_.saveOccurrenceEvent(username, calendarId, exoEvent, true);
          } else {
            if (event.getProperty(Property.RRULE) != null && event.getProperty(Property.RECURRENCE_ID) == null) {
              exoEvent = RemoteCalendarServiceImpl.calculateEvent(event, exoEvent);

              originalRecurrenceEvents.put(event.getUid().getValue(), exoEvent.getId());

              List<String> excludeIds = new ArrayList<String>();
              PropertyList exdates = event.getProperties(Property.EXDATE);
              if (exdates != null && exdates.size() > 0) {
                for (Object exdate : exdates) {
                  for (Object date : ((ExDate) exdate).getDates()) {
                    excludeIds.add(format.format(new Date(((net.fortuna.ical4j.model.DateTime) date).getTime())));
                  }
                }
              }

              List<CalendarEvent> exceptions = exceptionOccurrences.get(event.getUid().getValue());
              if (exceptions != null && exceptions.size() > 0) {
                for (CalendarEvent exception : exceptions) {
                  excludeIds.add(exception.getRecurrenceId());
                }
              }
              exoEvent.setExcludeId(excludeIds.toArray(new String[0]));

              if (calType == Utils.PRIVATE_TYPE)
                storage_.saveUserEvent(username, calendarId, exoEvent, true);
              else if (calType == Utils.SHARED_TYPE)
                storage_.saveEventToSharedCalendar(username, calendarId, exoEvent, true);
              else if (calType == Utils.PUBLIC_TYPE)
                storage_.savePublicEvent(calendarId, exoEvent, true);

              Node originalNode = storage_.getCalendarEventNode(username, String.valueOf(calType), calendarId, exoEvent.getId());
              String uuid = originalNode.getUUID();
              if (exceptions != null && exceptions.size() > 0) {
                for (CalendarEvent exception : exceptions) {
                  exception.setOriginalReference(uuid);
                  storage_.saveOccurrenceEvent(username, calendarId, exception, false);
                }
              }
            } else {
              if (calType == Utils.PRIVATE_TYPE)
                storage_.saveUserEvent(username, calendarId, exoEvent, true);
              else if (calType == Utils.SHARED_TYPE)
                storage_.saveEventToSharedCalendar(username, calendarId, exoEvent, true);
              else if (calType == Utils.PUBLIC_TYPE)
                storage_.savePublicEvent(calendarId, exoEvent, true);
            }
          }
        } catch (Exception e) {
          if (logger.isDebugEnabled()) {
            logger.debug("Exception occurs when importing iCalendar component: " + event.getUid().getValue() + ". Skip this component.", e);
          }
          continue;
        }
      }

      else if (obj instanceof VToDo) {
        VToDo event = (VToDo) obj;
        exoEvent = new CalendarEvent();
        try {
          exoEvent = RemoteCalendarServiceImpl.setTaskAttachment(event, exoEvent,username,calendarId,vFreeBusyData);
          switch (storage_.getTypeOfCalendar(username, calendarId)) {
          case Utils.PRIVATE_TYPE:
            calService.saveUserEvent(username, calendarId, exoEvent, true);
            break;
          case Utils.SHARED_TYPE:
            calService.saveEventToSharedCalendar(username, calendarId, exoEvent, true);
            break;
          case Utils.PUBLIC_TYPE:
            calService.savePublicEvent(calendarId, exoEvent, true);
            break;
          }
        } catch (Exception e) {
          if (logger.isDebugEnabled()) {
            logger.debug("Exception occurs when importing iCalendar VTODO component: " + event.getUid().getValue() + ". Skip this component.", e);
          }
          continue;
        }
      }
    }

  }

  @Override
  public ByteArrayOutputStream exportEventCalendar(CalendarEvent exoEvent) throws Exception {
    net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar();
    calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
    calendar.getProperties().add(Version.VERSION_2_0);
    calendar.getProperties().add(CalScale.GREGORIAN);
    calendar.getProperties().add(Method.REQUEST);
    if (exoEvent.getEventType().equals(CalendarEvent.TYPE_EVENT)) {
      calendar = getCalendarComponent(calendar, exoEvent, CalendarComponent.VEVENT);
    } else {
      calendar = getCalendarComponent(calendar, exoEvent, CalendarComponent.VTODO);
    }
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    CalendarOutputter output = new CalendarOutputter();
    try {
      output.output(calendar, bout);
    } catch (ValidationException e) {
      if (logger.isDebugEnabled()) {
        logger.debug("Validate error", e);
      }
      return null;
    }
    return bout;
  }
  
  public static void setPriorityCalEvent(PropertyList propertyList, CalendarEvent exoEvent) {
    if (exoEvent.getPriority() != null) {
      int priority = 0;
      for (int i = 0; i < CalendarEvent.PRIORITY.length; i++) {
        if (exoEvent.getPriority().equalsIgnoreCase(CalendarEvent.PRIORITY[i])) {
          if (i == CalendarEvent.PRI_MEDIUM) {
            priority = 5;
          } else if (i == CalendarEvent.PRI_LOW) {
            priority = 6;
          } else {
            priority = i;
          }
          propertyList.add(new Priority(priority));
          propertyList.getProperty(Property.PRIORITY).getParameters()
                      .add(net.fortuna.ical4j.model.parameter.Value.INTEGER);
          break;
        }
      }
    }
  }

  public static void setPriorityExoEvent(Priority priority, CalendarEvent exoEvent) {
    if (priority != null) {
      if (priority.getValue() != null) {
        int priorityVl = 0;
        try {
          priorityVl = Integer.parseInt(priority.getValue());
          if (1 < priorityVl && priorityVl <= 4) {
            priorityVl = CalendarEvent.PRI_HIGH;
          } else if (priorityVl == 5) {
            priorityVl = CalendarEvent.PRI_MEDIUM;
          } else if (priorityVl > 5) {
            priorityVl = CalendarEvent.PRI_LOW;
          }
          exoEvent.setPriority(CalendarEvent.PRIORITY[priorityVl]);
        } catch (NumberFormatException e) {
          logger.warn("Can not set the priority of this event");
        }
      }
    }
  }
  
  public void importCalendarByJob(String username,
                                  InputStream icalInputStream,
                                  String calendarId, String calendarName,
                                  java.util.Calendar from, java.util.Calendar to,
                                  boolean isNew) throws Exception {
    JobSchedulerServiceImpl  schedulerService = (JobSchedulerServiceImpl)ExoContainerContext.getCurrentContainer().
        getComponentInstance(JobSchedulerService.class) ;

    JobDetail job = findImportJob(schedulerService, calendarId);

    if(job == null) {
      byte[] icalInput = IOUtils.toByteArray(icalInputStream);
      job = ImportCalendarJob.getImportICSFileJobDetail(username, calendarId, calendarName, icalInput, from, to, isNew);
    }

    SimpleTriggerImpl trigger = new SimpleTriggerImpl();
    trigger.setName(calendarId);
    trigger.setGroup(ImportCalendarJob.IMPORT_CALENDAR_JOB_GROUP_NAME);
    trigger.setStartTime(new Date());

    schedulerService.addJob(job, trigger);  
  }

  public JobDetail findImportJob(JobSchedulerService schedulerService, String calendarId) throws Exception {
    List<JobDetail> listJobs = schedulerService.getAllJobs();
    for(JobDetail job : listJobs) {
      if(job.getKey().getName().equals(calendarId)) {
        return job;
      }
    }
    return null;
  }
}
