package org.jboss.resteasy.spi.touri;

import org.jboss.resteasy.specimpl.UriBuilderImpl;
import org.jboss.resteasy.util.AnnotationResolver;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractURITemplateAnnotationResolver implements
        URIResolver
{

   @SuppressWarnings("unchecked")
   public boolean handles(Class type)
   {
      return AnnotationResolver.getClassWithAnnotation(type, getAnnotationType()) != null;
   }

   @SuppressWarnings("unchecked")
   public String resolveURI(Object object)
   {
      Class<? extends Object> clazz = AnnotationResolver
              .getClassWithAnnotation(object.getClass(), getAnnotationType());
      UriBuilderImpl uriBuilderImpl = getUriBuilder(clazz);
      Map<String, PropertyDescriptor> descriptors = getPropertyDescriptors(clazz);
      List<Object> values = getValues(object, descriptors, uriBuilderImpl
              .getPathParamNamesInDeclarationOrder());
      return uriBuilderImpl.build(values.toArray()).toString();
   }

   protected abstract Class<? extends Annotation> getAnnotationType();

   protected abstract UriBuilderImpl getUriBuilder(Class<? extends Object> clazz);

   private List<Object> getValues(Object object,
                                  Map<String, PropertyDescriptor> descriptors, List<String> params)
   {
      List<Object> values = new ArrayList<Object>();
      for (String param : params)
      {
         PropertyDescriptor propertyDescriptor = descriptors.get(param);
         if (propertyDescriptor == null)
         {
            throw new RuntimeException(
                    "URITemplateAnnotationResolver could not find a getter for param "
                            + param);
         }

         Method readMethod = propertyDescriptor.getReadMethod();
         if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))
         {
            readMethod.setAccessible(true);
         }

         try
         {
            values.add(readMethod.invoke(object, new Object[0]));
         }
         catch (Exception e)
         {
            throw new RuntimeException(
                    "URITemplateAnnotationResolver could not get a value for "
                            + param, e);
         }
      }
      return values;
   }

   private Map<String, PropertyDescriptor> getPropertyDescriptors(
           Class<? extends Object> clazz)
   {
      try
      {
         BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
         HashMap<String, PropertyDescriptor> results = new HashMap<String, PropertyDescriptor>();
         PropertyDescriptor[] propertyDescriptors = beanInfo
                 .getPropertyDescriptors();
         for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
         {
            results.put(propertyDescriptor.getName(), propertyDescriptor);
         }
         return results;
      }
      catch (IntrospectionException e)
      {
         throw new RuntimeException(
                 "URITemplateAnnotationResolver could not introspect class "
                         + clazz.getName(), e);
      }
   }
}
