/**
 * Copyright (C) 2003-2008 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.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.jcr.ItemExistsException;

import org.exoplatform.calendar.service.CalendarEvent;
import org.exoplatform.calendar.service.CalendarImportExport;
import org.exoplatform.calendar.service.CalendarService;
import org.exoplatform.calendar.service.EventCategory;
import org.exoplatform.calendar.service.Utils;
import org.exoplatform.calendar.util.Constants;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

/**
 * Created by The eXo Platform SAS
 * Author : Pham Tuan
 *          tuan.pham@exoplatform.com
 * Apr 1, 2008  
 */
public class CsvImportExport implements CalendarImportExport {
  public static final String                    CSV_PATTERN         = "\"([^\"]+?)\",?|([^,]+),?|,";

  private static Pattern                        csvRE;

  public static String                          EV_SUMMARY          = "Subject".intern();

  public static String                          EV_STARTDATE        = "Start Date".intern();

  public static String                          EV_STARTTIME        = "Start Time".intern();

  public static String                          EV_ENDDATE          = "End Date".intern();

  public static String                          EV_ENDTIME          = "End Time".intern();

  public static String                          EV_ALLDAY           = "All day event".intern();

  public static String                          EV_HASREMINDER      = "Reminder on/off".intern();

  public static String                          EV_REMINDERDATE     = "Reminder Date".intern();

  public static String                          EV_REMINDERTIME     = "Reminder Time".intern();

  public static String                          EV_MEETINGORGANIZER = "Meeting Organizer".intern();

  public static String                          EV_ATTENDEES        = "Required Attendees".intern();

  public static String                          EV_INVITATION       = "Optional Attendees".intern();

  public static String                          EV_ATTACTMENT       = "Meeting Resources".intern();

  public static String                          EV_BILLINGINFO      = "Billing Information".intern();

  public static String                          EV_CATEGORIES       = "Categories".intern();

  public static String                          EV_DESCRIPTION      = "Description".intern();

  public static String                          EV_LOCATION         = "Location".intern();

  public static String                          EV_MILEAGE          = "Mileage".intern();

  public static String                          EV_PRIORITY         = "Priority".intern();

  public static String                          EV_PRIVATE          = "Private".intern();

  public static String                          EV_SENSITIVITY      = "Sensitivity".intern();

  public static String                          EV_STATUS           = "Show time as".intern();

  private static final String                   PRIVATE_TYPE        = "0".intern();

  private JCRDataStorage                        storage_;

  private static final Log                      logger              = ExoLogger.getLogger(CsvImportExport.class);
  
  private List<String> headers;
  
  /** Construct a regex-based CSV parser. */

  public CsvImportExport(JCRDataStorage dataStore) {
    csvRE = Pattern.compile(CSV_PATTERN);
    storage_ = dataStore;
  }

  /** Process one file. Delegates to parse() a line at a time */
  public List<CalendarEvent> process(BufferedReader in) throws Exception {
    String line;
    // For each line...
    int lineCount = 0;
    List<CalendarEvent> eventList = new ArrayList<CalendarEvent>();

    while ((line = in.readLine()) != null) {
      String tempLine = line;
      if (!line.endsWith("\""))
        line = tempLine + in.readLine();
      if(lineCount == 0) { // the first line, parse the line to have the headers
        headers = parse(line);
      } else {
          CalendarEvent eventObj = getEventFromLine(line);
          if(eventObj != null) {
            eventList.add(eventObj);
          }  
      }
      lineCount++;
    }
    return eventList;
  }

  // get event from a line
  private CalendarEvent getEventFromLine(String line) {
    CalendarEvent eventObj = new CalendarEvent();
    
    eventObj.setEventType(CalendarEvent.TYPE_EVENT);
    eventObj.setCalType(PRIVATE_TYPE);
    
    List<String> values = parse(line);
    Date fromDate = getFromDate(values);
    Date toDate = getToDate(values);
    
    if(fromDate != null && toDate != null) {
      eventObj.setFromDateTime(getFromDate(values));
      eventObj.setToDateTime(getToDate(values));
      
      for(int i = 0; i < values.size(); i++) {
        String key = headers.get(i);
        if(EV_SUMMARY.equals(key)) {
          eventObj.setSummary(values.get(i));
        } else if(EV_LOCATION.equals(key)) {
          eventObj.setLocation(values.get(i));
        } else if(EV_DESCRIPTION.equals(key)) {
          eventObj.setDescription(values.get(i));
        } else if(EV_STATUS.equals(key)) {
          int st = Integer.valueOf(values.get(i));
          if (st == 0 || st == 1) {
            eventObj.setStatus(CalendarEvent.ST_AVAILABLE);
          } else if (st == 2) {
            eventObj.setStatus(CalendarEvent.ST_BUSY);
          } else if (st == 3) {
            eventObj.setStatus(CalendarEvent.ST_OUTSIDE);
          }
        } else if(EV_PRIORITY.equals(key)) {
          for (int j = 0; j < CalendarEvent.PRIORITY.length; j++) {
            if (CalendarEvent.PRIORITY[i].equalsIgnoreCase(values.get(i).toLowerCase())) {
              eventObj.setPriority(String.valueOf(j));
              break;
            }
          }
        } else if(EV_CATEGORIES.equals(key)) {
          eventObj.setEventCategoryName(values.get(i));
        } else if(EV_ATTENDEES.equals(key)) {
          if(values.get(i) != null) {
            eventObj.setParticipant(values.get(i).split(";"));
          }
        } else if(EV_INVITATION.equals(key)) {
          if(values.get(i) != null) {
            eventObj.setInvitation(values.get(i).split(";"));
          }
        }
      }
      return eventObj;
    } else {
      return null;
    }
  }
  /** Parse one line.
   * @return List of Strings, minus their double quotes
   */
  public List<String> parse(String line) {
    List<String> list = new ArrayList<String>();
    Matcher m = csvRE.matcher(line);
    // For each field
    while (m.find()) {
      String match = m.group();
      if (match == null )
        break;
      if(match.equals(",")) {
        match = null;
      } else {
        if (match.endsWith(",")) { 
          match = match.substring(0, match.length() - 1);
        }
        if (match.startsWith("\"")) {
          match = match.substring(1);
        }
        if(match.endsWith("\"")) {
          match = match.substring(0, match.length() - 1);
        }
      }
      list.add(match);
    }
    return list;
  }

  public OutputStream exportCalendar(String username, List<String> calendarIds, String type) throws Exception {
    return null;
  }

  public List<CalendarEvent> getEventObjects(InputStream inputStream) throws Exception {
    return null;
  }

  public void importCalendar(String username, InputStream inputStream, String calendarId, String calendarName, java.util.Calendar from, java.util.Calendar to, boolean isNew) throws Exception {
    List<CalendarEvent> data = process(new BufferedReader(new InputStreamReader(inputStream)));
    if (data == null || data.size() < 1)
      return;

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

    if (isNew) {
      org.exoplatform.calendar.service.Calendar exoCalendar = new org.exoplatform.calendar.service.Calendar();
      exoCalendar.setName(calendarName);
      exoCalendar.setCalendarColor(Constants.COLORS[0]);
      exoCalendar.setDescription(Utils.EMPTY_STR);
      exoCalendar.setPublic(true);
      exoCalendar.setCalendarOwner(username);
      storage_.saveUserCalendar(username, exoCalendar, true);
      calendarId = exoCalendar.getId();
    }

    for (CalendarEvent exoEvent : data) {
      if (!Utils.isEmpty(exoEvent.getEventCategoryName())) {
        EventCategory evCate = new EventCategory();
        evCate.setName(exoEvent.getEventCategoryName());
        try {
          calService.saveEventCategory(username, evCate, true);
        } catch (ItemExistsException e) {
          evCate = calService.getEventCategoryByName(username, evCate.getName());
        } catch (Exception e) {
          if (logger.isDebugEnabled()) {
            logger.debug("Exception occurs when saving new event category '" + evCate.getName() + "' for event: " + exoEvent.getId(), e);
          }
        }
        exoEvent.setEventCategoryId(evCate.getId());
        exoEvent.setEventCategoryName(evCate.getName());
      }
      exoEvent.setCalendarId(calendarId);
      storage_.saveUserEvent(username, calendarId, exoEvent, true);
    }
  }

  public boolean isValidate(InputStream icalInputStream) throws Exception {
    try {
      List<CalendarEvent> eventObjs = process(new BufferedReader(new InputStreamReader(icalInputStream)));
      return eventObjs.size() > 0;
    } catch (Exception e) {
      if (logger.isDebugEnabled()) {
        logger.debug("The inputStream is not valid", e);
      }
      return false;
    }
  }

  public OutputStream exportCalendar(String username, List<String> calendarIds, String type, int number) throws Exception {
    return null;
  }

  public OutputStream exportEventCalendar(String username, String calendarId, String type, String eventId) throws Exception {
    return null;
    // not implemented yet, export for CSV file

  }

  @Override
  public ByteArrayOutputStream exportEventCalendar(CalendarEvent event) throws Exception {
    return null;
  }
  
  /*
   * gets from date from values returned by method parse() above
   * if the header does not have info about start time -> the event will be processed as an all day event
   */
  private Date getFromDate(List<String> values) {
    DateFormat df = new SimpleDateFormat("MM/dd/yy hh:mm:ss a", Locale.ENGLISH);
    Calendar cal = Calendar.getInstance();
    Date date = null;
    try {
      String dateStr = getValue(values,EV_STARTDATE);
      String allDay = getValue(values, EV_ALLDAY);
      if("True".equals(allDay)) {
        date = df.parse(dateStr + "0:00:00 AM"); 
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
      }
      date = df.parse(getValue(values,EV_STARTDATE) + " " + getValue(values, EV_STARTTIME));
      return date;
    } catch (ParseException e) {
      if(logger.isDebugEnabled()) {
        logger.debug("can't parse the date",e);
      }
      return null;
    }
  }
  /*
   * gets to date of event from values returned by method parse() above
   */
  private Date getToDate(List<String> values) {
    DateFormat df = new SimpleDateFormat("MM/dd/yy hh:mm:ss a", Locale.ENGLISH);
    Calendar cal = Calendar.getInstance();
    Date date;
    try {
      String dateStr = getValue(values, EV_ENDDATE);
      String allDay = getValue(values, EV_ALLDAY);
      
      if(dateStr == null) {
        return getFromDate(values);
      }
      if("True".equals(allDay)) {
        date = df.parse(dateStr + "23:59:59 PM");
        cal.set(Calendar.MILLISECOND, 999);
        return cal.getTime();
      }
      date = df.parse(getValue(values,EV_ENDDATE) + " " + getValue(values,EV_ENDTIME));
      return date;
    } catch (ParseException e) {
      if(logger.isDebugEnabled()) {
        logger.debug("can't parse the date",e);
      }
      return null;
    }
  }
  /*
   * gets value of a field from list of values
   * the index of the value in the list is equals index of the field in the header
   */
  private String getValue(List<String> values, String field) {
    int i = headers.indexOf(field);
    if(i > -1) {
      return values.get(i);
    } 
    return null;
  }
}
