/**
 * Copyright (C) 2010 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.xcmis.spi.utils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.xcmis.spi.ItemsIterator;
import org.xcmis.spi.ObjectData;
import org.xcmis.spi.model.AccessControlEntry;

/**
 * @author <a href="mailto:andrey.parfonov@exoplatform.com">Andrey Parfonov</a>
 * @version $Id: CmisUtils.java 332 2010-03-11 17:24:56Z andrew00x $
 */
public final class CmisUtils
{
   public static final Comparator<ObjectData> versionComparator = new Comparator<ObjectData>()
   {
      public int compare(ObjectData object1, ObjectData object2)
      {
         Calendar c1 = object1.getLastModificationDate();
         Calendar c2 = object2.getLastModificationDate();
         return c2.compareTo(c1);
      }

   };

   public static ItemsIterator<Object> EMPTY_ITEMS_ITERATOR = new EmptyItemsIterator();

   private static class EmptyItemsIterator implements ItemsIterator<Object>
   {

      public int size()
      {
         return 0;
      }

      public void skip(int skip) throws NoSuchElementException
      {
         throw new NoSuchElementException("skip");
      }

      public boolean hasNext()
      {
         return false;
      }

      public Object next()
      {
         throw new NoSuchElementException("next");
      }

      public void remove()
      {
         throw new UnsupportedOperationException();
      }

   }

   public static <T> ItemsIterator<T> emptyItemsIterator()
   {
      return (ItemsIterator<T>)EMPTY_ITEMS_ITERATOR;
   }

   public static List<AccessControlEntry> createAclFromPermissionMap(Map<String, Set<String>> permissions)
   {
      List<AccessControlEntry> acl = new ArrayList<AccessControlEntry>();
      if (permissions != null)
      {
         for (Map.Entry<String, Set<String>> e : permissions.entrySet())
         {
            AccessControlEntry ace = new AccessControlEntry(e.getKey(), e.getValue());
            acl.add(ace);
         }
      }
      return acl;
   }

   public static List<AccessControlEntry> mergeACLs(List<AccessControlEntry> existedAcl,
      List<AccessControlEntry> addAcl, List<AccessControlEntry> removeAcl)
   {
      Map<String, Set<String>> cache = new HashMap<String, Set<String>>();
      addAclToPermissionMap(cache, existedAcl);
      addAclToPermissionMap(cache, addAcl);
      removeAclFromPermissionMap(cache, removeAcl);
      return createAclFromPermissionMap(cache);
   }

   public static void addAclToPermissionMap(Map<String, Set<String>> map, List<AccessControlEntry> acl)
   {
      if (acl != null)
      {
         for (AccessControlEntry ace : acl)
         {
            String principal = ace.getPrincipal();
            if (principal == null)
            {
               continue;
            }

            Set<String> permissions = map.get(principal);
            if (permissions == null)
            {
               permissions = new HashSet<String>();
               map.put(principal, permissions);
            }
            permissions.addAll(ace.getPermissions());
         }
      }
   }

   public static void removeAclFromPermissionMap(Map<String, Set<String>> map, List<AccessControlEntry> acl)
   {
      if (acl != null)
      {
         for (AccessControlEntry ace : acl)
         {
            String principal = ace.getPrincipal();
            if (principal == null)
            {
               continue;
            }

            Set<String> permissions = map.get(principal);
            if (permissions != null)
            {
               permissions.removeAll(ace.getPermissions());
               if (permissions.size() == 0)
               {
                  map.remove(principal);
               }
            }
         }
      }
   }

   // ----- Dates ------

   /** The z-format pattern. */
   private static final Pattern Z_FORMAT =
      Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})[Tt](\\d{2}):(\\d{2}):(\\d{2})(\\.(\\d{1,3}))?[zZ]");

   /** The td-format pattern. */
   private static final Pattern TD_FORMAT =
      Pattern
         .compile("(\\d{4})-(\\d{2})-(\\d{2})[Tt](\\d{2}):(\\d{2}):(\\d{2})(\\.(\\d{1,3}))?([+-])((\\d{2}):(\\d{2}))");
   
   /** The ISO 8601 date format */
   public static final DateFormat ISO_8601_DATE_TIME = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ");

   /**
    * Get XMLGregorianCalendar that is based on Calendar.
    *
    * @param calendar source Calendar
    * @return XMLGregorianCalendar
    */
   public static XMLGregorianCalendar fromCalendar(Calendar calendar)
   {
      XMLGregorianCalendar xmlCalendar;
      try
      {
         xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar();
      }
      catch (DatatypeConfigurationException e)
      {
         String msg = "Unable get XMLGregorianCalendar.";
         throw new RuntimeException(msg, e);
      }
      xmlCalendar.setYear(calendar.get(Calendar.YEAR));
      xmlCalendar.setMonth(calendar.get(Calendar.MONTH) + 1);
      xmlCalendar.setDay(calendar.get(Calendar.DAY_OF_MONTH));
      int zoneOffsetInMinutes = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60*1000);
      xmlCalendar.setTimezone(zoneOffsetInMinutes);
      xmlCalendar.setTime(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar
         .get(Calendar.SECOND), calendar.get(Calendar.MILLISECOND));
      return xmlCalendar;
   }

   /**
    * Convert calendar to ISO8601 string representation.
    *
    * @param c the Calendar
    * @return the ISO8601 string
    */
   public static String convertToString(Calendar c)
   {
      // Can't use SimpleDateFormat here, because can't setTimeZone for it with ZONE_OFFSET in int.
      int zoneOffsetInMinutes = (c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET)) / (60*1000);
      String signOffset = zoneOffsetInMinutes < 0 ? "-" : "+";
      zoneOffsetInMinutes = zoneOffsetInMinutes < 0 ? -zoneOffsetInMinutes : zoneOffsetInMinutes;
      int hoursOffset = zoneOffsetInMinutes / 60;
      int minutesOffset = zoneOffsetInMinutes % 60;
      String result =  String.format("%04d-%02d-%02dT%02d:%02d:%02d.%03d" + signOffset + "%02d:%02d", c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, 
            c.get(Calendar.DAY_OF_MONTH), c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND), 
            c.get(Calendar.MILLISECOND), hoursOffset, minutesOffset);
      
      return result;
   }

   /**
    * Parses the calendar.
    *
    * @param date the date
    * @return the calendar
    */
   public static Calendar parseCalendar(String date)
   {
      Matcher m = Z_FORMAT.matcher(date);
      if (m.matches())
      {
         Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
         c.set(Calendar.YEAR, Integer.parseInt(m.group(1)));
         c.set(Calendar.MONTH, Integer.parseInt(m.group(2)) - 1);
         c.set(Calendar.DATE, Integer.parseInt(m.group(3)));
         c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(m.group(4)));
         c.set(Calendar.MINUTE, Integer.parseInt(m.group(5)));
         c.set(Calendar.SECOND, Integer.parseInt(m.group(6)));
         c.set(Calendar.MILLISECOND, m.group(7) == null ? 0 : Integer.parseInt(m.group(8)));
         return c;
      }
      else
      {
         m = TD_FORMAT.matcher(date);
         if (m.matches())
         {
            int t = m.group(9).equals("+") ? 1 : -1;
            Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            c.set(Calendar.YEAR, Integer.parseInt(m.group(1)));
            c.set(Calendar.MONTH, Integer.parseInt(m.group(2)) - 1);
            c.set(Calendar.DATE, Integer.parseInt(m.group(3)));
            c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(m.group(4)));
            c.set(Calendar.MINUTE, Integer.parseInt(m.group(5)));
            c.set(Calendar.SECOND, Integer.parseInt(m.group(6)));
            c.set(Calendar.MILLISECOND, m.group(7) == null ? 0 : Integer.parseInt(m.group(8)));
            int zoneOffset = t * (Integer.parseInt(m.group(11))*60*60*1000 + Integer.parseInt(m.group(12))*60*1000);
			   c.set(Calendar.ZONE_OFFSET, zoneOffset);
            return c;
         }
         else
         {
            throw new IllegalArgumentException("Unsupported date format " + date);
         }
      }
   }

   /**
    * Parses the xml calendar.
    *
    * @param date the date
    * @return the xML gregorian calendar
    */
   public static XMLGregorianCalendar parseXMLCalendar(String date)
   {
      return fromCalendar(parseCalendar(date));
   }

   /**
    * Not instantiable.
    */
   private CmisUtils()
   {
   }

}
