package org.jboss.resteasy.wadl;

import org.jboss.resteasy.core.ResourceInvoker;
import org.jboss.resteasy.core.ResourceMethodInvoker;
import org.jboss.resteasy.core.ResourceMethodRegistry;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.metadata.ResourceLocator;
import org.jboss.resteasy.util.GetRestful;
import org.jboss.resteasy.wadl.i18n.LogMessages;
import org.jboss.resteasy.wadl.i18n.Messages;

import javax.ws.rs.Path;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author <a href="mailto:l.weinan@gmail.com">Weinan Li</a>
 */
public class ResteasyWadlServiceRegistry {

    private ResourceMethodRegistry registry;

    public ResteasyProviderFactory getProviderFactory() {
        return providerFactory;
    }

    private ResteasyProviderFactory providerFactory;

    private ResteasyWadlServiceRegistry parent;

    private Map<String, ResteasyWadlResourceMetaData> resources;

    private List<ResteasyWadlServiceRegistry> locators;

    private ResourceLocator locator;

    private String uri;

    private String functionPrefix;

    public ResteasyWadlServiceRegistry(ResteasyWadlServiceRegistry parent, ResourceMethodRegistry registry,
                                       ResteasyProviderFactory providerFactory, ResourceLocator locator) {
        this.parent = parent;
        this.registry = registry;
        this.providerFactory = providerFactory;
        this.locator = locator;
        if (locator != null) {
            Method method = locator.getMethod();
            Path methodPath = method.getAnnotation(Path.class);
            Class<?> declaringClass = method.getDeclaringClass();
            Path classPath = declaringClass.getAnnotation(Path.class);
            this.uri = ResteasyWadlMethodMetaData.appendURIFragments(parent, classPath, methodPath);
            if (parent.isRoot())
                this.functionPrefix = declaringClass.getSimpleName() + "." + method.getName();
            else
                this.functionPrefix = parent.getFunctionPrefix() + "." + method.getName();
        }
        scanRegistry();
    }

    private void scanRegistry() {
        resources = new HashMap<String, ResteasyWadlResourceMetaData>();

        locators = new ArrayList<ResteasyWadlServiceRegistry>();

        for (Map.Entry<String, List<ResourceInvoker>> entry : registry.getBounded().entrySet()) {
            List<ResourceInvoker> invokers = entry.getValue();
            for (ResourceInvoker invoker : invokers) {
                if (invoker instanceof ResourceMethodInvoker) {
                    ResteasyWadlMethodMetaData methodMetaData = new ResteasyWadlMethodMetaData(this, (ResourceMethodInvoker) invoker);
                    ResteasyWadlResourceMetaData resourceMetaData = resources.get(methodMetaData.getKlassUri());
                    if (resourceMetaData == null) {
                        resourceMetaData = new ResteasyWadlResourceMetaData(methodMetaData.getKlassUri());
                        resources.put(methodMetaData.getKlassUri(), resourceMetaData);
                    }
                    resourceMetaData.addMethodMetaData(methodMetaData);
                } else if (invoker instanceof ResourceLocator) {
                    ResourceLocator locator = (ResourceLocator) invoker;
                    Method method = locator.getMethod();
                    Class<?> locatorType = method.getReturnType();
                    Class<?>[] locatorResourceTypes = GetRestful.getSubResourceClasses(locatorType);
                    for (Class<?> locatorResourceType : locatorResourceTypes) {
                        if (locatorResourceType == null) {
                            // FIXME: we could generate an error for the client, which would be more informative than
                            // just logging this
                            LogMessages.LOGGER.warn(Messages.MESSAGES.impossibleToGenerateWADL(method.getDeclaringClass().getName(), method.getName())); // skip this
                            continue;
                        }
                        ResourceMethodRegistry locatorRegistry = new ResourceMethodRegistry(providerFactory);
                        locatorRegistry.addResourceFactory(null, null, locatorResourceType);
                        locators.add(new ResteasyWadlServiceRegistry(this, locatorRegistry, providerFactory, locator));
                    }
                }
            }
        }
    }

    public Map<String, ResteasyWadlResourceMetaData> getResources() {
        return resources;
    }

    public List<ResteasyWadlServiceRegistry> getLocators() {
        return locators;
    }

    public String getUri() {
        return uri;
    }

    public boolean isRoot() {
        return parent == null;
    }

    public String getFunctionPrefix() {
        return functionPrefix;
    }

    public void collectResourceMethodsUntilRoot(List<Method> methods) {
        if (isRoot())
            return;
        methods.add(locator.getMethod());
        parent.collectResourceMethodsUntilRoot(methods);
    }

}

