/**
 *  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.command;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMAttribute;
import org.apache.synapse.config.xml.SynapseXPathSerializer;
import org.apache.synapse.config.xml.SynapseXPathFactory;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.wso2.carbon.mediator.service.MediatorException;
import org.wso2.carbon.mediator.service.ui.AbstractMediator;
import org.jaxen.JaxenException;

import javax.xml.namespace.QName;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;

public class CommandMediator extends AbstractMediator {
    protected static final QName ATT_ACTION = new QName("action");
    protected static final QName ATT_CTXNAME = new QName("context-name");

    protected static final String RM_ACTION = "ReadMessage";
    protected static final String UM_ACTION = "UpdateMessage";
    protected static final String RC_ACTION = "ReadContext";
    protected static final String UC_ACTION = "UpdateContext";
    protected static final String RAUM_ACTION = "ReadAndUpdateMessage";
    protected static final String RAUC_ACTION = "ReadAndUpdateContext";

    private String command = null;

    private final Map<String, Object> staticSetterProperties = new HashMap<String, Object>();

    private final Map<String, SynapseXPath> messageSetterProperties = new HashMap<String, SynapseXPath>();

    private final Map<String, String> contextSetterProperties = new HashMap<String, String>();

    private final Map<String, String> contextGetterProperties = new HashMap<String, String>();

    private final Map<String, SynapseXPath> messageGetterProperties = new HashMap<String, SynapseXPath>();

    public String getCommand() {
        return command;
    }

    public void setCommand(String command) {
        this.command = command;
    }

    public void addStaticSetterProperty(String name, Object value) {
        this.staticSetterProperties.put(name, value);
    }

    public void addMessageSetterProperty(String name, SynapseXPath xpath) {
        this.messageSetterProperties.put(name, xpath);
    }

    public void addContextSetterProperty(String name, String ctxName) {
        this.contextSetterProperties.put(name, ctxName);
    }

    public void addContextGetterProperty(String name, String value) {
        this.contextGetterProperties.put(name, value);
    }

    public void addMessageGetterProperty(String name, SynapseXPath xpath) {
        this.messageGetterProperties.put(name, xpath);
    }

    public Map<String, Object> getStaticSetterProperties() {
        return this.staticSetterProperties;
    }

    public Map<String, SynapseXPath> getMessageSetterProperties() {
        return this.messageSetterProperties;
    }

    public Map<String, String> getContextSetterProperties() {
        return this.contextSetterProperties;
    }

    public Map<String, String> getContextGetterProperties() {
        return this.contextGetterProperties;
    }

    public Map<String, SynapseXPath> getMessageGetterProperties() {
        return this.messageGetterProperties;
    }

    public OMElement serialize(OMElement parent) {
        OMElement pojoCommand = fac.createOMElement("pojoCommand", synNS);
        saveTracingState(pojoCommand, this);

        if (command != null) {
            pojoCommand.addAttribute(fac.createOMAttribute(
                "name", nullNS, command));
        } else {
            throw new MediatorException("Invalid POJO Command mediator. The command class name is required");
        }

        for (String propName : getStaticSetterProperties().keySet()) {
            Object value = getStaticSetterProperties().get(propName);
            OMElement prop = fac.createOMElement(PROP_Q.getLocalPart(), synNS);
            prop.addAttribute(fac.createOMAttribute("name", nullNS, propName));

            if (value instanceof String) {
                prop.addAttribute(fac.createOMAttribute("value", nullNS, (String) value));
            } else if (value instanceof OMElement) {
                prop.addChild((OMElement) value);
            } else {
                throw new MediatorException("Unable to serialize the command " +
                    "mediator property with the naem " + propName + " : Unknown type");
            }

            if (getContextGetterProperties().containsKey(propName)) {
                prop.addAttribute(fac.createOMAttribute("context-name", nullNS,
                    getContextGetterProperties().get(propName)));
            } else if (getMessageGetterProperties().containsKey(propName)) {
                SynapseXPathSerializer.serializeXPath(
                    getMessageGetterProperties().get(propName), prop, "expression");
            }
            pojoCommand.addChild(prop);
        }

        for (String propName : getMessageSetterProperties().keySet()) {
            OMElement prop = fac.createOMElement(PROP_Q.getLocalPart(), synNS);
            prop.addAttribute(fac.createOMAttribute("name", nullNS, propName));
            SynapseXPathSerializer.serializeXPath(
                getMessageSetterProperties().get(propName), prop, "expression");

            if (getMessageGetterProperties().containsKey(propName)) {
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "ReadAndUpdateMessage"));
            } else if (getContextGetterProperties().containsKey(propName)) {
                prop.addAttribute(fac.createOMAttribute("context-name", nullNS,
                    getContextGetterProperties().get(propName)));
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "ReadMessage"));
            } else {
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "ReadMessage"));
            }
            pojoCommand.addChild(prop);
        }

        for (String propName : getContextSetterProperties().keySet()) {
            OMElement prop = fac.createOMElement(PROP_Q.getLocalPart(), synNS);
            prop.addAttribute(fac.createOMAttribute("name", nullNS, propName));
            prop.addAttribute(fac.createOMAttribute("context-name", nullNS,
                getContextSetterProperties().get(propName)));

            if (getContextGetterProperties().containsKey(propName)) {
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "ReadAndUpdateContext"));
            } else if (getMessageGetterProperties().containsKey(propName)) {
                SynapseXPathSerializer.serializeXPath(
                    getMessageGetterProperties().get(propName), prop, "expression");
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "ReadContext"));
            } else {
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "ReadContext"));
            }
            pojoCommand.addChild(prop);
        }

        for (String propName : getContextGetterProperties().keySet()) {
            if (!isSerialized(propName)) {
                String value = getContextGetterProperties().get(propName);
                OMElement prop = fac.createOMElement(PROP_Q.getLocalPart(), synNS);
                prop.addAttribute(fac.createOMAttribute("name", nullNS, propName));
                prop.addAttribute(fac.createOMAttribute("context-name", nullNS, value));
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "UpdateContext"));
                pojoCommand.addChild(prop);
            }
        }

        for (String propName : getMessageGetterProperties().keySet()) {
            if (!isSerialized(propName)) {
                OMElement prop = fac.createOMElement(PROP_Q.getLocalPart(), synNS);
                prop.addAttribute(fac.createOMAttribute("name", nullNS, propName));
                SynapseXPathSerializer.serializeXPath(
                    getMessageGetterProperties().get(propName), prop, "expression");
                prop.addAttribute(fac.createOMAttribute("action", nullNS, "UpdateMessage"));
                pojoCommand.addChild(prop);
            }
        }

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

     private boolean isSerialized(String propName) {
        return getContextSetterProperties().containsKey(propName) ||
            getStaticSetterProperties().containsKey(propName) ||
            getMessageSetterProperties().containsKey(propName);
    }

    public void build(OMElement elem) {
        OMAttribute name = elem.getAttribute(ATT_NAME);
        if (name == null) {
            String msg = "The name of the actual POJO command implementation class" +
                    " is a required attribute";
            throw new MediatorException(msg);
        }

        this.command = name.getAttributeValue();

        // setting the properties to the command. these properties will be instantiated
        // at the mediation time
        for (Iterator it = elem.getChildElements(); it.hasNext();) {
            OMElement child = (OMElement) it.next();
            if("property".equals(child.getLocalName())) {

                OMAttribute nameAttr = child.getAttribute(ATT_NAME);
                if (nameAttr != null && nameAttr.getAttributeValue() != null
                    && !"".equals(nameAttr.getAttributeValue())) {

                    handlePropertyAction(nameAttr.getAttributeValue(), child);
                } else {
                    throw new MediatorException("A POJO command mediator " +
                        "property must specify the name attribute");
                }
            }
        }
    }

    private void handlePropertyAction(String name, OMElement propElem) {

        OMAttribute valueAttr   = propElem.getAttribute(ATT_VALUE);
        OMAttribute exprAttr    = propElem.getAttribute(ATT_EXPRN);
        OMAttribute ctxNameAttr = propElem.getAttribute(ATT_CTXNAME);
        OMAttribute actionAttr  = propElem.getAttribute(ATT_ACTION);

        SynapseXPath xpath = null;
        try {
            if (exprAttr != null) {
                xpath = SynapseXPathFactory.getSynapseXPath(propElem, ATT_EXPRN);
            }
        } catch (JaxenException e) {
            throw new MediatorException("Error in building the expression as an SynapseXPath" + e);
        }

        // if there is a value attribute there is no action (action is implied as read value)
        if (valueAttr != null) {
            String value = valueAttr.getAttributeValue();
            // all other three attributes can not co-exists
            if (exprAttr != null && ctxNameAttr != null) {
                throw new MediatorException("Command properties can not contain all three 'value', " +
                    "'expression' and 'context-name' attributes. Only one or " +
                    "combination of two can be there.");
            } else {
                addStaticSetterProperty(name, value);
                if (exprAttr != null) {
                    // action ==> ReadValueAndUpdateMesssage
                    addMessageGetterProperty(name, xpath);
                } else if (ctxNameAttr != null) {
                    // action ==> ReadValueAndUpdateContext
                    addContextGetterProperty(name, ctxNameAttr.getAttributeValue());
                } // else the action ==> ReadValue
            }
        } else if (propElem.getFirstElement() != null) {
            // all other two attributes can not co-exists
            if (exprAttr != null && ctxNameAttr != null) {
                throw new MediatorException("Command properties can not contain all the " +
                    "'expression' and 'context-name' attributes with a child. Only one " +
                    "attribute of those can co-exists with a child");
            } else {
                addStaticSetterProperty(name, propElem.getFirstElement());
                if (exprAttr != null) {
                    // action ==> ReadValueAndUpdateMesssage
                    addMessageGetterProperty(name, xpath);
                } else if (ctxNameAttr != null) {
                    // action ==> ReadValueAndUpdateContext
                    addContextGetterProperty(name, ctxNameAttr.getAttributeValue());
                } // else the action ==> ReadValue
            }
        } else {
            // if both context-name and expression is there
            if (exprAttr != null && ctxNameAttr != null) {
                if (actionAttr != null && actionAttr.getAttributeValue() != null) {
                    String action = actionAttr.getAttributeValue();
                    if (RM_ACTION.equals(action) || UC_ACTION.equals(action)) {
                        // action ==> ReadMessageAndUpdateContext
                        addMessageSetterProperty(name, xpath);
                        addContextGetterProperty(name, ctxNameAttr.getAttributeValue());
                    } else if (RC_ACTION.equals(action) || UM_ACTION.equals(action)) {
                        // action ==> ReadContextAndUpdateMessage
                        addContextSetterProperty(name, ctxNameAttr.getAttributeValue());
                        addMessageGetterProperty(name, xpath);
                    } else {
                        throw new MediatorException("Invalid action for " +
                            "the command property with the name " + name);
                    }
                } else {
                    throw new MediatorException("Action attribute " +
                        "is required for the command property with name " + name);
                }
            } else {
                // only one of expression or context-name is present
                if (actionAttr != null && actionAttr.getAttributeValue() != null) {
                    String action = actionAttr.getAttributeValue();
                    if (exprAttr != null) {
                        if (RM_ACTION.equals(action)) {
                            // action ==> ReadMessage
                            addMessageSetterProperty(name, xpath);
                        } else if (UM_ACTION.equals(action)) {
                            // action ==> UpdateMessage
                            addMessageGetterProperty(name, xpath);
                        } else if (RAUM_ACTION.equals(action)) {
                            // action ==> ReadAndUpdateMessage
                            addMessageSetterProperty(name, xpath);
                            addMessageGetterProperty(name, xpath);
                        } else {
                            throw new MediatorException("Invalid action for " +
                                "the command property with the name " + name);
                        }
                    } else if (ctxNameAttr != null) {
                        String ctxName = ctxNameAttr.getAttributeValue();
                        if (RC_ACTION.equals(action)) {
                            // action ==> ReadContext
                            addContextSetterProperty(name, ctxName);
                        } else if (UC_ACTION.equals(action)) {
                            // action ==> UpdateContext
                            addContextGetterProperty(name, ctxName);
                        } else if (RAUC_ACTION.equals(action)) {
                            // action ==> ReadAndUpdateContext
                            addContextSetterProperty(name, ctxName);
                            addContextGetterProperty(name, ctxName);
                        } else {
                            throw new MediatorException("Invalid action for " +
                                "the command property with the name " + name);
                        }
                    } else {
                        throw new MediatorException("Unrecognized command property with the name " + name);
                    }
                } else {
                    // action ==> ReadAndUpdateMessage/Context
                    if (exprAttr != null) {
                        addMessageSetterProperty(name, xpath);
                        addMessageGetterProperty(name, xpath);
                    } else if (ctxNameAttr != null) {
                        String ctxName = ctxNameAttr.getAttributeValue();
                        addContextSetterProperty(name, ctxName);
                        addContextGetterProperty(name, ctxName);
                    } else {
                        throw new MediatorException("Unrecognized command property with the name " + name);
                    }
                }
            }
        }
    }

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