/**
 *  Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  Licensed 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.wso2.carbon.mediator.xquery;

import net.sf.saxon.javax.xml.xquery.XQItemType;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.synapse.config.xml.*;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;
import org.wso2.carbon.mediator.service.MediatorException;
import org.wso2.carbon.mediator.service.ui.AbstractListMediator;
import org.wso2.carbon.mediator.service.util.MediatorProperty;
import org.apache.synapse.mediators.Value;
import org.apache.synapse.config.xml.ValueSerializer;
import org.apache.synapse.config.xml.ValueFactory;

import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class XQueryMediator extends AbstractListMediator {
    public static final QName ATT_NAME_Q = new QName(XMLConfigConstants.NULL_NAMESPACE, "name");
    public static final QName ATT_VALUE_Q = new QName(XMLConfigConstants.NULL_NAMESPACE, "value");
    public static final QName ATT_EXPR_Q = new QName(XMLConfigConstants.NULL_NAMESPACE, "expression");
    public static final QName ATT_KEY_Q = new QName(XMLConfigConstants.NULL_NAMESPACE, "key");
    public static final QName ATT_TYPE_Q = new QName(XMLConfigConstants.NULL_NAMESPACE, "type");

    private Value queryKey = null;
    private String querySource = null;
    private SynapseXPath target = null;
    private final List<MediatorProperty> dataSourceProperties = new ArrayList<MediatorProperty>();
    private final List<Variable> variables = new ArrayList<Variable>();

    public String getTagLocalName() {
        return "xquery";
    }

    public OMElement serialize(OMElement parent) {
        OMElement xquery = fac.createOMElement("xquery", synNS);

        if (queryKey != null) {
            // Use KeySerializer to serialize Key
            ValueSerializer keySerializer =  new ValueSerializer();
            keySerializer.serializeValue(queryKey, XMLConfigConstants.KEY, xquery);
        }

        saveTracingState(xquery, this);

        if (target != null) {
            SynapseXPathSerializer.serializeXPath(target, xquery, "target");
        }
        
        if (dataSourceProperties != null && !dataSourceProperties.isEmpty()) {
            OMElement dataSource = fac.createOMElement("dataSource", synNS);
            serializeMediatorProperties(dataSource, dataSourceProperties, PROP_Q);
            xquery.addChild(dataSource);
        }

        if (variables != null && !variables.isEmpty()) {
            for (Variable variable : variables) {
                if (variable.getVariableType() == Variable.BASE_VARIABLE) {
                    QName name = variable.getName();
                    Object value = variable.getValue();
                    if (name != null && value != null) {
                        OMElement baseElement = fac.createOMElement("variable", synNS);
                        baseElement.addAttribute(fac.createOMAttribute(
                                "name", nullNS, name.getLocalPart()));
                        baseElement.addAttribute(fac.createOMAttribute(
                                "value", nullNS, (String) value));
                        String type = null;
                        int varibelType = variable.getType();
                        if (XQItemType.XQBASETYPE_INT == varibelType) {
                            type = "INT";
                        } else if (XQItemType.XQBASETYPE_INTEGER == varibelType) {
                            type = "INTEGER";
                        } else if (XQItemType.XQBASETYPE_BOOLEAN == varibelType) {
                            type = "BOOLEAN";
                        } else if (XQItemType.XQBASETYPE_BYTE == varibelType) {
                            type = "BYTE";
                        } else if (XQItemType.XQBASETYPE_DOUBLE == varibelType) {
                            type = "DOUBLE";
                        } else if (XQItemType.XQBASETYPE_SHORT == varibelType) {
                            type = "SHORT";
                        } else if (XQItemType.XQBASETYPE_LONG == varibelType) {
                            type = "LONG";
                        } else if (XQItemType.XQBASETYPE_FLOAT == varibelType) {
                            type = "FLOAT";
                        } else if (XQItemType.XQBASETYPE_STRING == varibelType) {
                            type = "STRING";
                        } else if (XQItemType.XQITEMKIND_DOCUMENT == varibelType) {
                            type = "DOCUMENT";
                        } else if (XQItemType.XQITEMKIND_DOCUMENT_ELEMENT == varibelType) {
                            type = "DOCUMENT_ELEMENT";
                        } else if (XQItemType.XQITEMKIND_ELEMENT == varibelType) {
                            type = "ELEMENT";
                        } else {
                            throw new MediatorException("Unknown Type " + varibelType);
                        }
                        if (type != null) {
                            baseElement.addAttribute(fac.createOMAttribute(
                                    "type", nullNS, type));

                        }
                        xquery.addChild(baseElement);
                    }
                } else if (variable.getVariableType() == Variable.CUSTOM_VARIABLE) {
                    QName name = variable.getName();
                    if (name != null) {
                        OMElement customElement = fac.createOMElement("variable", synNS);
                        customElement.addAttribute(fac.createOMAttribute(
                                "name", nullNS, name.getLocalPart()));
                        String regkey = variable.getRegKey();
                        if (regkey != null) {
                            customElement.addAttribute(fac.createOMAttribute(
                                    "key", nullNS, regkey));
                        }
                        SynapseXPath expression = variable.getExpression();
                        if (expression != null) {
                            SynapseXPathSerializer.serializeXPath(expression,
                                    customElement, "expression");
                        }
                        String type = null;
                        int varibelType = variable.getType();
                        if (XQItemType.XQITEMKIND_DOCUMENT == varibelType) {
                            type = "DOCUMENT";
                        } else if (XQItemType.XQITEMKIND_DOCUMENT_ELEMENT == varibelType) {
                            type = "DOCUMENT_ELEMENT";
                        } else if (XQItemType.XQITEMKIND_ELEMENT == varibelType) {
                            type = "ELEMENT";
                        } else if (XQItemType.XQBASETYPE_INT == varibelType) {
                            type = "INT";
                        } else if (XQItemType.XQBASETYPE_INTEGER == varibelType) {
                            type = "INTEGER";
                        } else if (XQItemType.XQBASETYPE_BOOLEAN == varibelType) {
                            type = "BOOLEAN";
                        } else if (XQItemType.XQBASETYPE_BYTE == varibelType) {
                            type = "BYTE";
                        } else if (XQItemType.XQBASETYPE_DOUBLE == varibelType) {
                            type = "DOUBLE";
                        } else if (XQItemType.XQBASETYPE_SHORT == varibelType) {
                            type = "SHORT";
                        } else if (XQItemType.XQBASETYPE_LONG == varibelType) {
                            type = "LONG";
                        } else if (XQItemType.XQBASETYPE_FLOAT == varibelType) {
                            type = "FLOAT";
                        } else if (XQItemType.XQBASETYPE_STRING == varibelType) {
                            type = "STRING";
                        } else {
                            throw new MediatorException("Unknown Type " + varibelType);
                        }
                        if (type != null) {
                            customElement.addAttribute(fac.createOMAttribute(
                                    "type", nullNS, type));

                        }
                        xquery.addChild(customElement);
                    }
                }
            }
        }
        if (parent != null) {
            parent.addChild(xquery);
        }
        return xquery;
    }

    public void build(OMElement elem) {
        OMAttribute xqueryKey = elem.getAttribute(new QName(XMLConfigConstants.NULL_NAMESPACE,
                "key"));
        OMAttribute attrTarget = elem.getAttribute(new QName(XMLConfigConstants.NULL_NAMESPACE,
                "target"));
        if (xqueryKey != null) {
            //Use KeyFactory to create Key
            ValueFactory keyFactory = new ValueFactory();
            queryKey = keyFactory.createValue(XMLConfigConstants.KEY, elem);
        } else {
            throw new MediatorException("The 'key' attribute is required for the XQuery mediator");
        }
        if (attrTarget != null) {
            String targetValue = attrTarget.getAttributeValue();
            if (targetValue != null && !"".equals(targetValue)) {
                try {
                    this.querySource = targetValue;
                    this.target = SynapseXPathFactory.getSynapseXPath(elem, ATT_TARGET);
                } catch (JaxenException e) {
                    throw new MediatorException("Invalid XPath specified for the target attribute : " +
                            targetValue);
                }
            }
        }
        // after successfully creating the mediator
        // set its common attributes such as tracing etc
        processAuditStatus(this, elem);
        OMElement dataSource = elem.getFirstChildWithName(
                new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "dataSource"));
        if (dataSource != null) {
            dataSourceProperties.addAll(getMediatorProperties(dataSource));
        }

        Iterator it = elem.getChildrenWithName(new QName(XMLConfigConstants.SYNAPSE_NAMESPACE,
                "variable"));
        while (it.hasNext()) {
            OMElement variableOM = (OMElement) it.next();
            String name = variableOM.getAttributeValue(ATT_NAME_Q);
            if (name != null && !"".equals(name)) {
                String type = variableOM.getAttributeValue(ATT_TYPE_Q);
                if (type != null && !"".equals(type)) {
                    String value = variableOM.getAttributeValue(ATT_VALUE_Q);
                    Variable variable;
                    if (value != null && !"".equals(value)) {
                        variable = new Variable(new QName(name.trim()));
                        variable.setValue(value.trim());
                        variable.setType(Variable.BASE_VARIABLE);
                    } else {
                        String key = variableOM.getAttributeValue(ATT_KEY_Q);
                        String expr = variableOM.getAttributeValue(ATT_EXPR_Q);
                        variable = new Variable(new QName(name.trim()));
                        variable.setVariableType(Variable.CUSTOM_VARIABLE);
                        if (key != null) {
                            variable.setRegKey(key.trim());
                        }
                        if (expr != null && !"".equals(expr)) {
                            try {
                                variable.setExpression(SynapseXPathFactory.getSynapseXPath(
                                        variableOM, MediatorProperty.ATT_EXPR_Q));
                            } catch (JaxenException e) {
                                throw new MediatorException("Invalid XPath specified for" +
                                        " the expression attribute : " + expr);
                            }
                        }
                    }
                    if ("INT".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_INT);
                    } else if ("INTEGER".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_INTEGER);
                    } else if ("BOOLEAN".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_BOOLEAN);
                    } else if ("BYTE".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_BYTE);
                    } else if ("DOUBLE".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_DOUBLE);
                    } else if ("SHORT".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_SHORT);
                    } else if ("LONG".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_LONG);
                    } else if ("FLOAT".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_FLOAT);
                    } else if ("STRING".equals(type.trim())) {
                        variable.setType(XQItemType.XQBASETYPE_STRING);
                    } else if ("DOCUMENT".equals(type.trim())) {
                        variable.setType(XQItemType.XQITEMKIND_DOCUMENT);
                    } else if ("DOCUMENT_ELEMENT".equals(type.trim())) {
                        variable.setType(XQItemType.XQITEMKIND_DOCUMENT_ELEMENT);
                    } else if ("ELEMENT".equals(type.trim())) {
                        variable.setType(XQItemType.XQITEMKIND_ELEMENT);
                    } else {
                        throw new MediatorException("Unsupported Type");
                    }
                    variables.add(variable);
                }
            }
        }
    }

    public Value getQueryKey() {
        return queryKey;
    }

    public void setQueryKey(Value queryKey) {
        this.queryKey = queryKey;
    }

    public String getQuerySource() {
        return querySource;
    }

    public void setQuerySource(String querySource) {
        this.querySource = querySource;
    }

    public void addAllVariables(List<Variable> list) {
        this.variables.addAll(list);
    }

    public void addVariable(Variable variable) {
        this.variables.add(variable);
    }

    public List<MediatorProperty> getDataSourceProperties() {
        return dataSourceProperties;
    }

    public List<Variable> getVariables() {
        return variables;
    }

    public SynapseXPath getTarget() {
        return target;
    }

    public void setTarget(SynapseXPath source) {
        this.target = source;
    }

    public void addAllDataSourceProperties(List<MediatorProperty> list) {
        this.dataSourceProperties.addAll(list);
    }
}
