/**
 * 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;

import java.util.ArrayList;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;

import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.commons.utils.ISO8601;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.component.ComponentRequestLifecycle;
import org.exoplatform.job.MultiTenancyJob;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.impl.core.query.lucene.IndexOfflineRepositoryException;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.ws.frameworks.cometd.ContinuationService;
import org.quartz.JobExecutionContext;


public class PopupReminderJob extends MultiTenancyJob {
  private static Log log_ = ExoLogger.getLogger(PopupReminderJob.class);

  @Override
  public Class<? extends MultiTenancyTask> getTask() {
    return PopupReminderTask.class;
  }
  
  public class PopupReminderTask extends MultiTenancyTask{

    public PopupReminderTask(JobExecutionContext context, String repoName) {
      super(context, repoName);
    }

    @Override
    public void run() {
      super.run();
      SessionProvider provider = SessionProvider.createSystemProvider();
      OrganizationService orgService = container.getComponentInstanceOfType(OrganizationService.class);
      //We have JobEnvironmentConfigListener call request lifecycle methods
      //But it's run in difference thread that create bug with PicketlinkIDM using hibernate session (CAL-1031)
      if (orgService instanceof ComponentRequestLifecycle) {
        ((ComponentRequestLifecycle)orgService).startRequest(ExoContainerContext.getCurrentContainer());          
      }
      
      try {
        if (log_.isDebugEnabled())
          log_.debug("Calendar popup reminder service");
        java.util.Calendar fromCalendar = Utils.getInstanceTempCalendar();
        ContinuationService continuation = (ContinuationService) container.getComponentInstanceOfType(ContinuationService.class);
        Node calendarHome = Utils.getPublicServiceHome(provider);
        if (calendarHome == null)
          return;
        StringBuffer path = new StringBuffer(getReminderPath(fromCalendar, provider));
        path.append("//element(*,exo:reminder)");
        path.append("[@exo:remindDateTime <= xs:dateTime('" + ISO8601.format(fromCalendar) + "') and @exo:isOver = 'false' and @exo:reminderType = 'popup' ]");
        QueryManager queryManager = Utils.getSession(provider).getWorkspace().getQueryManager();
        Query query = queryManager.createQuery(path.toString(), Query.XPATH);
        QueryResult results = query.execute();
        NodeIterator iter = results.getNodes();
        Node reminder;
        List<Reminder> popupReminders = new ArrayList<Reminder>();
       
        while (iter.hasNext()) {
          reminder = iter.nextNode();
          boolean isRepeat = reminder.getProperty(Utils.EXO_IS_REPEAT).getBoolean();
          long fromTime = reminder.getProperty(Utils.EXO_FROM_DATE_TIME).getDate().getTimeInMillis();
          long remindTime = reminder.getProperty(Utils.EXO_REMINDER_DATE).getDate().getTimeInMillis();
          long interval = reminder.getProperty(Utils.EXO_TIME_INTERVAL).getLong() * 60 * 1000;
          
          Reminder rmdObj = new Reminder();
          rmdObj.setRepeate(isRepeat);
          rmdObj.setReminderOwner(reminder.getProperty(Utils.EXO_OWNER).getString());
          rmdObj.setId(reminder.getProperty(Utils.EXO_EVENT_ID).getString());
          
          if(isRepeat) {
            long currentTime1 = java.util.Calendar.getInstance().getTimeInMillis();
            long nextRemindTime = getNextRemindTime(remindTime, currentTime1, interval);
            // if it's time to send reminder, add the rmdObj to list of popup reminders
            if(nextRemindTime > 0) {
              popupReminders.add(rmdObj);
              // if the next reminder time is greater than event from time, the reminder is over (exo:isOver = true)
              if(nextRemindTime > fromTime) { 
                reminder.setProperty(Utils.EXO_IS_OVER, true);
              } else {
                // the reminder is continued, set new time of reminder
                reminder.setProperty(Utils.EXO_IS_OVER, false);
                reminder.setProperty(Utils.EXO_REMINDER_DATE, nextRemindTime);
              }
            }
          } else {
            long currentTime2 = java.util.Calendar.getInstance().getTimeInMillis();
            if(isTimeToRemind(remindTime, currentTime2)) {
              popupReminders.add(rmdObj);
              reminder.setProperty(Utils.EXO_IS_OVER, true);
            }
          }
          reminder.save();
        }
        if (!popupReminders.isEmpty()) {
          for (Reminder rmdObj : popupReminders) {
            for (String user : rmdObj.getReminderOwner().split(Utils.COMMA)) {
              if (CommonsUtils.isUserEnabled(user)) {
                continuation.sendMessage(user, "/eXo/Application/Calendar/messages", rmdObj.getId());
              }  
            }
          }
        }
      } catch (IndexOfflineRepositoryException e) {
        if (log_.isTraceEnabled()) {
          log_.trace("An Error occurred while running Calendar PopupReminderJob: " + e.getMessage(),e);
        }
      } catch (Exception e) {
        log_.error(e.getMessage(), e);
      } finally {
        if (orgService instanceof ComponentRequestLifecycle) {
          ((ComponentRequestLifecycle)orgService).endRequest(ExoContainerContext.getCurrentContainer());          
        }
        provider.close();
      }
      if (log_.isDebugEnabled())
        log_.debug("File plan job done");
    }
  }

  /*
   * Gets next reminder time based on current reminder time, current time and the interval.
   * Current time is time to send the reminder if it is greater than reminder time and 
   * (current time - reminder time) % interval <= delta 
   * (here we choose delta = 15 seconds, it's equals the period between jobs
   * If current time is time to send the reminder, the method returns next reminder time
   * otherwise, it return -1
   */
  private long getNextRemindTime(long remindTime, long currentTime, long interval) {
    long delta = 15000; 
    long diff = currentTime - remindTime;
    long remaining =  diff % interval;
    if(remaining <= delta && currentTime >= remindTime) {
      // because the user can choose start time of reminder is very long before the from time of event, (refer to CAL-422)
      // here we must get the most recent reminder time before adding the interval to avoid sending many unexpected reminders
      return currentTime - remaining + interval; 
    } else {
      return -1;
    }
  }
  
  /*
   * Checks if current time is time to send the reminder, in case the reminder is not repeated (no interval)
   */
  private Boolean isTimeToRemind(long remindTime, long currentTime) {
    long delta = 15000;
    long diff = currentTime - remindTime;
    return diff <= delta && currentTime >= remindTime;
  }
  
  public static String getReminderPath(java.util.Calendar fromCalendar, SessionProvider provider) throws Exception {
    String year = "Y" + String.valueOf(fromCalendar.get(java.util.Calendar.YEAR));
    String month = "M" + String.valueOf(fromCalendar.get(java.util.Calendar.MONTH) + 1);
    String day = "D" + String.valueOf(fromCalendar.get(java.util.Calendar.DATE));
    StringBuffer path = new StringBuffer("/jcr:root");
    path.append(Utils.getPublicServiceHome(provider).getPath());
    path.append(Utils.SLASH).append(year).append(Utils.SLASH).append(month).append(Utils.SLASH).append(day);
    path.append(Utils.SLASH).append(Utils.CALENDAR_REMINDER);
    return path.toString();
  }
}
