package org.jboss.resteasy.plugins.providers;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;

import javax.ws.rs.CookieParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.Separator;
import org.jboss.resteasy.core.ResteasyContext;
import org.jboss.resteasy.core.StringParameterInjector;
import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.util.Types;

/**
 * @author Marek Kopecky mkopecky@redhat.com
 * @author Ron Sigal rsigal@redhat.com
 */
@Provider
public class MultiValuedParamConverterProvider implements ParamConverterProvider
{
   private static final Pattern PROXY_REGEX = Pattern.compile("\\p{Punct}|\\[\\p{Punct}+\\]");

   @SuppressWarnings("unchecked")
   @Override
   public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations)
   {
      if (!isApplicable(annotations))
      {
         return null;
      }
      String separator = getSeparator(annotations);
      Class<?> paramType = getHeaderParam(annotations);
      if (Collection.class.isAssignableFrom(rawType))
      {
         if (!PROXY_REGEX.matcher(separator).matches())
         {
            LogMessages.LOGGER.invalidRegex(rawType.getName(), separator);
            return null;
         }
         Class<?> type = null;
         try
         {
            type = Types.getTypeArgument(genericType);
            if (type == null)
            {
               return null;
            }
         }
         catch (Exception e)
         {
            return null;
         }
         Constructor<?> constructor = getConstructor(rawType);
         if (constructor == null)
         {
            return null;
         }
         ResteasyProviderFactory factory = ResteasyContext.getContextData(ResteasyProviderFactory.class);
         StringParameterInjector stringParameterInjector = new StringParameterInjector(type, null, null, paramType, null, null, annotations, factory);
         return (ParamConverter<T>) new MultiValuedCollectionParamConverter(stringParameterInjector, separator, constructor);
      }
      else if (rawType.isArray())
      {
         Class<?> type = rawType.getComponentType();
         if (type.isArray() || Collection.class.isAssignableFrom(type))
         {
            return null;
         }
         ResteasyProviderFactory factory = ResteasyContext.getContextData(ResteasyProviderFactory.class);
         StringParameterInjector stringParameterInjector = new StringParameterInjector(type, null, null, paramType, null, null, annotations, factory);
         return (ParamConverter<T>) new MultiValuedArrayParamConverter(stringParameterInjector, separator, rawType);
      }
      return null;
   }

   //////////////////////////////////////////////////////////////////////////
   private boolean isApplicable(Annotation[] annotations)
   {
      if (annotations == null)
      {
         return false;
      }
      for (Annotation a : annotations)
      {
         if (a instanceof Separator)
         {
            return true;
         }
      }
      return false;
   }

   private Constructor<?> getConstructor(Class<?> clazz)
   {
      try
      {
         if (List.class.equals(clazz) || ArrayList.class.equals(clazz))
         {
            return ArrayList.class.getConstructor();
         }
         else if (SortedSet.class.equals(clazz) || TreeSet.class.equals(clazz))
         {
            return TreeSet.class.getConstructor();
         }
         else if (Set.class.equals(clazz) || HashSet.class.equals(clazz))
         {
            return HashSet.class.getConstructor();
         }
         else
         {
            return null;
         }
      }
      catch (NoSuchMethodException e)
      {
         return null;
      }
   }

   private String getSeparator(Annotation[] annotations)
   {
      for (Annotation a : annotations)
      {
         if (a instanceof Separator)
         {
            if ("".equals(((Separator) a).value()))
            {
               break;
            }
            return ((Separator) a).value();
         }
      }

      for (Annotation a : annotations)
      {
         if (a instanceof CookieParam)
         {
            return "-";
         }
      }
      return ",";
   }

   private Class<?> getHeaderParam(Annotation[] annotations)
   {
      if (annotations == null)
      {
         return null;
      }
      for (Annotation a : annotations)
      {
         if (a instanceof HeaderParam)
         {
            return HeaderParam.class;
         }
      }
      return null;
   }
}
