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

import org.xcmis.spi.model.Rendition;
import org.xcmis.spi.utils.MimeType;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * @author <a href="mailto:andrey.parfonov@exoplatform.com">Andrey Parfonov</a>
 * @version $Id: RenditionFilter.java 265 2010-03-04 11:10:52Z andrew00x $
 */
public class RenditionFilter
{

   /** None filter to include all renditions. */
   public static final String ANY = CmisConstants.WILDCARD;

   /** None filter to exclude all renditions. */
   public static final String NONE = "cmis:none";

   /** None filter to accept only renditions with 'cmis:thumbnail' kind. */
   public static final String THUMBNAIL = "cmis:thumbnail";

   /** Filter instance with 'cmis:thumbnail' kind. */
   public static final RenditionFilter THUMBNAIL_FILTER;

   /** Filter instance with all renditions included. */
   public static final RenditionFilter ANY_FILTER;

   /** Filter instance with none renditions included. */
   public static final RenditionFilter NONE_FILTER;

   static
   {
      THUMBNAIL_FILTER = new RenditionFilter();
      Set<String> tmp = new HashSet<String>();
      tmp.add(THUMBNAIL);
      THUMBNAIL_FILTER.kinds = Collections.unmodifiableSet(tmp);
      THUMBNAIL_FILTER.mediaTypes = Collections.emptySet();
      ANY_FILTER = new RenditionFilter();
      ANY_FILTER.anyRenditions = true;
      NONE_FILTER = new RenditionFilter();
      NONE_FILTER.kinds = Collections.emptySet();
      NONE_FILTER.mediaTypes = Collections.emptySet();
   }

   /** Pattern for parsing source filter string. */
   private static final Pattern SPLITTER = Pattern.compile("\\s*,\\s*");

   /** Whether any renditions. */
   private boolean anyRenditions = false;

   /** Kinds. */
   private Set<String> kinds;

   /** Media types. */
   private Set<MimeType> mediaTypes;

   /**
    * Construct new Rendition Filter.
    * 
    * @param filterString string that contains either '*' or comma-separated
    *        list of rendition's kind or mime-types. An arbitrary number of
    *        space allowed before and after each comma. Each token will be
    *        interpreted as rendition mime-type if it has form 'type/sub-type'
    *        otherwise it will be interpreted as kind.
    * @throws FilterNotValidException if <code>filterString</code> is invalid
    */
   public RenditionFilter(String filterString) throws FilterNotValidException
   {
      if (filterString == null || filterString.length() == 0 || NONE.equals(filterString.trim()))
      {
         // Filter will exclude all associated renditions.
         this.kinds = Collections.emptySet();
         this.mediaTypes = Collections.emptySet();
      }
      else if (ANY.equals(filterString.trim()))
      {
         this.anyRenditions = true;
      }
      else
      {
         filterString = filterString.trim();
         this.kinds = new HashSet<String>();
         this.mediaTypes = new HashSet<MimeType>();
         for (String token : SPLITTER.split(filterString))
         {
            if (token.length() > 0 && !token.equals(ANY))
            {
               for (char ch : token.toCharArray())
               {
                  if (Character.isWhitespace(ch))
                  {
                     String msg = "Invalid filter \"" + filterString + "\". Filter's tokens must not have whitespace.";
                     throw new FilterNotValidException(msg);
                  }
               }
               if (token.indexOf('/') > 0)
                  this.mediaTypes.add(MimeType.fromString(token));
               else
                  this.kinds.add(token);
            }
            else
            {
               // String contains empty token or some tokens and special token '*'
               String msg =
                  "Invalid filter \"" + filterString
                     + "\". Filter must contains either '*' OR comma-separated list mime-types or kinds of renditions.";
               throw new FilterNotValidException(msg);
            }
         }
      }
   }

   protected RenditionFilter()
   {
   }

   /**
    * {@inheritDoc}
    */
   public boolean accept(Rendition rendition)
   {
      if (anyRenditions)
         return true;
      if (kinds.contains(rendition.getKind()))
         return true;
      MimeType toCheck = MimeType.fromString(rendition.getMimeType());
      for (MimeType mediaType : mediaTypes)
      {
         if (mediaType.match(toCheck))
            return true;
      }
      return false;
   }

   // TODO : do smarter with filter. Temporary just avoid to do unnecessary work and skip results. 
   public boolean isNone()
   {
      return !anyRenditions && mediaTypes.size() == 0 && kinds.size() == 0;
   }

}
