/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.cxf.tools.wsdlto.frontend.jaxws.processor.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import javax.wsdl.OperationType;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.namespace.QName;

import org.apache.cxf.common.i18n.Message;
import org.apache.cxf.common.jaxb.JAXBUtils;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.service.model.MessageInfo;
import org.apache.cxf.service.model.MessagePartInfo;
import org.apache.cxf.service.model.OperationInfo;
import org.apache.cxf.tools.common.ToolConstants;
import org.apache.cxf.tools.common.ToolContext;
import org.apache.cxf.tools.common.ToolException;
import org.apache.cxf.tools.common.model.JavaMethod;
import org.apache.cxf.tools.common.model.JavaParameter;
import org.apache.cxf.tools.common.model.JavaReturn;
import org.apache.cxf.tools.common.model.JavaType;
import org.apache.cxf.tools.wsdlto.core.DataBindingProfile;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.customization.JAXWSBinding;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.customization.JAXWSParameter;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.processor.internal.annotator.WebParamAnnotator;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.processor.internal.annotator.XmlJavaTypeAdapterAnnotator;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.processor.internal.annotator.XmlListAnotator;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.processor.internal.mapper.ParameterMapper;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaSimpleType;
import org.apache.ws.commons.schema.XmlSchemaSimpleTypeList;
import org.apache.ws.commons.schema.constants.Constants;

public class ParameterProcessor extends AbstractProcessor {
    public static final String HEADER = "messagepart.isheader";
    public static final String OUT_OF_BAND_HEADER = "messagepart.is_out_of_band_header";

    private DataBindingProfile dataBinding;

    public ParameterProcessor(ToolContext penv) {
        super(penv);
        dataBinding = context.get(DataBindingProfile.class);
    }

    private boolean isRequestResponse(JavaMethod method) {
        return method.getStyle() == OperationType.REQUEST_RESPONSE;
    }

    public void process(JavaMethod method,
                        MessageInfo inputMessage,
                        MessageInfo outputMessage,
                        List<String> parameterOrder) throws ToolException {

        if (!StringUtils.isEmpty(parameterOrder)
            && isValidOrdering(parameterOrder, inputMessage, outputMessage)
            && !method.isWrapperStyle()) {

            buildParamModelsWithOrdering(method,
                                         inputMessage,
                                         outputMessage,
                                         parameterOrder);
        } else {
            buildParamModelsWithoutOrdering(method,
                                            inputMessage,
                                            outputMessage);
        }
    }

    /**
     * This method will be used by binding processor to change existing
     * generated java method of porttype
     *
     * @param method
     * @param part
     * @param style
     * @throws ToolException
     */
    public JavaParameter addParameterFromBinding(JavaMethod method,
                                                 MessagePartInfo part,
                                                 JavaType.Style style)
        throws ToolException {
        return addParameter(part, method, getParameterFromPart(method, part, style));
    }

    private JavaParameter getParameterFromPart(JavaMethod jm, MessagePartInfo part, JavaType.Style style) {
        return ParameterMapper.map(jm, part, style, context);
    }

    protected JavaParameter addParameter(MessagePartInfo mpart,
                                         JavaMethod method,
                                         JavaParameter parameter) throws ToolException {
        if (parameter == null) {
            return null;
        }
        String name = parameter.getName();
        int count = 0;
        while (method.getParameter(parameter.getName()) != null
            && context.optionSet(ToolConstants.CFG_AUTORESOLVE)
            && parameter.getStyle() != JavaType.Style.INOUT) {
            parameter.setName(name + (++count));
        }

        parameter.setMethod(method);
        parameter.annotate(new WebParamAnnotator(isOutOfBandHeader(mpart)));
        method.addParameter(parameter);

        return parameter;
    }

    private void processReturn(JavaMethod method, MessagePartInfo part) {
        String name = part == null ? "return" : part.getName().getLocalPart();
        String type = part == null ? "void" : ProcessorUtil.resolvePartType(part, context);

        String namespace = part == null ? null : ProcessorUtil.resolvePartNamespace(part);

        JavaReturn returnType = new JavaReturn(name, type, namespace);
        if (part != null) {
            returnType.setDefaultValueWriter(ProcessorUtil.getDefaultValueWriter(part, context));
        }

        returnType.setQName(ProcessorUtil.getElementName(part));
        returnType.setStyle(JavaType.Style.OUT);
        if (namespace != null && type != null && !"void".equals(type)) {
            returnType.setClassName(ProcessorUtil.getFullClzName(part, context, false));
        }

        if (part != null && part.getXmlSchema() instanceof XmlSchemaSimpleType) {
            processXmlSchemaSimpleType((XmlSchemaSimpleType)part.getXmlSchema(), method, part);
        } else if (part != null && part.getXmlSchema() instanceof XmlSchemaElement) {
            XmlSchemaElement element = (XmlSchemaElement)part.getXmlSchema();
            if (element.getSchemaType() instanceof XmlSchemaSimpleType) {
                processXmlSchemaSimpleType((XmlSchemaSimpleType)element.getSchemaType(), method, part);
            }
        }

        method.setReturn(returnType);
    }

    private static void processXmlSchemaSimpleType(XmlSchemaSimpleType xmlSchema, JavaMethod method,
                                                   MessagePartInfo part) {
        if (xmlSchema.getContent() instanceof XmlSchemaSimpleTypeList
            && (!part.isElement() || !method.isWrapperStyle())) {
            method.annotate(new XmlListAnotator(method.getInterface()));
        }
        if (Constants.XSD_HEXBIN.equals(xmlSchema.getQName())
            && (!part.isElement() || !method.isWrapperStyle())) {
            method.annotate(new XmlJavaTypeAdapterAnnotator(method.getInterface(), HexBinaryAdapter.class));
        }
    }

    private boolean isOutOfBandHeader(final MessagePartInfo part) {
        if (part == null) {
            return false;
        }
        return Boolean.TRUE.equals(part.getProperty(OUT_OF_BAND_HEADER));
    }

    private boolean requireOutOfBandHeader() {
        String value = (String)context.get(ToolConstants.CFG_EXTRA_SOAPHEADER);
        if (StringUtils.isEmpty(value)) {
            return false;
        }
        return Boolean.parseBoolean(value);
    }

    private int countOutOfBandHeader(MessageInfo message) {
        int count = 0;
        for (MessagePartInfo part : message.getMessageParts()) {
            if (isOutOfBandHeader(part)) {
                count++;
            }
        }
        return count;
    }

    private boolean messagePartsNotUnique(final MessageInfo message) {
        int count = countOutOfBandHeader(message);
        return message.getMessagePartsNumber() - count > 1;
    }

    private void processInput(JavaMethod method, MessageInfo inputMessage) throws ToolException {
        if (requireOutOfBandHeader()) {
            try {
                Class.forName("org.apache.cxf.binding.soap.SoapBindingFactory");
            } catch (Exception e) {
                LOG.log(Level.WARNING, new Message("SOAP_MISSING", LOG).toString());
            }
        }

        JAXWSBinding mBinding = inputMessage.getOperation().getExtensor(JAXWSBinding.class);
        for (MessagePartInfo part : inputMessage.getMessageParts()) {
            if (isOutOfBandHeader(part) && !requireOutOfBandHeader()) {
                continue;
            }
            JavaParameter param = getParameterFromPart(method, part, JavaType.Style.IN);
            if (mBinding != null && mBinding.getJaxwsParas() != null) {
                for (JAXWSParameter jwp : mBinding.getJaxwsParas()) {
                    if (part.getName().getLocalPart().equals(jwp.getPart())) {
                        param.setName(jwp.getName());
                    }
                }
            }
            addParameter(part, method, param);
        }
    }

    private void processWrappedInput(JavaMethod method, MessageInfo inputMessage) throws ToolException {
        if (messagePartsNotUnique(inputMessage)) {
            processInput(method, inputMessage);
            return;
        } else if (inputMessage.getMessagePartsNumber() == 0) {
            return;
        }
        MessagePartInfo part = inputMessage.getFirstMessagePart();

        List<QName> wrappedElements = ProcessorUtil.getWrappedElementQNames(context, part.getElementQName());
        if ((wrappedElements == null || wrappedElements.isEmpty())
            && countOutOfBandHeader(inputMessage) == 0) {
            return;
        }
        for (QName item : wrappedElements) {
            JavaParameter jp = getParameterFromQName(part.getElementQName(),
                                  item, JavaType.Style.IN, part);

            checkPartName(inputMessage, item, jp);

            if (StringUtils.isEmpty(part.getConcreteName().getNamespaceURI())) {
                jp.setTargetNamespace("");
            }

            addParameter(part, method, jp);
        }

        // Adding out of band headers
        if (requireOutOfBandHeader() && countOutOfBandHeader(inputMessage) > 0) {
            for (MessagePartInfo hpart : inputMessage.getMessageParts()) {
                if (!isOutOfBandHeader(hpart)) {
                    continue;
                }
                addParameter(hpart, method, getParameterFromPart(method, hpart, JavaType.Style.IN));
            }
        }
    }

    private void processOutput(JavaMethod method, MessageInfo inputMessage, MessageInfo outputMessage)
        throws ToolException {

        Map<QName, MessagePartInfo> inputPartsMap = inputMessage.getMessagePartsMap();
        List<MessagePartInfo> outputParts =
            outputMessage == null ? new ArrayList<>() : outputMessage.getMessageParts();
        // figure out output parts that are not present in input parts
        List<MessagePartInfo> outParts = new ArrayList<>();
        int numHeader = 0;
        if (isRequestResponse(method)) {
            for (MessagePartInfo outpart : outputParts) {
                boolean oob = false;
                if (isOutOfBandHeader(outpart)) {
                    if (!requireOutOfBandHeader()) {
                        continue;
                    }
                    oob = true;
                }

                MessagePartInfo inpart = inputPartsMap.get(outpart.getName());
                if (inpart == null) {
                    outParts.add(outpart);
                    if (oob) {
                        numHeader++;
                    }
                    continue;
                } else if (isSamePart(inpart, outpart)) {
                    boolean found = false;
                    for (JavaParameter p : method.getParameters()) {
                        if (p.getQName().equals(ProcessorUtil.getElementName(outpart))
                            && p.getPartName().equals(outpart.getName().getLocalPart())) {
                            p.setHolder(true);
                            p.setHolderName(javax.xml.ws.Holder.class.getName());
                            String holderClass = p.getClassName();
                            if (JAXBUtils.holderClass(holderClass) != null) {
                                holderClass = JAXBUtils.holderClass(holderClass).getName();
                            }
                            p.setClassName(holderClass);
                            p.getAnnotations().clear();
                            p.setStyle(JavaType.Style.INOUT);
                            p.annotate(new WebParamAnnotator(isOutOfBandHeader(outpart)));
                            found = true;
                        }
                    }
                    if (!found) {
                        addParameter(outpart, method,
                                     getParameterFromPart(method, outpart, JavaType.Style.INOUT));
                    }
                    continue;
                } else if (!isSamePart(inpart, outpart)) {
                    if (oob) {
                        numHeader++;
                    }
                    outParts.add(outpart);
                    continue;
                }
            }
        }

        if (isRequestResponse(method)) {
            if (outParts.size() - numHeader == 1
                && !isHeader(outParts.get(0))) {
                processReturn(method, outParts.get(0));
                outParts.remove(0);
            } else {
                processReturn(method, null);
            }
            JAXWSBinding mBinding = outputMessage.getOperation().getExtensor(JAXWSBinding.class);
            for (MessagePartInfo part : outParts) {

                JavaParameter param = getParameterFromPart(method, part, JavaType.Style.OUT);
                if (mBinding != null && mBinding.getJaxwsParas() != null) {
                    for (JAXWSParameter jwp : mBinding.getJaxwsParas()) {
                        if (part.getName().getLocalPart().equals(jwp.getPart())) {
                            param.setName(jwp.getName());
                        }
                    }
                }

                addParameter(part, method, param);
            }
        } else {
            processReturn(method, null);
        }
    }

    private void processWrappedOutput(JavaMethod method,
                                      MessageInfo inputMessage,
                                      MessageInfo outputMessage) throws ToolException {

        processWrappedAbstractOutput(method, inputMessage, outputMessage);

        // process out of band headers
        if (countOutOfBandHeader(outputMessage) > 0) {
            for (MessagePartInfo hpart : outputMessage.getMessageParts()) {
                if (!isOutOfBandHeader(hpart) || !requireOutOfBandHeader()) {
                    continue;
                }
                addParameter(hpart, method, getParameterFromPart(method, hpart, JavaType.Style.OUT));
            }
        }
    }

    private void processWrappedAbstractOutput(JavaMethod method,
                                              MessageInfo inputMessage,
                                              MessageInfo outputMessage) throws ToolException {

        if (messagePartsNotUnique(inputMessage) || messagePartsNotUnique(outputMessage)) {
            processOutput(method, inputMessage, outputMessage);
            return;
        }
        if (outputMessage.getMessagePartsNumber() == 0) {
            addVoidReturn(method);
            return;
        }

        MessagePartInfo inputPart = inputMessage.getMessagePartsNumber() > 0 ? inputMessage
            .getFirstMessagePart() : null;
        MessagePartInfo outputPart = outputMessage.getMessagePartsNumber() > 0 ? outputMessage
            .getFirstMessagePart() : null;

        List<QName> inputWrapElement = null;
        List<QName> outputWrapElement = null;

        if (inputPart != null) {
            inputWrapElement = ProcessorUtil.getWrappedElementQNames(context, inputPart.getElementQName());
        }

        if (outputPart != null) {
            outputWrapElement = ProcessorUtil.getWrappedElementQNames(context, outputPart.getElementQName());
        }

        if (inputWrapElement == null || outputWrapElement.isEmpty()) {
            addVoidReturn(method);
            return;
        }
        method.setReturn(null);
        boolean qualified = ProcessorUtil.isSchemaFormQualified(context, outputPart.getElementQName());

        if (outputWrapElement.size() == 1) {
            QName outElement = outputWrapElement.iterator().next();
            boolean sameWrapperChild = false;
            for (QName inElement : inputWrapElement) {
                if (isSameWrapperChild(inElement, outElement)) {
                    JavaParameter jpIn = null;
                    for (JavaParameter j : method.getParameters()) {
                        if (inElement.equals(j.getQName())) {
                            jpIn = j;
                        }
                    }
                    JavaParameter jp = getParameterFromQName(outputPart.getElementQName(), outElement,
                                                             JavaType.Style.INOUT, outputPart);

                    if (!qualified && !isRefElement(outputPart, outElement)) {
                        jp.setTargetNamespace("");
                    }
                    if (jpIn != null && !jpIn.getClassName().equals(jp.getClassName())) {
                        jp.setStyle(JavaType.Style.OUT);
                    }
                    addParameter(outputPart, method, jp);
                    sameWrapperChild = true;

                    if (method.getReturn() == null) {
                        addVoidReturn(method);
                    }
                    break;
                }
            }
            if (!sameWrapperChild) {
                JavaReturn jreturn = getReturnFromQName(outElement, outputPart);
                if (!qualified && !this.isRefElement(outputPart, outElement)) {
                    jreturn.setTargetNamespace("");
                }
                method.setReturn(jreturn);
            }
            return;
        }
        for (QName outElement : outputWrapElement) {
            if ("return".equals(outElement.getLocalPart())) {
                if (method.getReturn() != null) {
                    org.apache.cxf.common.i18n.Message msg =
                        new org.apache.cxf.common.i18n.Message("WRAPPER_STYLE_TWO_RETURN_TYPES", LOG);
                    throw new ToolException(msg);
                }
                JavaReturn jreturn = getReturnFromQName(outElement, outputPart);
                if (!qualified) {
                    jreturn.setTargetNamespace("");
                }
                method.setReturn(jreturn);
                continue;
            }
            boolean sameWrapperChild = false;
            for (QName inElement : inputWrapElement) {
                if (isSameWrapperChild(inElement, outElement)) {

                    JavaParameter jpIn = null;
                    for (JavaParameter j : method.getParameters()) {
                        if (inElement.equals(j.getQName())) {
                            jpIn = j;
                        }
                    }
                    JavaParameter jp = getParameterFromQName(outputPart.getElementQName(), outElement,
                                                             JavaType.Style.INOUT, outputPart);

                    if (!qualified && !isRefElement(outputPart, outElement)) {
                        jp.setTargetNamespace("");
                    }
                    if (jpIn != null && !jpIn.getClassName().equals(jp.getClassName())) {
                        jp.setStyle(JavaType.Style.OUT);
                        checkPartName(outputMessage, outElement, jp);
                    }

                    addParameter(outputPart, method, jp);
                    sameWrapperChild = true;
                    break;
                }
            }
            if (!sameWrapperChild) {
                JavaParameter jp = getParameterFromQName(outputPart.getElementQName(), outElement,
                                                          JavaType.Style.OUT, outputPart);
                if (!qualified && !isRefElement(outputPart, outElement)) {
                    jp.setTargetNamespace("");
                }
                checkPartName(outputMessage, outElement, jp);

                addParameter(outputPart, method, jp);
            }
        }
        if (method.getReturn() == null) {
            addVoidReturn(method);
        }
    }
    private void checkPartName(MessageInfo message, QName element, JavaParameter jp) {
        JAXWSBinding mBinding = message.getOperation().getExtensor(JAXWSBinding.class);
        if (mBinding != null && mBinding.getJaxwsParas() != null) {
            for (JAXWSParameter jwsp : mBinding.getJaxwsParas()) {
                if (element.getLocalPart().equals(jwsp.getElementName().getLocalPart())
                    && jwsp.getMessageName().equals(message.getName().getLocalPart())) {
                    jp.setName(jwsp.getName());
                }
            }
        }
    }
    private void addVoidReturn(JavaMethod method) {
        JavaReturn returnType = new JavaReturn("return", "void", null);
        method.setReturn(returnType);
    }

    private boolean isSameWrapperChild(QName in, QName out) {
        if (!in.getLocalPart().equals(out.getLocalPart())) {
            return false;
        }

        return in.getNamespaceURI().equals(out.getNamespaceURI());
    }

    private JavaParameter getParameterFromQName(QName wrapperElement, QName item, JavaType.Style style,
                                                MessagePartInfo part) {

        String fullJavaName = this.dataBinding.getWrappedElementType(wrapperElement, item);

        String targetNamespace = item.getNamespaceURI();

        String jpname = ProcessorUtil.mangleNameToVariableName(item.getLocalPart());
        JavaParameter parameter = new JavaParameter(jpname, fullJavaName, targetNamespace);
        parameter.setStyle(style);
        parameter.setQName(item);

        parameter.setDefaultValueWriter(ProcessorUtil.getDefaultValueWriterForWrappedElement(part,
                                                                                             context,
                                                                                             item));

        if (style == JavaType.Style.OUT || style == JavaType.Style.INOUT) {
            parameter.setHolder(true);
            parameter.setHolderName(javax.xml.ws.Holder.class.getName());
            String holderClass = fullJavaName;
            if (JAXBUtils.holderClass(fullJavaName) != null) {
                holderClass = JAXBUtils.holderClass(fullJavaName).getName();
            }
            parameter.setClassName(holderClass);
        }
        return parameter;

    }

    private JavaReturn getReturnFromQName(QName element, MessagePartInfo part) {

        String fullJavaName = this.dataBinding.getWrappedElementType(part.getElementQName(), element);
        String simpleJavaName = fullJavaName;

        int index = fullJavaName.lastIndexOf('.');

        if (index > -1) {
            simpleJavaName = fullJavaName.substring(index);
        }

        final String targetNamespace;
        if (isHeader(part)) {
            targetNamespace = part.getMessageInfo().getOperation().getInterface().
            getService().getTargetNamespace();
        }  else {
            targetNamespace = element.getNamespaceURI();
        }

        String jpname = ProcessorUtil.mangleNameToVariableName(simpleJavaName);
        JavaReturn returnType = new JavaReturn(jpname, fullJavaName, targetNamespace);

        returnType.setDefaultValueWriter(
            ProcessorUtil.getDefaultValueWriterForWrappedElement(part, context, element));

        returnType.setQName(element);
        returnType.setStyle(JavaType.Style.OUT);
        return returnType;
    }

    private boolean isHeader(final MessagePartInfo part) {
        return Boolean.TRUE.equals(part.getProperty(HEADER));
    }

    private void buildParamModelsWithoutOrdering(JavaMethod method,
                                                 MessageInfo inputMessage,
                                                 MessageInfo outputMessage) throws ToolException {
        boolean wrapped = method.isWrapperStyle();
        if (wrapped) {
            //check if really can be wrapper style....

            if (inputMessage != null) {
                List<MessagePartInfo> inputParts = inputMessage.getMessageParts();
                MessagePartInfo inputPart = !inputParts.isEmpty() ? inputParts.iterator().next() : null;
                List<QName> inputWrapElement = null;
                if (inputPart != null && inputPart.isElement()) {
                    inputWrapElement = ProcessorUtil.getWrappedElementQNames(context,
                                                                             inputPart.getElementQName());
                }
                if (inputWrapElement != null) {
                    for (QName item : inputWrapElement) {
                        String fullJavaName = dataBinding
                            .getWrappedElementType(inputPart.getElementQName(), item);
                        if (StringUtils.isEmpty(fullJavaName)) {
                            wrapped = false;
                            break;
                        }
                    }
                }
            }
            if (outputMessage != null) {
                List<MessagePartInfo> outputParts = outputMessage.getMessageParts();
                MessagePartInfo outputPart = !outputParts.isEmpty() ? outputParts.iterator().next() : null;
                List<QName> outputWrapElement = null;
                if (outputPart != null && outputPart.isElement()) {
                    outputWrapElement = ProcessorUtil.getWrappedElementQNames(context,
                                                                              outputPart.getElementQName());
                }
                if (outputWrapElement != null) {
                    for (QName item : outputWrapElement) {
                        String fullJavaName = dataBinding
                            .getWrappedElementType(outputPart.getElementQName(), item);
                        if (StringUtils.isEmpty(fullJavaName)) {
                            wrapped = false;
                            break;
                        }
                    }
                }
            }
            if (!wrapped) {
                //could not map one of the parameters to a java type, need to drop down to bare style
                method.setWrapperStyle(false);
            }
        }

        if (inputMessage != null) {
            if (method.isWrapperStyle()) {
                processWrappedInput(method, inputMessage);
            } else {
                processInput(method, inputMessage);
            }
        }
        if (outputMessage == null) {
            processReturn(method, null);
        } else {
            if (method.isWrapperStyle()) {
                processWrappedOutput(method, inputMessage, outputMessage);
            } else {
                processOutput(method, inputMessage, outputMessage);
            }
        }
    }

    private void buildParamModelsWithOrdering(JavaMethod method,
                                              MessageInfo inputMessage,
                                              MessageInfo outputMessage,
                                              List<String> parameterList) throws ToolException {
        Map<QName, MessagePartInfo> inputPartsMap = inputMessage.getMessagePartsMap();
        Collection<MessagePartInfo> inputParts = inputPartsMap.values();

        final Map<QName, MessagePartInfo> outputPartsMap;
        final Collection<MessagePartInfo> outputParts;

        if (outputMessage != null) {
            outputPartsMap = outputMessage.getMessagePartsMap();
            outputParts = outputPartsMap.values();
        } else {
            outputPartsMap = new LinkedHashMap<>();
            outputParts = new ArrayList<>();
        }


        List<MessagePartInfo> inputUnlistedParts = new ArrayList<>();
        List<MessagePartInfo> outputUnlistedParts = new ArrayList<>();

        for (MessagePartInfo part : inputParts) {
            if (!parameterList.contains(part.getName().getLocalPart())) {
                inputUnlistedParts.add(part);
            }
        }

        if (isRequestResponse(method)) {
            for (MessagePartInfo part : outputParts) {
                if (!parameterList.contains(part.getName().getLocalPart())) {
                    MessagePartInfo inpart = inputMessage.getMessagePart(part.getName());
                    if (inpart == null || !isSamePart(inpart, part)) {
                        outputUnlistedParts.add(part);
                    }
                }
            }

            if (outputUnlistedParts.size() == 1) {
                processReturn(method, outputUnlistedParts.get(0));
                outputParts.remove(outputUnlistedParts.get(0));
                outputUnlistedParts.clear();
            } else {
                processReturn(method, null);
            }
        } else {
            processReturn(method, null);
        }

        // now create list of paramModel with parts
        // first for the ordered list
        int index = 0;
        int size = parameterList.size();

        while (index < size) {
            String partName = parameterList.get(index);
            MessagePartInfo part = inputPartsMap.get(inputMessage.getMessagePartQName(partName));
            JavaType.Style style = JavaType.Style.IN;
            if (part == null) {
                part = outputPartsMap.get(inputMessage.getMessagePartQName(partName));
                style = JavaType.Style.OUT;
            } else if (outputPartsMap.get(inputMessage.getMessagePartQName(partName)) != null
                && isSamePart(part, outputPartsMap.get(inputMessage.getMessagePartQName(partName)))) {

                style = JavaType.Style.INOUT;
            }
            if (part != null) {
                addParameter(part, method, getParameterFromPart(method, part, style));
            }
            index++;
        }
        // now from unlisted input parts
        for (MessagePartInfo part : inputUnlistedParts) {
            addParameter(part, method, getParameterFromPart(method, part, JavaType.Style.IN));
        }
        // now from unlisted output parts
        for (MessagePartInfo part : outputUnlistedParts) {
            addParameter(part, method, getParameterFromPart(method, part, JavaType.Style.INOUT));
        }
    }

    private boolean isSamePart(MessagePartInfo part1, MessagePartInfo part2) {
        QName qname1 = part1.getElementQName();
        QName qname2 = part2.getElementQName();
        QName tname1 = part1.getTypeQName();
        QName tname2 = part2.getTypeQName();
        if (qname1 != null && qname2 != null) {
            return qname1.equals(qname2) && (tname1 == null || tname1.equals(tname2));
        }
        if (tname1 != null && tname2 != null) {
            return tname1.equals(tname2);
        }
        return false;
    }

    private boolean isValidOrdering(List<String> parameterOrder,
                                    MessageInfo inputMessage, MessageInfo outputMessage) {
        Iterator<String> params = parameterOrder.iterator();

        List<MessagePartInfo> inputParts = inputMessage.getMessageParts();
        List<MessagePartInfo> outputParts;

        if (outputMessage != null) {
            outputParts = outputMessage.getMessageParts();
        } else {
            outputParts = new ArrayList<>();
        }

        while (params.hasNext()) {
            String param = params.next();
            MessagePartInfo inPart = null;
            MessagePartInfo outPart = null;
            for (MessagePartInfo part : inputParts) {
                if (param.equals(part.getName().getLocalPart())) {
                    inPart = part;
                    break;
                }
            }
            //check output parts
            for (MessagePartInfo part : outputParts) {
                if (param.equals(part.getName().getLocalPart())) {
                    outPart = part;
                    break;
                }
            }
            if (inPart == null && outPart == null) {
                return false;
            } else if (inPart != null
                && outPart != null) {
                if (inPart.isElement() != outPart.isElement()) {
                    return false;
                }
                if (inPart.isElement()
                    && !inPart.getElementQName().equals(outPart.getElementQName())) {
                    return false;
                } else if (!inPart.isElement()
                    && !inPart.getTypeQName().equals(outPart.getTypeQName())) {
                    return false;
                }
            }

        }
        return true;
    }

    private boolean isRefElement(MessagePartInfo outputPart, QName outElement) {
        OperationInfo wrappedOp = outputPart.getMessageInfo().getOperation().getUnwrappedOperation();
        MessagePartInfo mpart = wrappedOp.getOutput().getMessagePart(outElement);
        if (mpart == null) {
            return false;
        }
        return mpart.getProperty("isRefElement") != null;
    }
}
