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

import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.config.xml.SynapseJsonPathFactory;
import org.apache.synapse.config.xml.SynapseXPathFactory;
import org.apache.synapse.config.xml.SynapseXPathSerializer;
import org.apache.synapse.config.xml.XMLConfigConstants;
import org.apache.synapse.util.xpath.SynapseJsonPath;
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.AbstractMediator;

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


public class PayloadFactoryMediator extends AbstractMediator {

    private static Log log = LogFactory.getLog(PayloadFactoryMediator.class);

    private static final String PAYLOAD_FACTORY = "payloadFactory";
    private static final String FORMAT = "format";
    private static final String ARGS = "args";
    private static final String ARG = "arg";
    private static final String VALUE = "value";
    private static final String EXPRESSION = "expression";
    private static final String EVAL = "evaluator";
    private static final String LITERAL = "literal";
    private static final String TYPE = "media-type";
    private static final String ESCAPEXMLCHARS = "escapeXmlChars";

    private static final QName FORMAT_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "format");
    private static final QName ARGS_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "args");
    protected static final QName ATT_KEY = new QName("key");
    protected static final QName ATT_EVAL   = new QName("evaluator");
    protected static final QName ATT_LITERAL   = new QName("literal");
    protected static final QName ATT_MEDIA   = new QName("media-type");
    protected static final QName ATT_ESCAPE_JSON   = new QName("escapeXmlChars");

    private final String JSON_TYPE="json";
    private final String XML_TYPE="xml";
    private final String TEXT_TYPE="text";

    private String format = null;
    private String type = null;
    private boolean dynamic = false;
    private String formatKey = null;
    private boolean escapeXmlChars = false;
    private List<Argument> argumentList = new ArrayList<Argument>();

    /// This the error private String evaluator = null;

    public OMElement serialize(OMElement parent) {

        OMElement payloadFactoryElem = fac.createOMElement(PAYLOAD_FACTORY, synNS);
        if(type!=null){
            payloadFactoryElem.addAttribute(fac.createOMAttribute(TYPE, nullNS,type));
        }
        if (isEscapeXmlChars()) {
            payloadFactoryElem.addAttribute(fac.createOMAttribute(ESCAPEXMLCHARS, nullNS,
                    Boolean.toString(isEscapeXmlChars())));
        }
        saveTracingState(payloadFactoryElem, this);

        if (!dynamic) {
            if (format != null) {
                try {
                    OMElement formatElem = fac.createOMElement(FORMAT, synNS);
                    if(type!=null && (type.contains(JSON_TYPE) || type.contains(TEXT_TYPE))){
                        formatElem.setText(format);
                    } else{
                        formatElem.addChild(AXIOMUtil.stringToOM(format));
                    }
                    payloadFactoryElem.addChild(formatElem);
                } catch (XMLStreamException e) {
                    handleException("Error while serializing payloadFactory mediator", e);
                }
            } else {
                handleException("Invalid payloadFactory mediator, format is required");
            }
        } else {
            if (formatKey != null) {
                OMElement formatElem = fac.createOMElement(FORMAT, synNS);
                formatElem.addAttribute(fac.createOMAttribute(
                        "key", nullNS, formatKey));
                payloadFactoryElem.addChild(formatElem);
            } else {
                handleException("Invalid payloadFactory mediator, format is required");
            }
        }

        if (argumentList != null && argumentList.size() > 0) {

            OMElement argumentsElem = fac.createOMElement(ARGS, synNS);

            for (Argument arg : argumentList) {

                OMElement argElem = fac.createOMElement(ARG, synNS);

                if (arg.isLiteral()) {
                    argElem.addAttribute(fac.createOMAttribute(LITERAL, nullNS, "true"));
                } else {
                    argElem.addAttribute(fac.createOMAttribute(LITERAL, nullNS, "false"));
                }

                if (arg.getValue() != null) {
                    argElem.addAttribute(fac.createOMAttribute(VALUE, nullNS, arg.getValue()));
                } else if (arg.getExpression() != null) {
                        SynapseXPathSerializer.serializeXPath(arg.getExpression(), argElem, EXPRESSION);
                } else if(arg.getJsonPath()!=null){
                    argElem.addAttribute(fac.createOMAttribute(EXPRESSION, nullNS, arg.getJsonPath().getExpression()));
                }
                 //error happens somewhere here
                if (arg.getEvaluator() != null) {
                    argElem.addAttribute(fac.createOMAttribute(EVAL, nullNS, arg.getEvaluator()));
                }
                argumentsElem.addChild(argElem);

            }
            payloadFactoryElem.addChild(argumentsElem);
        }

        if (parent != null) {
            parent.addChild(payloadFactoryElem);
        }

        return payloadFactoryElem;
    }

    public void build(OMElement elem) {

        OMAttribute mediaType = elem.getAttribute(ATT_MEDIA);
        if(mediaType!=null){
            this.type=mediaType.getAttributeValue();
        }
        OMAttribute escapeXmlChar = elem.getAttribute(ATT_ESCAPE_JSON);
        if (escapeXmlChar != null) {
            this.setEscapeXmlChars(Boolean.parseBoolean(escapeXmlChar.getAttributeValue()));
        }
        OMElement formatElem = elem.getFirstChildWithName(FORMAT_Q);

        if (formatElem != null) {
            OMAttribute n = formatElem.getAttribute(ATT_KEY);
            if (n == null) {
                if(type!=null && (type.contains(JSON_TYPE) || type.contains(TEXT_TYPE))){
                    this.format = formatElem.getText();
                } else{
                    this.format = formatElem.getFirstElement().toString();
                }
            } else {
                this.formatKey = n.getAttributeValue();
                this.dynamic = true;
            }
        } else {
            handleException("format element of payloadFactoryMediator is required");
        }

        OMElement argumentsElem = elem.getFirstChildWithName(ARGS_Q);

        if (argumentsElem != null) {

            Iterator itr = argumentsElem.getChildElements();

            while (itr.hasNext()) {
                OMElement argElem = (OMElement) itr.next();
                Argument arg = new Argument();
                String attrValue;
                String isLiteral;

                if ((isLiteral = argElem.getAttributeValue(ATT_LITERAL)) != null) {
                    if (isLiteral.equalsIgnoreCase("false")) {
                        arg.setLiteral(false);
                    } else {
                        arg.setLiteral(true);
                    }
                }

                if ((attrValue = argElem.getAttributeValue(ATT_VALUE)) != null) {

                    arg.setValue(attrValue);
                    argumentList.add(arg);
                } else if ((attrValue = argElem.getAttributeValue(ATT_EXPRN)) != null) {
                    if (attrValue.trim().length() == 0) {
                        handleException("Attribute value for expression cannot be empty");
                    } else {
                        try {
                            String evaluator;

                            if((evaluator=argElem.getAttributeValue(ATT_EVAL))!=null && evaluator.contains(JSON_TYPE)){

                                arg.setEvaluator(evaluator);
                                arg.setJsonPath(SynapseJsonPathFactory.getSynapseJsonPath(attrValue));
                                argumentList.add(arg);

                            } else{

                                if(evaluator!=null && evaluator.equals(XML_TYPE)){
                                    arg.setEvaluator(XML_TYPE);
                                }
                                arg.setExpression(SynapseXPathFactory.getSynapseXPath(argElem, ATT_EXPRN));
                                argumentList.add(arg);
                            }

                        } catch (JaxenException e) {
                            handleException("Invalid XPath expression for attribute expression : " +
                                    attrValue, e);
                        }
                    }

                } else {
                    handleException("Unsupported arg type or expression attribute required");
                }

                  ///argumentList.add(arg); values and args all get added to this.
                // need to change this part of the cord.
            }
        }

        // set its common attributes such as tracing etc
        processAuditStatus(this, elem);
    }

    public boolean isDynamic() {
        return dynamic;
    }

    public void setDynamic(boolean dynamic) {
        this.dynamic = dynamic;
    }

    public String getFormatKey() {
        return formatKey;
    }

    public void setFormatKey(String formatKey) {
        this.formatKey = formatKey;
    }

    public String getFormat() {
        return format;
    }

    public String getType(){
        return type;
    }

    public void setType(String type){
        this.type=type;
    }

    public void addArgument(Argument arg) {
        argumentList.add(arg);
    }

    public List<Argument> getArgumentList() {
        return argumentList;
    }

    public String getTagLocalName() {
        return PAYLOAD_FACTORY;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    private void handleException(String msg) {
        log.error(msg);
        throw new MediatorException(msg);
    }

    private void handleException(String msg, Exception ex) {
        log.error(msg, ex);
        throw new MediatorException(msg + " Caused by " + ex.getMessage());
    }

    public boolean isEscapeXmlChars() {
        return escapeXmlChars;
    }

    public void setEscapeXmlChars(boolean escapeXmlChars) {
        this.escapeXmlChars = escapeXmlChars;
    }

    public static class Argument {

        private String value;
        private SynapseXPath expression;
        private SynapseJsonPath jsonPath;
        private String evaluator;
        private boolean literal = false;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public SynapseXPath getExpression() {
            return expression;
        }

        public void setExpression(SynapseXPath expression) {
            this.expression = expression;
        }

        public SynapseJsonPath getJsonPath() {
            return jsonPath;
        }

        public void setJsonPath(SynapseJsonPath jsonPath) {
            this.jsonPath = jsonPath;
        }

        public String getEvaluator() {
            return evaluator;
        }

        public void setEvaluator(String evaluator) {
            this.evaluator = evaluator;
        }

        public boolean isLiteral() {
            return literal;
        }

        public void setLiteral(boolean literal) {
            this.literal = literal;
        }
    }
}
