package org.wildfly.swarm.cdi.jaxrsapi.deployment;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;

import javax.ws.rs.Path;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.client.jaxrs.ProxyConfig;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.jboss.resteasy.client.jaxrs.i18n.Messages;
import org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker;
import org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy;
import org.jboss.resteasy.client.jaxrs.internal.proxy.MethodInvoker;
import org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy;
import org.jboss.resteasy.client.jaxrs.internal.proxy.SubResourceInvoker;
import org.jboss.resteasy.util.IsHttpMethod;

/**
 * Borrowed from RESTEasy for initial work
 */
public class ProxyBuilder<T> {
    private static final Class<?>[] cClassArgArray = {Class.class};

    private final Class<T> iface;

    private final ResteasyWebTarget webTarget;

    private ClassLoader loader = Thread.currentThread().getContextClassLoader();

    private MediaType serverConsumes;

    private MediaType serverProduces;

    public static <T> ProxyBuilder<T> builder(Class<T> iface, WebTarget webTarget) {
        return new ProxyBuilder<T>(iface, (ResteasyWebTarget) webTarget);
    }

    @SuppressWarnings("unchecked")
    public static <T> T proxy(final Class<T> iface, WebTarget base, final ProxyConfig config) {
        if (iface.isAnnotationPresent(Path.class)) {
            Path path = iface.getAnnotation(Path.class);
            if (!path.value().equals("") && !path.value().equals("/")) {
                base = base.path(path.value());
            }
        }
        HashMap<Method, MethodInvoker> methodMap = new HashMap<Method, MethodInvoker>();
        for (Method method : iface.getMethods()) {
            // ignore the as method to allow declaration in client interfaces
            if ("as".equals(method.getName()) && Arrays.equals(method.getParameterTypes(), cClassArgArray)) {
                continue;
            }

            // Added by Ken Finnigan
            // Ignore default methods
            if (method.isDefault()) {
                continue;
            }

            MethodInvoker invoker;
            Set<String> httpMethods = IsHttpMethod.getHttpMethods(method);
            if ((httpMethods == null || httpMethods.size() == 0) && method.isAnnotationPresent(Path.class) && method.getReturnType().isInterface()) {
                invoker = new SubResourceInvoker((ResteasyWebTarget) base, method, config);
            } else {
                invoker = createClientInvoker(iface, method, (ResteasyWebTarget) base, config);
            }
            methodMap.put(method, invoker);
        }

        Class<?>[] intfs =
                {
                        iface, ResteasyClientProxy.class
                };

        ClientProxy clientProxy = new ClientProxy(methodMap, base, config);
        // this is done so that equals and hashCode work ok. Adding the proxy to a
        // Collection will cause equals and hashCode to be invoked. The Spring
        // infrastructure had some problems without this.
        clientProxy.setClazz(iface);

        return (T) Proxy.newProxyInstance(config.getLoader(), intfs, clientProxy);
    }

    private static <T> ClientInvoker createClientInvoker(Class<T> clazz, Method method, ResteasyWebTarget base, ProxyConfig config) {
        Set<String> httpMethods = IsHttpMethod.getHttpMethods(method);
        if (httpMethods == null || httpMethods.size() != 1) {
            throw new RuntimeException(Messages.MESSAGES.mustUseExactlyOneHttpMethod(method.toString()));
        }
        ClientInvoker invoker = new ClientInvoker(base, clazz, method, config);
        invoker.setHttpMethod(httpMethods.iterator().next());
        return invoker;
    }

    private ProxyBuilder(Class<T> iface, ResteasyWebTarget webTarget) {
        this.iface = iface;
        this.webTarget = webTarget;
    }

    public ProxyBuilder<T> classloader(ClassLoader cl) {
        this.loader = cl;
        return this;
    }

    public ProxyBuilder<T> defaultProduces(MediaType type) {
        this.serverProduces = type;
        return this;
    }

    public ProxyBuilder<T> defaultConsumes(MediaType type) {
        this.serverConsumes = type;
        return this;
    }

    public ProxyBuilder<T> defaultProduces(String type) {
        this.serverProduces = MediaType.valueOf(type);
        return this;
    }

    public ProxyBuilder<T> defaultConsumes(String type) {
        this.serverConsumes = MediaType.valueOf(type);
        return this;
    }

    public T build() {
        return proxy(iface, webTarget, new ProxyConfig(loader, serverConsumes, serverProduces));
    }


}
