/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ws.axis2;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import javax.jws.WebService;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingType;
import javax.xml.ws.Provider;
import javax.xml.ws.Service;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.soap.SOAPBinding;

import org.ow2.jonas.ws.axis2.util.JAXWSUtils;

public class JaxWsImplementorInfo {

    private Class<?> implementorClass;

    private Class<?> seiClass;

    private List<WebService> wsAnnotations = new ArrayList<WebService>(2);

    private WebServiceProvider wsProviderAnnotation;

    public JaxWsImplementorInfo(final Class<?> ic) {
        implementorClass = ic;
        initialize();
    }

    public Class<?> getSEIClass() {
        return seiClass;
    }

    public Class<?> getImplementorClass() {
        return implementorClass;
    }

    public Class<?> getEndpointClass() {
        Class endpointInterface = getSEIClass();
        if (null == endpointInterface) {
            endpointInterface = getImplementorClass();
        }
        return endpointInterface;
    }

    public String getWsdlLocation() {
        for (WebService service : wsAnnotations) {
            if (!JAXWSUtils.isEmpty(service.wsdlLocation())) {
                return service.wsdlLocation();
            }
        }

        if (null != wsProviderAnnotation && !JAXWSUtils.isEmpty(wsProviderAnnotation.wsdlLocation())) {
            return wsProviderAnnotation.wsdlLocation();
        }
        return null;
    }

    /**
     * See use of targetNamespace in {@link WebService}.
     * @return the qualified name of the service.
     */
    public QName getServiceName() {
        String serviceName = null;
        String namespace = null;

        // serviceName cannot be specified on SEI so check impl class only
        if (wsAnnotations.size() > 0) {
            int offset = 1;
            if (seiClass == null) {
                offset = 0;
            }
            // traverse up the parent impl classes for this info as well, but
            // not the last one which would be the sei annotation
            for (int x = 0; x < wsAnnotations.size() - offset; x++) {
                if (JAXWSUtils.isEmpty(serviceName)) {
                    serviceName = wsAnnotations.get(x).serviceName();
                }
                if (JAXWSUtils.isEmpty(namespace)) {
                    namespace = wsAnnotations.get(x).targetNamespace();
                }
            }
        }

        if ((serviceName == null || namespace == null) && wsProviderAnnotation != null) {
            serviceName = wsProviderAnnotation.serviceName();
            namespace = wsProviderAnnotation.targetNamespace();
        }

        if (JAXWSUtils.isEmpty(serviceName)) {
            serviceName = implementorClass.getSimpleName() + "Service";
        }

        if (JAXWSUtils.isEmpty(namespace)) {
            namespace = getDefaultNamespace(implementorClass);
        }

        return new QName(namespace, serviceName);
    }

    /**
     * See use of targetNamespace in {@link WebService}.
     * @return the qualified name of the endpoint.
     */
    public QName getEndpointName() {
        String portName = null;
        String namespace = null;
        String name = null;

        // portName cannot be specified on SEI so check impl class only
        if (wsAnnotations.size() > 0) {
            int offset = 1;
            if (seiClass == null) {
                offset = 0;
            }
            // traverse up the parent impl classes for this info as well, but
            // not the last one which would be the sei annotation
            for (int x = 0; x < wsAnnotations.size() - offset; x++) {
                if (JAXWSUtils.isEmpty(portName)) {
                    portName = wsAnnotations.get(x).portName();
                }
                if (JAXWSUtils.isEmpty(namespace)) {
                    namespace = wsAnnotations.get(x).targetNamespace();
                }
                if (JAXWSUtils.isEmpty(name)) {
                    name = wsAnnotations.get(x).name();
                }
            }
        }

        if ((portName == null || namespace == null) && wsProviderAnnotation != null) {
            portName = wsProviderAnnotation.portName();
            namespace = wsProviderAnnotation.targetNamespace();
        }
        if (JAXWSUtils.isEmpty(portName) && !JAXWSUtils.isEmpty(name)) {
            portName = name + "Port";
        }
        if (JAXWSUtils.isEmpty(portName)) {
            portName = implementorClass.getSimpleName() + "Port";
        }

        if (JAXWSUtils.isEmpty(namespace)) {
            namespace = getDefaultNamespace(implementorClass);
        }

        return new QName(namespace, portName);
    }

    public QName getInterfaceName() {
        String name = null;
        String namespace = null;

        if (seiClass != null) {
            WebService service = seiClass.getAnnotation(WebService.class);
            if (!JAXWSUtils.isEmpty(service.name())) {
                name = service.name();
            }
            if (!JAXWSUtils.isEmpty(service.targetNamespace())) {
                namespace = service.targetNamespace();
            }
        } else {
            for (WebService service : wsAnnotations) {
                if (!JAXWSUtils.isEmpty(service.name()) && name == null) {
                    name = service.name();
                }
                if (!JAXWSUtils.isEmpty(service.targetNamespace()) && namespace == null) {
                    namespace = service.targetNamespace();
                }
            }
        }
        if (name == null) {
            if (seiClass != null) {
                name = seiClass.getSimpleName();
            } else if (implementorClass != null) {
                name = implementorClass.getSimpleName();
            }
        }
        if (namespace == null) {
            if (seiClass != null) {
                namespace = getDefaultNamespace(seiClass);
            } else if (implementorClass != null) {
                namespace = getDefaultNamespace(implementorClass);
            }
        }

        return new QName(namespace, name);
    }

    private String getDefaultNamespace(final Class clazz) {
        String pkg = JAXWSUtils.getNamespace(JAXWSUtils.getPackageName(clazz));
        return JAXWSUtils.isEmpty(pkg) ? "http://unknown.namespace/" : pkg;
    }

    private String getWSInterfaceName(final Class<?> implClz) {
        if (implClz.isInterface() && implClz.getAnnotation(WebService.class) != null) {
            return implClz.getName();
        }
        Class<?>[] clzs = implClz.getInterfaces();
        for (Class<?> clz : clzs) {
            if (null != clz.getAnnotation(WebService.class)) {
                return clz.getName();
            }
        }
        return null;
    }

    private String getImplementorClassName() {
        for (WebService service : wsAnnotations) {
            if (!JAXWSUtils.isEmpty(service.endpointInterface())) {
                return service.endpointInterface();
            }
        }
        return null;
    }

    private void initialize() {
        Class<?> cls = implementorClass;
        while (cls != null) {
            WebService annotation = cls.getAnnotation(WebService.class);
            if (annotation != null) {
                wsAnnotations.add(annotation);
                if (cls.isInterface()) {
                    cls = null;
                }
            }
            if (cls != null) {
                cls = cls.getSuperclass();
            }
        }
        String sei = getImplementorClassName();
        boolean seiFromWsAnnotation = true;
        if (JAXWSUtils.isEmpty(sei)) {
            seiFromWsAnnotation = false;
            sei = getWSInterfaceName(implementorClass);
        }
        if (!JAXWSUtils.isEmpty(sei)) {
            try {
                seiClass = JAXWSUtils.loadClass(sei, implementorClass);
            } catch (ClassNotFoundException ex) {
                throw new WebServiceException(ex);
            }
            WebService seiAnnotation = seiClass.getAnnotation(WebService.class);
            if (null == seiAnnotation) {
                throw new WebServiceException();
            }
            if (seiFromWsAnnotation
                    && (!JAXWSUtils.isEmpty(seiAnnotation.portName()) || !JAXWSUtils.isEmpty(seiAnnotation.serviceName()) || !JAXWSUtils.isEmpty(seiAnnotation
                            .endpointInterface()))) {
                throw new WebServiceException();
            }
            wsAnnotations.add(seiAnnotation);
        }
        wsProviderAnnotation = implementorClass.getAnnotation(WebServiceProvider.class);
    }

    public boolean isWebServiceProvider() {
        return Provider.class.isAssignableFrom(implementorClass);
    }

    public WebServiceProvider getWsProvider() {
        return wsProviderAnnotation;
    }

    public Service.Mode getServiceMode() {
        ServiceMode m = implementorClass.getAnnotation(ServiceMode.class);
        if (m != null && m.value() != null) {
            return m.value();
        }
        return Service.Mode.PAYLOAD;
    }

    public Class<?> getProviderParameterType() {
        // The Provider Implementor inherits out of Provider<T>
        Type intfTypes[] = implementorClass.getGenericInterfaces();
        for (Type t : intfTypes) {
            Class<?> clazz = JAXWSUtils.getClassFromType(t);
            if (Provider.class == clazz) {
                Type paramTypes[] = ((ParameterizedType) t).getActualTypeArguments();
                return JAXWSUtils.getClassFromType(paramTypes[0]);
            }
        }
        return null;
    }

    public String getBindingType() {
        BindingType bType = implementorClass.getAnnotation(BindingType.class);
        if (bType != null) {
            return bType.value();
        }
        return SOAPBinding.SOAP11HTTP_BINDING;
    }

}
