/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.reflection.inspect;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.IncompleteAnnotationException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import net.ranides.assira.collection.ArrayUtils;
import net.ranides.assira.generic.LazyRef;
import net.ranides.assira.generic.Serializer;
import net.ranides.assira.reflection.GenericClass;
import net.ranides.assira.reflection.GenericParam;
import net.ranides.assira.reflection.MethodFactory.MethodPointer;
import net.ranides.assira.text.Strings;

/**
 *
 * @author ranides
 */
public class WebServiceInspector {

    private final Class<?> clazz;

    private final String name;
    private final String targetNamespace;
    private final String service;
    private final String port;
    private final String location;

    public WebServiceInspector(Class<?> clazz) {
        this.clazz = clazz;
        WebService serviceInfo = clazz.getAnnotation(WebService.class);

        if (null == serviceInfo) {
            throw new IncompleteAnnotationException(WebParam.class, clazz.getCanonicalName() );
        }
        name            = serviceInfo.name();
        targetNamespace = serviceInfo.targetNamespace();
        service         = serviceInfo.serviceName();
        port            = serviceInfo.portName();
        location        = serviceInfo.wsdlLocation();
    }

    public List<WSMethod> methods() {
        List<WSMethod> list = new ArrayList<>();
        for(MethodPointer<?> pointer : MethodInspector.inclass(clazz).published().annotated(WebMethod.class).pointers()) {
            WSMethod method = new WSMethod(pointer);
            if(!method.exclude) {
                list.add(method);
            }
        }
        return list;
    }

    public String name() {
        return name;
    }

    public String port() {
        return port;
    }

    public String service() {
        return service;
    }

    public String namespace() {
        return targetNamespace;
    }

    public String location() {
        return location;
    }

    public Class<?> reflective() {
        return clazz;
    }

    public static class WSMethod<R> extends MethodPointer<R> implements Serializable {
		
		private static final long serialVersionUID = 1L;

        private final String resultname;
        private final String action;
        private final String operation;
        private final boolean exclude;
        
        private final LazyRef<Map<String, GenericClass>> webparams = LazyRef.lockfree(new Callable<Map<String, GenericClass>>() {
            @Override
            public Map<String, GenericClass> call() throws Exception {
                return iwebparams();
            }
        });
		
		WSMethod(Class<?> clazz, String name, Class[] params) {
			super(clazz, name, params);
			WebMethod info = method.getAnnotation(WebMethod.class);
            
            this.operation = info.operationName();
            this.action = info.action();
            this.exclude = info.exclude();

            WebResult iresult = method.getAnnotation(WebResult.class);
            if(null == iresult) {
                this.resultname = "";
            } else {
                this.resultname = iresult.name();
            }
		}

        WSMethod(MethodPointer<R> pointer) {
            super(pointer);

            WebMethod info = method.getAnnotation(WebMethod.class);
            
            this.operation = info.operationName();
            this.action = info.action();
            this.exclude = info.exclude();

            WebResult iresult = method.getAnnotation(WebResult.class);
            if(null == iresult) {
                this.resultname = "";
            } else {
                this.resultname = iresult.name();
            }
        }
        
        private Map<String, GenericClass> iwebparams() {
            Map<String, GenericClass> map = new LinkedHashMap<>();
            Annotation[][] paramInfos  = method.getParameterAnnotations();

            int index = 0;
            for(GenericParam param : params()) {
                WebParam info = ArrayUtils.first(paramInfos[index], WebParam.class);
                map.put((null != info) ? info.name() : param.name(), param.type());
                index++;
            }
            return map;
        }


        public String action() {
            return action;
        }
        
        @Override
        public String name() {
            return Strings.or(operation, method.getName());
        }
        
        public Map<String, GenericClass> webparams() {
            return webparams.get();
        }
        
        public String resultname() {
            return Strings.or(resultname, type().toString());
        }
		
		private Object writeReplace() {
			return Serializer.proxy(this, method.getDeclaringClass(), method.getName(), method.getParameterTypes());
		}

    }

}
