/*
 * Copyright (C) 2012 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.exoplatform.services.jcr.webdav;

import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ValueParam;
import org.exoplatform.container.xml.ValuesParam;
import org.exoplatform.services.jcr.webdav.util.InitParamsDefaults;
import org.exoplatform.services.jcr.webdav.util.InitParamsNames;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.impl.MultivaluedMapImpl;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Provides means to parse WebDAV service initial parameters and gain access to their values.
 * 
 * @author  Dmitry Kuleshov - dmi3.kuleshov@gmail.com
 * @version $Id$
 */
public class WebDavServiceInitParams
{
   /**
    * Logger.
    */
   private static Log log = ExoLogger.getLogger("exo.jcr.component.webdav.WebDavServiceInitParams");

   /**
    * Default folder node type.
    */
   private String defaultFolderNodeType = InitParamsDefaults.FOLDER_NODE_TYPE;

   /**
    * Default file node type.
    */
   private String defaultFileNodeType = InitParamsDefaults.FILE_NODE_TYPE;

   /**
    * Default file mime type.
    */
   private String defaultFileMimeType = InitParamsDefaults.FILE_MIME_TYPE;

   /**
    * Update policy.
    */
   private String defaultUpdatePolicyType = InitParamsDefaults.UPDATE_POLICY;

   /**
    * Auto-version default value.
    */
   private String defaultAutoVersionType = InitParamsDefaults.AUTO_VERSION;

   /**
    * XSLT parameters.
    */
   private Map<String, String> xsltParams = new HashMap<String, String>();

   /**
    * Set of untrusted user agents. Special rules are applied for listed agents.
    */
   private Set<String> untrustedUserAgents = new HashSet<String>();

   /**
    * Set of pattern untrusted user agents.
    * */
   private Set<Pattern>  untrustedUserAgentsPattern = new HashSet<Pattern>();

   /**
    * Set of allowed file node types.
    */
   private Set<String> allowedFileNodeTypes = new HashSet<String>();

   /**
    * Set of allowed folder node types.
    */
   private Set<String> allowedFolderNodeTypes = new HashSet<String>();

   /**
    * Cache control parameters.
    */
   private Map<MediaType, String> cacheControlMap = new HashMap<MediaType, String>();

   /**
    * enable auto versionning strategy (updatePolicyType : create-version , autoVersion : checkin-checkout).
    */

   private boolean enableAutoVersion =  false ;

   /**
    * folder listing paths allowed regex
    */

   private String folderListingAllowedRegex =  "" ;

   /**
    * Map of jcr (path , workspace) of allowed auto versioning.
    */
   private MultivaluedMap<String,String> allowedAutoVersionPath = new MultivaluedMapImpl();

   /**
    * Default constructor, all initial parameters take default values.
    * The list of default parameters values may be obtained from {@link InitParamsDefaults}.
    */
   public WebDavServiceInitParams()
   {
      allowedFileNodeTypes.add(InitParamsDefaults.FILE_NODE_TYPE);
      allowedFolderNodeTypes.add(InitParamsDefaults.FOLDER_NODE_TYPE);
   }

   /**
    * Create an instance of WebDAV service initial parameters from {@link InitParams} 
    * which are obtained from configuration file.
    * @param params initial parameters
    */
   public WebDavServiceInitParams(InitParams params)
   {
      this();
      Map<String, String> parametersMap = new HashMap<String, String>();

      Iterator<ValueParam> valueParamIterator = params.getValueParamIterator();
      ValueParam valueParam;
      while (valueParamIterator.hasNext())
      {
         valueParam = valueParamIterator.next();
         parametersMap.put(valueParam.getName(), valueParam.getValue());
      }

      Iterator<ValuesParam> valuesParamIterator = params.getValuesParamIterator();
      ValuesParam valuesParam;
      while (valuesParamIterator.hasNext())
      {
         valuesParam = valuesParamIterator.next();

         List<String> values = valuesParam.getValues();
         StringBuffer sb = new StringBuffer();
         for (int i = 0; i < values.size(); i++)
         {
            sb.append(values.get(i));
            sb.append(',');
         }

         parametersMap.put(valuesParam.getName(), sb.toString());
      }

      processParametersMap(parametersMap);
   }

   /**
    * Create an instance of WebDAV service initial parameters from parameters map.
    * Please note, that this constructor receives {@code Map<String, String>} instead of InitParams
    * and we cannot pass multi-valued parameters in the form of 
    * {@link String} {@literal ->} {@link java.util.Map}
    * To overcome this shortage we pass a set of parameter values as a single {@link String}
    * with each value separated by comma (",") i.e. "agent1, agent2, agent3"
    * @param params initial parameters
    */
   public WebDavServiceInitParams(Map<String, String> params)
   {
      this();
      processParametersMap(params);
   }

   private void processParametersMap(Map<String, String> parameters)
   {
      ParametersMapProcessor pmp = new ParametersMapProcessor(parameters);

      defaultFolderNodeType = pmp.processSingleParameter(defaultFolderNodeType, InitParamsNames.DEF_FOLDER_NODE_TYPE);
      defaultFileNodeType = pmp.processSingleParameter(defaultFileNodeType, InitParamsNames.DEF_FILE_NODE_TYPE);
      defaultFileMimeType = pmp.processSingleParameter(defaultFileMimeType, InitParamsNames.DEF_FILE_MIME_TYPE);
      defaultUpdatePolicyType = pmp.processSingleParameter(defaultUpdatePolicyType, InitParamsNames.UPDATE_POLICY);
      defaultAutoVersionType = pmp.processSingleParameter(defaultAutoVersionType, InitParamsNames.AUTO_VERSION);
      enableAutoVersion = pmp.processSingleParameter(enableAutoVersion, InitParamsNames.ENABLE_AUTO_VERSION);
      folderListingAllowedRegex = pmp.processSingleParameter(folderListingAllowedRegex, InitParamsNames.FOLDER_LISTING_PATHS_ALLOWED_REGEX);

      pmp.processMultiParameter(untrustedUserAgents, InitParamsNames.UNTRUSTED_USER_AGENTS);
      for (String pattern : untrustedUserAgents)
      {
         untrustedUserAgentsPattern.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
      }
      pmp.processMultiParameter(allowedFileNodeTypes, InitParamsNames.ALLOWED_FILE_NODE_TYPES);
      pmp.processMultiParameter(allowedFolderNodeTypes, InitParamsNames.ALLOWED_FOLDER_NODE_TYPES);
      pmp.processValueParameterToMap(allowedAutoVersionPath, InitParamsNames.ALLOWED_JCR_PATH_AUTO_VERSION);

      pmp.processSingleParameterToMap(xsltParams, InitParamsNames.FILE_ICON_PATH);
      pmp.processSingleParameterToMap(xsltParams, InitParamsNames.FOLDER_ICON_PATH);

      pmp.processCacheControlParameter();
   }



   public String getDefaultFolderNodeType()
   {
      return defaultFolderNodeType;
   }

   public String getDefaultFileNodeType()
   {
      return defaultFileNodeType;
   }

   public String getDefaultFileMimeType()
   {
      return defaultFileMimeType;
   }

   public String getDefaultUpdatePolicyType()
   {
      return defaultUpdatePolicyType;
   }

   public String getDefaultAutoVersionType()
   {
      return defaultAutoVersionType;
   }

   public Map<String, String> getXsltParams()
   {
      return xsltParams;
   }

   public Set<String> getUntrustedUserAgents()
   {
      return untrustedUserAgents;
   }

   public Set<Pattern> getUntrustedUserAgentsPattern()
   {
      return untrustedUserAgentsPattern;
   }

   public boolean isUntrustedUserAgent(String userAgent)
   {
      if (userAgent == null)
      {
         return false;
      }
      for (Pattern p : untrustedUserAgentsPattern)
      {
         if (p.matcher(userAgent).find())
         {
            return true;
         }
      }
      return false;
   }

   public Set<String> getAllowedFileNodeTypes()
   {
      return allowedFileNodeTypes;
   }

   public Set<String> getAllowedFolderNodeTypes()
   {
      return allowedFolderNodeTypes;
   }

   public Map<MediaType, String> getCacheControlMap()
   {
      return cacheControlMap;
   }

   public void setDefaultFolderNodeType(String defaultFolderNodeType)
   {
      this.defaultFolderNodeType = defaultFolderNodeType;
   }

   public void setDefaultFileNodeType(String defaultFileNodeType)
   {
      this.defaultFileNodeType = defaultFileNodeType;
   }

   public void setDefaultFileMimeType(String defaultFileMimeType)
   {
      this.defaultFileMimeType = defaultFileMimeType;
   }

   public void setDefaultUpdatePolicyType(String defaultUpdatePolicyType)
   {
      this.defaultUpdatePolicyType = defaultUpdatePolicyType;
   }

   public void setDefaultAutoVersionType(String defaultAutoVersionType)
   {
      this.defaultAutoVersionType = defaultAutoVersionType;
   }

   public void setXsltParams(Map<String, String> xsltParams)
   {
      this.xsltParams = xsltParams;
   }

   public void setUntrustedUserAgents(Set<String> untrustedUserAgents)
   {
      this.untrustedUserAgents = untrustedUserAgents;
   }

   public void setAllowedFileNodeTypes(Set<String> allowedFileNodeTypes)
   {
      this.allowedFileNodeTypes = allowedFileNodeTypes;
   }

   public void setAllowedFolderNodeTypes(Set<String> allowedFolderNodeTypes)
   {
      this.allowedFolderNodeTypes = allowedFolderNodeTypes;
   }

   public void setCacheControlMap(Map<MediaType, String> cacheControlMap)
   {
      this.cacheControlMap = cacheControlMap;
   }

   public void setAllowedAutoVersionPath(MultivaluedMap<String, String> allowedAutoVersionPath)
   {

      this.allowedAutoVersionPath = merge( this.allowedAutoVersionPath,allowedAutoVersionPath);
   }

   public MultivaluedMap<String, String> getAllowedAutoVersionPath()
   {
      return allowedAutoVersionPath;
   }

   public void setEnableAutoVersion(boolean enableAutoVersion)
   {
      this.enableAutoVersion = enableAutoVersion;
   }

   public boolean isEnableAutoVersion()
   {
      return enableAutoVersion;
   }

   public String getFolderListingAllowedRegex() {
      return folderListingAllowedRegex;
   }

   public void setFolderListingAllowedRegex(String folderListingAllowedRegex) {
      this.folderListingAllowedRegex = folderListingAllowedRegex;
   }

   private class ParametersMapProcessor
   {
      Map<String, String> parameters;

      public ParametersMapProcessor(Map<String, String> parameters)
      {
         this.parameters = parameters;
      }

      private void processSingleParameterToMap(Map<String, String> parameterMap, String parameterName)
      {
         String paramValue = parameters.get(parameterName);
         if (paramValue != null)
         {
            parameterMap.put(parameterName, paramValue);
            log.info(parameterName + " = " + paramValue);
         }
      }

      private void processValueParameterToMap(MultivaluedMap<String, String> parameterMap, String parameterName)
      {
         String paramValue = parameters.get(parameterName);
         if (paramValue != null)
         {
            String[] elements = paramValue.split(";");
            for (String element : elements)
            {
               String workspaceName = element.split(":")[0];
               String pathName = element.split(":")[1];
               parameterMap.add(workspaceName, pathName);

            }
         }
      }

      private void processCacheControlParameter()
      {
         String paramValue = parameters.get(InitParamsNames.CACHE_CONTROL);
         if (paramValue != null)
         {
            try
            {
               String[] elements = paramValue.split(";");
               for (String element : elements)
               {
                  String cacheValue = element.split(":")[1];
                  String keys = element.split(":")[0];
                  for (String key : keys.split(","))
                  {
                     MediaType mediaType = new MediaType(key.split("/")[0], key.split("/")[1]);
                     cacheControlMap.put(mediaType, cacheValue);
                  }
               }
            }
            catch (PatternSyntaxException e)
            {
               log.warn("Invalid " + InitParamsNames.CACHE_CONTROL + " parameter");
            }
            catch (UnsupportedOperationException e)
            {
               log.warn("Invalid " + InitParamsNames.CACHE_CONTROL + " parameter");
            }
            catch (ClassCastException e)
            {
               log.warn("Invalid " + InitParamsNames.CACHE_CONTROL + " parameter");
            }
            catch (IllegalArgumentException e)
            {
               log.warn("Invalid " + InitParamsNames.CACHE_CONTROL + " parameter");
            }
         }
      }

      private String processSingleParameter(String parameter, String parameterName)
      {
         String paramValue = parameters.get(parameterName);
         if (paramValue != null)
         {
            parameter = paramValue;
            log.info(parameterName + " = " + parameter);
         }
         return parameter;
      }

      private boolean processSingleParameter(boolean parameter, String parameterName)
      {
         String paramValue = parameters.get(parameterName);
         if (paramValue != null)
         {
            parameter = Boolean.getBoolean(paramValue);
            log.info(parameterName + " = " + parameter);
         }
         return parameter;
      }

      private void processMultiParameter(Set<String> valuesSet, String parameterName)
      {
         String parameterMultivalue = parameters.get(parameterName);
         if (parameterMultivalue != null)
         {
            valuesSet.clear();
            for (String value : parameterMultivalue.split(","))
            {
               valuesSet.add(value.trim());
               log.info(parameterName + " = " + value.trim());
            }
         }
      }
   }

   private MultivaluedMap<String, String> merge(MultivaluedMap<String, String> map1, MultivaluedMap<String, String> map2)
   {
      if (map1.isEmpty())
      {
         return map2;
      }
      else if (map2.isEmpty())
      {
         return map1;
      }
      else
      {
         for (String key : map2.keySet())
         {
            if (key != null)
            {
               List<String> list = map2.get(key);
               for (String value : list)
               {
                  map1.add(key, value);
               }
            }
         }
         return map1;
      }
   }
}
