/*
*  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.apache.synapse.config.xml.rest;

import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axis2.Constants;
import org.apache.axiom.om.OMNode;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.SynapseException;
import org.apache.synapse.aspects.AspectConfiguration;
import org.apache.synapse.commons.util.PropertyHelper;
import org.apache.synapse.config.xml.XMLConfigConstants;
import org.apache.synapse.rest.API;
import org.apache.synapse.rest.Handler;
import org.apache.synapse.rest.RESTConstants;
import org.apache.synapse.rest.version.VersionStrategy;
import org.apache.synapse.util.CommentListUtil;

import javax.xml.namespace.QName;
import java.util.Iterator;
import java.util.Properties;

public class APIFactory {

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

    static final QName PROP_Q
        = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "property");

    static final QName ATT_NAME = new QName("name");

    static final QName ATT_VALUE   = new QName("value");

    public static API createAPI(OMElement apiElt) {
        return createAPI(apiElt, new Properties());
    }

    public static API createAPI(OMElement apiElt, Properties properties) {
        OMAttribute nameAtt = apiElt.getAttribute(new QName("name"));
        if (nameAtt == null || "".equals(nameAtt.getAttributeValue())) {
            handleException("Attribute 'name' is required for an API definition");
        }

        OMAttribute contextAtt = apiElt.getAttribute(new QName("context"));
        if (contextAtt == null || "".equals(contextAtt.getAttributeValue())) {
            handleException("Attribute 'context' is required for an API definition");
        }

        API api = new API(nameAtt.getAttributeValue(), contextAtt.getAttributeValue());

        OMAttribute hostAtt = apiElt.getAttribute(new QName("hostname"));
        if (hostAtt != null && !"".equals(hostAtt.getAttributeValue())) {
            api.setHost(hostAtt.getAttributeValue());
        }

        VersionStrategy vStrategy = VersionStrategyFactory.createVersioningStrategy(api, apiElt);

        api.setVersionStrategy(vStrategy);

        OMAttribute portAtt = apiElt.getAttribute(new QName("port"));
        if (portAtt != null && !"".equals(portAtt.getAttributeValue())) {
            api.setPort(Integer.parseInt(portAtt.getAttributeValue()));
        }

        OMAttribute publishSwagger = apiElt.getAttribute(new QName("publishSwagger"));
        if (publishSwagger != null) {
            api.setSwaggerResourcePath(publishSwagger.getAttributeValue());
        }

        Iterator resources = apiElt.getChildrenWithName(new QName(
                XMLConfigConstants.SYNAPSE_NAMESPACE, "resource"));
        boolean noResources = true;
        while (resources.hasNext()) {
            OMElement resourceElt = (OMElement) resources.next();
            api.addResource(ResourceFactory.createResource(resourceElt, properties));
            noResources = false;
        }

        if (noResources) {
            handleException("An API must contain at least one resource definition");
        }

        OMElement handlersElt = apiElt.getFirstChildWithName(new QName(
                XMLConfigConstants.SYNAPSE_NAMESPACE, "handlers"));
        if (handlersElt != null) {
            Iterator handlers = handlersElt.getChildrenWithName(new QName(
                    XMLConfigConstants.SYNAPSE_NAMESPACE, "handler"));
            while (handlers.hasNext()) {
                OMElement handlerElt = (OMElement) handlers.next();
                defineHandler(api, handlerElt);
            }
        }

        OMAttribute trans = apiElt.getAttribute(
                new QName(XMLConfigConstants.NULL_NAMESPACE, "transports"));
        if (trans != null) {
            String transports = trans.getAttributeValue();
            if (!"".equals(transports)) {
                if (Constants.TRANSPORT_HTTP.equals(transports)) {
                    api.setProtocol(RESTConstants.PROTOCOL_HTTP_ONLY);
                } else if (Constants.TRANSPORT_HTTPS.equals(transports)) {
                    api.setProtocol(RESTConstants.PROTOCOL_HTTPS_ONLY);
                } else {
                    handleException("Invalid protocol name: " + transports);
                }
            }
        }

        String nameString = api.getName();
        if (nameString == null || "".equals(nameString)) {
            nameString = SynapseConstants.ANONYMOUS_API;
        }
        AspectConfiguration aspectConfiguration = new AspectConfiguration(nameString);
        api.configure(aspectConfiguration);

        OMAttribute statistics = apiElt.getAttribute(
                new QName(XMLConfigConstants.NULL_NAMESPACE, XMLConfigConstants.STATISTICS_ATTRIB_NAME));
        if (statistics != null) {
            String statisticsValue = statistics.getAttributeValue();
            if (statisticsValue != null) {
                if (XMLConfigConstants.STATISTICS_ENABLE.equals(statisticsValue)) {
                    aspectConfiguration.enableStatistics();
                }
            }
        }

        OMAttribute tracing = apiElt.getAttribute(
                new QName(XMLConfigConstants.NULL_NAMESPACE, XMLConfigConstants.TRACE_ATTRIB_NAME));
        if (tracing != null) {
            String tracingValue = tracing.getAttributeValue();
            if (tracingValue != null) {
                if (XMLConfigConstants.TRACE_ENABLE.equals(tracingValue)) {
                    aspectConfiguration.enableTracing();
                }
            }
        }
        CommentListUtil.populateComments(apiElt, api.getCommentsList());
        return api;
    }

    private static void defineHandler(API api, OMElement handlerElt) {
        String handlerClass = handlerElt.getAttributeValue(new QName("class"));
        if (handlerClass == null || "".equals(handlerClass)) {
            handleException("A handler element must have a class attribute");
        }

        try {
            Class clazz = APIFactory.class.getClassLoader().loadClass(handlerClass);
            Handler handler = (Handler) clazz.newInstance();
            api.addHandler(handler);

            for (Iterator it = handlerElt.getChildrenWithName(PROP_Q); it.hasNext();) {
                OMElement child = (OMElement) it.next();

                String propName = child.getAttribute(ATT_NAME).getAttributeValue();
                if (propName == null) {
                    handleException(
                        "A Class mediator property must specify the name attribute");
                } else {
                    if (child.getAttribute(ATT_VALUE) != null) {
                        String value = child.getAttribute(ATT_VALUE).getAttributeValue();
                        handler.addProperty(propName, value);
                        PropertyHelper.setInstanceProperty(propName, value, handler);
                    } else {
                        OMNode omElt = child.getFirstElement();
                        if (omElt != null) {
                            handler.addProperty(propName, omElt);
                            PropertyHelper.setInstanceProperty(propName, omElt, handler);
                        } else {
                            handleException("A Class mediator property must specify " +
                                "name and value attributes, or a name and a child XML fragment");
                        }
                    }
                }
            }

        } catch (Exception e) {
            handleException("Error initializing API handler: " + handlerClass, e);
        }
    }

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

    private static void handleException(String msg, Exception e) {
        log.error(msg, e);
        throw new SynapseException(msg, e);
    }
}
