package org.jboss.resteasy.plugins.touri;

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;

import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
import org.jboss.resteasy.spi.ResteasyUriBuilder;
import org.jboss.resteasy.spi.touri.URIResolver;
import org.jboss.resteasy.util.AnnotationResolver;

public abstract class AbstractURITemplateAnnotationResolver implements
        URIResolver {

    @SuppressWarnings("rawtypes")
    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());
        ResteasyUriBuilder 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 ResteasyUriBuilder 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(Messages.MESSAGES.couldNotFindGetterForParam(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(Messages.MESSAGES.couldNotGetAValue(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(Messages.MESSAGES.couldNotIntrospectClass(clazz.getName()), e);
        }
    }
}
