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

import java.lang.annotation.Annotation;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import net.ranides.assira.collection.maps.Cache;
import net.ranides.assira.collection.maps.MapCollectors;
import net.ranides.assira.collection.query.CQueryBuilder;
import net.ranides.assira.generic.LazyReference;
import net.ranides.assira.reflection.IAnnotations;
import net.ranides.assira.reflection.IArgument;
import net.ranides.assira.reflection.IArguments;
import net.ranides.assira.reflection.IAttribute;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IContext;
import net.ranides.assira.reflection.IMethod;
import net.ranides.assira.reflection.WSMethod;
import net.ranides.assira.reflection.WSModel;
import net.ranides.assira.text.StringTraits;

/**
 *
 * @author ranides
 */
public class WSInpsector implements WSModel {
    
    private static final Cache<IClass<?>,WSModel> CACHE = new Cache<>();

    private final IClass<?> type;
    private final String name;
    private final String targetNamespace;
    private final String service;
    private final String port;
    private final String location;
    private final Map<String, WSMethod> methods;

    private WSInpsector(IClass<?> type) {
        this.type = type;
        WebService serviceInfo = type.annotation(WebService.class)
                .orElseThrow(() -> new IncompleteAnnotationException(WebParam.class, type.name()));

        name = serviceInfo.name();
        targetNamespace = serviceInfo.targetNamespace();
        service = serviceInfo.serviceName();
        port = serviceInfo.portName();
        location = serviceInfo.wsdlLocation();

        methods = type.methods()
                .require(IAttribute.PUBLIC)
                .require(WebMethod.class)
                .stream()
                .map(RWSMethod::new)
                .filter(m -> !m.exclude)
                .collect(MapCollectors.unique(m -> m.operation()));
    }
    
    public static WSModel resolve(IClass<?> type) {
        return CACHE.get(type, () -> new WSInpsector(type));
    }

    @Override
    public Map<String, WSMethod> methods() {
        return methods;
    }

    @Override
    public Optional<WSMethod> method(String name) {
        return Optional.ofNullable(methods.get(name));
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public String port() {
        return port;
    }

    @Override
    public String service() {
        return service;
    }

    @Override
    public String namespace() {
        return targetNamespace;
    }

    @Override
    public String location() {
        return location;
    }

    @Override
    public IClass<?> type() {
        return type;
    }

    private static class RWSMethod extends RMethod implements WSMethod {

        final String resultname;
        final String action;
        final String operation;
        final boolean exclude;
        final Supplier<IArguments> arguments;

        RWSMethod(IMethod method) {
            super(false, method.context(), method.reflective(), null);
            WebMethod info = method.annotation(WebMethod.class).get();
            this.operation = or(info.operationName(), method.name());
            this.action = info.action();
            this.exclude = info.exclude();
            this.resultname = or(method.annotation(WebResult.class).map(WebResult::name).orElse(""), returns().shortname());
            this.arguments = LazyReference.concurrent(() -> {
                IArguments args = method.arguments();
                return FElements.newArguments(AHints.EMPTY, args.count(), args.stream().map(WSArgument::new));
            });
        }

        @Override
        public IArguments arguments() {
            return arguments.get();
        }

        @Override
        public String action() {
            return action;
        }

        @Override
        public String operation() {
            return operation;
        }

        @Override
        public String resultname() {
            return resultname;
        }

        private static String or(String a, String b) {
            return StringTraits.isEmpty(a) ? b : a;
        }

    }

    private static class WSArgument implements IArgument {

        private final IArgument that;
        private final Optional<String> name;

        public WSArgument(IArgument that) {
            this.that = that;
            this.name = that.annotation(WebParam.class).map(WebParam::name);
        }

        @Override
        public IArguments collect() {
            return FElements.newArguments(AHints.EMPTY, 1, CQueryBuilder.of(this));
        }

        @Override
        public IClass<?> type() {
            return that.type();
        }

        @Override
        public IMethod parent() {
            return that.parent();
        }

        @Override
        public Parameter reflective() {
            return that.reflective();
        }

        @Override
        public IContext context() {
            return that.context();
        }

        @Override
        public String name() {
            return name.orElse(that.name());
        }

        @Override
        public Set<IAttribute> attributes() {
            return that.attributes();
        }

        @Override
        public IAnnotations annotations() {
            return that.annotations();
        }

        @Override
        public <A extends Annotation> Optional<A> annotation(Class<A> type) {
            return that.annotation(type);
        }

    }

}
