/*
 * Copyright (c) 2005 - 2014, 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.event.receiver.core.internal.util;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.core.util.CryptoException;
import org.wso2.carbon.core.util.CryptoUtil;
import org.wso2.carbon.databridge.commons.AttributeType;
import org.wso2.carbon.databridge.commons.StreamDefinition;
import org.wso2.carbon.event.input.adapter.core.*;
import org.wso2.carbon.event.receiver.core.config.EventReceiverConfiguration;
import org.wso2.carbon.event.receiver.core.config.EventReceiverConstants;
import org.wso2.carbon.event.receiver.core.InputMapperFactory;
import org.wso2.carbon.event.receiver.core.exception.EventReceiverConfigurationException;
import org.wso2.carbon.event.receiver.core.exception.EventReceiverStreamValidationException;
import org.wso2.carbon.event.receiver.core.exception.EventReceiverValidationException;
import org.wso2.carbon.event.receiver.core.internal.ds.EventReceiverServiceValueHolder;
import org.wso2.carbon.event.stream.core.EventStreamService;
import org.wso2.carbon.event.stream.core.exception.EventStreamConfigurationException;

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


public class EventReceiverConfigurationBuilder {

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

    public static OMElement eventReceiverConfigurationToOM(
            EventReceiverConfiguration eventReceiverConfiguration) {

        OMFactory factory = OMAbstractFactory.getOMFactory();
        OMElement eventReceiverConfigElement = factory.createOMElement(new QName(EventReceiverConstants.ER_ELEMENT_ROOT_ELEMENT));
        eventReceiverConfigElement.declareDefaultNamespace(EventReceiverConstants.ER_CONF_NS);

        eventReceiverConfigElement.addAttribute(EventReceiverConstants.ER_ATTR_NAME, eventReceiverConfiguration.getEventReceiverName(), null);
        if (eventReceiverConfiguration.isTraceEnabled()) {
            eventReceiverConfigElement.addAttribute(EventReceiverConstants.ER_ATTR_TRACE_ENABLED, EventReceiverConstants.ENABLE_CONST, null);
        } else {
            eventReceiverConfigElement.addAttribute(EventReceiverConstants.ER_ATTR_TRACE_ENABLED, EventReceiverConstants.DISABLE_CONST, null);
        }
        if (eventReceiverConfiguration.isStatisticsEnabled()) {
            eventReceiverConfigElement.addAttribute(EventReceiverConstants.ER_ATTR_STATISTICS_ENABLED, EventReceiverConstants.ENABLE_CONST, null);
        } else {
            eventReceiverConfigElement.addAttribute(EventReceiverConstants.ER_ATTR_STATISTICS_ENABLED, EventReceiverConstants.DISABLE_CONST, null);
        }

        //From properties
        OMElement fromOMElement = factory.createOMElement(new QName(
                EventReceiverConstants.ER_ELEMENT_FROM));
        fromOMElement.declareDefaultNamespace(EventReceiverConstants.ER_CONF_NS);
        InputEventAdapterConfiguration inputEventAdapterConfiguration = eventReceiverConfiguration.getFromAdapterConfiguration();
        fromOMElement.addAttribute(EventReceiverConstants.ER_ATTR_TA_TYPE, inputEventAdapterConfiguration.getType(), null);
        Map<String, String> inputPropertyMap = inputEventAdapterConfiguration.getProperties();
        if (inputPropertyMap != null) {
            for (Map.Entry<String, String> propertyEntry : inputPropertyMap.entrySet()) {
                if(propertyEntry.getValue() != null){
                    OMElement propertyElement = factory.createOMElement(new QName(EventReceiverConstants.ER_ELEMENT_PROPERTY));
                    propertyElement.declareDefaultNamespace(EventReceiverConstants.ER_CONF_NS);
                    propertyElement.addAttribute(EventReceiverConstants.ER_ATTR_NAME, propertyEntry.getKey(), null);
                    propertyElement.setText(propertyEntry.getValue());
                    fromOMElement.addChild(propertyElement);
                }
            }
        }
        eventReceiverConfigElement.addChild(fromOMElement);

        OMElement mappingOMElement = EventReceiverServiceValueHolder.getMappingFactoryMap().get(eventReceiverConfiguration.getInputMapping().getMappingType()).constructOMFromInputMapping(eventReceiverConfiguration.getInputMapping(), factory);
        mappingOMElement.declareDefaultNamespace(EventReceiverConstants.ER_CONF_NS);
        eventReceiverConfigElement.addChild(mappingOMElement);

        OMElement toOMElement = factory.createOMElement(new QName(EventReceiverConstants.ER_ELEMENT_TO));
        toOMElement.declareDefaultNamespace(EventReceiverConstants.ER_CONF_NS);
        toOMElement.addAttribute(EventReceiverConstants.ER_ATTR_STREAM_NAME, eventReceiverConfiguration.getToStreamName(), null);
        toOMElement.addAttribute(EventReceiverConstants.ER_ATTR_VERSION, eventReceiverConfiguration.getToStreamVersion(), null);

        eventReceiverConfigElement.addChild(toOMElement);
        try {
            String formattedXml = XmlFormatter.format(eventReceiverConfigElement.toString());
            eventReceiverConfigElement = AXIOMUtil.stringToOM(formattedXml);
        } catch (XMLStreamException e) {
            log.warn("Could not format OMElement properly." + eventReceiverConfigElement.toString());
        }

        return eventReceiverConfigElement;
    }

    public static EventReceiverConfiguration getEventReceiverConfiguration(
            OMElement eventReceiverConfigOMElement, String mappingType, boolean isEditable, int tenantId)
            throws EventReceiverConfigurationException {

        if (!eventReceiverConfigOMElement.getLocalName().equals(EventReceiverConstants.ER_ELEMENT_ROOT_ELEMENT)) {
            throw new EventReceiverConfigurationException("Root element is not an event receiver.");
        }
        String eventReceiverName = eventReceiverConfigOMElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_NAME));

        boolean traceEnabled = false;
        boolean statisticsEnabled = false;
        String traceEnabledAttribute = eventReceiverConfigOMElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_TRACE_ENABLED));
        if (traceEnabledAttribute != null && traceEnabledAttribute.equalsIgnoreCase(EventReceiverConstants.ENABLE_CONST)) {
            traceEnabled = true;
        }
        String statisticsEnabledAttribute = eventReceiverConfigOMElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_STATISTICS_ENABLED));
        if (statisticsEnabledAttribute != null && statisticsEnabledAttribute.equalsIgnoreCase(EventReceiverConstants.ENABLE_CONST)) {
            statisticsEnabled = true;
        }

        EventReceiverConfiguration eventReceiverConfiguration = new EventReceiverConfiguration();

        OMElement fromElement = eventReceiverConfigOMElement.getFirstChildWithName(new QName(EventReceiverConstants.ER_CONF_NS, EventReceiverConstants.ER_ELEMENT_FROM));
        OMElement mappingElement = eventReceiverConfigOMElement.getFirstChildWithName(new QName(EventReceiverConstants.ER_CONF_NS, EventReceiverConstants.ER_ELEMENT_MAPPING));
        OMElement toElement = eventReceiverConfigOMElement.getFirstChildWithName(new QName(EventReceiverConstants.ER_CONF_NS, EventReceiverConstants.ER_ELEMENT_TO));

        String fromEventAdapterType = fromElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_TA_TYPE));

        if (!validateEventAdapter(fromEventAdapterType)) {
            throw new EventReceiverValidationException("Event Adapter with type: " + fromEventAdapterType + " does not exist", fromEventAdapterType);
        }

        InputEventAdapterConfiguration inputEventAdapterConfiguration = getInputEventAdapterConfiguration(fromEventAdapterType, eventReceiverName, mappingType);

        Iterator fromElementPropertyIterator = fromElement.getChildrenWithName(
                new QName(EventReceiverConstants.ER_CONF_NS, EventReceiverConstants.ER_ELEMENT_PROPERTY)
        );
        

        while (fromElementPropertyIterator.hasNext()) {
            OMElement toElementProperty = (OMElement) fromElementPropertyIterator.next();
            String propertyName = toElementProperty.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_NAME));
            String propertyValue = toElementProperty.getText();

            OMAttribute encryptedAttribute = toElementProperty.getAttribute(new QName(EventReceiverConstants.ER_ATTR_ENCRYPTED));
            if (encryptedAttribute != null) {
                if ("true".equals(encryptedAttribute.getAttributeValue())) {
                    try {
                        propertyValue = new String(CryptoUtil.getDefaultCryptoUtil().base64DecodeAndDecrypt(propertyValue));
                    } catch (CryptoException e) {
                        log.error("Unable to decrypt the encrypted field: " + propertyName + " in adaptor: " + inputEventAdapterConfiguration.getName());
                        propertyValue = "";   // resetting the password if decryption is not possible.
                    }
                }
            }

            if (inputEventAdapterConfiguration.getProperties().containsKey(propertyName)) {
                inputEventAdapterConfiguration.getProperties().put(propertyName, propertyValue);
            } else {
                log.warn("To property " + propertyName + " with value " + propertyValue + " is dropped as its irrelevant of input adapter type:" + fromEventAdapterType);
            }
        }
        String fromStreamName="";
        String fromStreamVersion="";
        String customMappingEnabledAttribute = mappingElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_CUSTOM_MAPPING_ENABLED));
        if (mappingType.equalsIgnoreCase(EventReceiverConstants.ER_WSO2EVENT_MAPPING_TYPE) && customMappingEnabledAttribute != null && customMappingEnabledAttribute.equalsIgnoreCase(EventReceiverConstants.ENABLE_CONST)) {
            OMElement fromOMElement = mappingElement.getFirstChildWithName(new QName(EventReceiverConstants.ER_CONF_NS, EventReceiverConstants.ER_ELEMENT_FROM));
            fromStreamName = fromOMElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_STREAM_NAME));
            fromStreamVersion = fromOMElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_VERSION));
        }

        String toStreamName = toElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_STREAM_NAME));
        String toStreamVersion = toElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_VERSION));

        if(fromStreamName == null || fromStreamName.isEmpty() || fromStreamVersion == null ||fromStreamVersion.isEmpty()){
            inputEventAdapterConfiguration.setInputStreamIdOfWso2eventMessageFormat(toStreamName + EventReceiverConstants.STREAM_NAME_VER_DELIMITER + toStreamVersion);
        }else{
            inputEventAdapterConfiguration.setInputStreamIdOfWso2eventMessageFormat(fromStreamName + EventReceiverConstants.STREAM_NAME_VER_DELIMITER + fromStreamVersion);
        }

        if (mappingType.equalsIgnoreCase(EventReceiverConstants.ER_WSO2EVENT_MAPPING_TYPE)) {
            if (!validateSupportedMapping(fromEventAdapterType, MessageType.WSO2EVENT)) {
                throw new EventReceiverConfigurationException("Wso2 Event Mapping is not supported by event adapter type " + fromEventAdapterType);
            }
        } else if (mappingType.equalsIgnoreCase(EventReceiverConstants.ER_TEXT_MAPPING_TYPE)) {
            if (!validateSupportedMapping(fromEventAdapterType, MessageType.TEXT)) {
                throw new EventReceiverConfigurationException("Text Mapping is not supported by event adapter type " + fromEventAdapterType);
            }
        } else if (mappingType.equalsIgnoreCase(EventReceiverConstants.ER_MAP_MAPPING_TYPE)) {
            if (!validateSupportedMapping(fromEventAdapterType, MessageType.MAP)) {
                throw new EventReceiverConfigurationException("Mapping for Map input is not supported by event adapter type " + fromEventAdapterType);
            }
        } else if (mappingType.equalsIgnoreCase(EventReceiverConstants.ER_XML_MAPPING_TYPE)) {
            if (!validateSupportedMapping(fromEventAdapterType, MessageType.XML)) {
                throw new EventReceiverConfigurationException("XML Mapping is not supported by event adapter type " + fromEventAdapterType);
            }
            eventReceiverConfiguration = new EventReceiverConfiguration();
        } else if (mappingType.equalsIgnoreCase(EventReceiverConstants.ER_JSON_MAPPING_TYPE)) {
            if (!validateSupportedMapping(fromEventAdapterType, MessageType.JSON)) {
                throw new EventReceiverConfigurationException("JSON Mapping is not supported by event adapter type " + fromEventAdapterType);
            }
        } else {
            String factoryClassName = getMappingTypeFactoryClass(mappingElement);
            if (factoryClassName == null) {
                throw new EventReceiverConfigurationException("Corresponding mappingType " + mappingType + " is not valid");
            }

            Class factoryClass;
            try {
                factoryClass = Class.forName(factoryClassName);
                InputMapperFactory inputMapperFactory = (InputMapperFactory) factoryClass.newInstance();
                EventReceiverServiceValueHolder.getMappingFactoryMap().putIfAbsent(mappingType, inputMapperFactory);
                eventReceiverConfiguration = new EventReceiverConfiguration();
            } catch (ClassNotFoundException e) {
                throw new EventReceiverConfigurationException("Class not found exception occurred ", e);
            } catch (InstantiationException e) {
                throw new EventReceiverConfigurationException("Instantiation exception occurred ", e);
            } catch (IllegalAccessException e) {
                throw new EventReceiverConfigurationException("Illegal exception occurred ", e);
            }
        }

        if (!validateStreamDetails(toStreamName, toStreamVersion, tenantId)) {
            throw new EventReceiverStreamValidationException("Stream " + toStreamName + ":" + toStreamVersion + " does not exist", toStreamName + ":" + toStreamVersion);
        }

        eventReceiverConfiguration.setEventReceiverName(eventReceiverName);
        eventReceiverConfiguration.setTraceEnabled(traceEnabled);
        eventReceiverConfiguration.setStatisticsEnabled(statisticsEnabled);
        eventReceiverConfiguration.setToStreamName(toStreamName);
        eventReceiverConfiguration.setToStreamVersion(toStreamVersion);
        InputMapperFactory mapperFactory = EventReceiverServiceValueHolder.getMappingFactoryMap().get(mappingType);
        eventReceiverConfiguration.setInputMapping(mapperFactory.constructInputMappingFromOM(mappingElement));
        eventReceiverConfiguration.setFromAdapterConfiguration(inputEventAdapterConfiguration);
        eventReceiverConfiguration.setEditable(isEditable);
        return eventReceiverConfiguration;
    }


    public static InputEventAdapterConfiguration getInputEventAdapterConfiguration(
            String eventAdapterType, String receiverName, String messageFormat) {
        InputEventAdapterSchema schema = EventReceiverServiceValueHolder.getInputEventAdapterService().getInputEventAdapterSchema(eventAdapterType);
        InputEventAdapterConfiguration inputEventAdapterConfiguration = new InputEventAdapterConfiguration();
        inputEventAdapterConfiguration.setName(receiverName);
        inputEventAdapterConfiguration.setMessageFormat(messageFormat);
        inputEventAdapterConfiguration.setType(eventAdapterType);
        Map<String, String> staticProperties = new HashMap<String, String>();
        if (schema != null && schema.getPropertyList() != null) {
            for (Property property : schema.getPropertyList()) {
                staticProperties.put(property.getPropertyName(), property.getDefaultValue());
            }
        }
        inputEventAdapterConfiguration.setProperties(staticProperties);
        return inputEventAdapterConfiguration;
    }

    public static String getMappingTypeFactoryClass(OMElement omElement) {
        return omElement.getAttributeValue(new QName(EventReceiverConstants.ER_ATTR_FACTORY_CLASS));
    }

    private static boolean validateEventAdapter(String eventAdapterType) {

        InputEventAdapterService eventAdapterService = EventReceiverServiceValueHolder.getInputEventAdapterService();
        List<String> eventAdapterTypes = eventAdapterService.getInputEventAdapterTypes();

        if (eventAdapterTypes == null || eventAdapterTypes.size() == 0) {
            throw new EventReceiverValidationException("Event adapter with type: " + eventAdapterType + " does not exist", eventAdapterType);
        }

        Iterator<String> eventAdaIteratorTypeIterator = eventAdapterTypes.iterator();
        for (; eventAdaIteratorTypeIterator.hasNext(); ) {
            String adapterType = eventAdaIteratorTypeIterator.next();
            if (adapterType.equals(eventAdapterType)) {
                return true;
            }
        }

        return false;
    }

    public static String getAttributeType(AttributeType attributeType) {
        Map<String, AttributeType> attributeMap = EventReceiverConstants.STRING_ATTRIBUTE_TYPE_MAP;
        for (Map.Entry<String, AttributeType> entry : attributeMap.entrySet()) {
            if (entry.getValue().equals(attributeType)) {
                return entry.getKey();
            }
        }
        return null;
    }

    public static boolean validateSupportedMapping(String eventAdapterType,
                                                   String messageType) {

        InputEventAdapterService eventAdapterService = EventReceiverServiceValueHolder.getInputEventAdapterService();
        InputEventAdapterSchema eventAdapterSchema = eventAdapterService.getInputEventAdapterSchema(eventAdapterType);

        if (eventAdapterSchema == null) {
            throw new EventReceiverValidationException("Event Adapter with type: " + eventAdapterType + " does not exist", eventAdapterType);
        }
        List<String> supportedInputMessageFormats = eventAdapterSchema.getSupportedMessageFormats();
        return supportedInputMessageFormats.contains(messageType);
    }


    private static boolean validateStreamDetails(String streamName, String streamVersion,
                                                 int tenantId)
            throws EventReceiverConfigurationException {

        EventStreamService eventStreamService = EventReceiverServiceValueHolder.getEventStreamService();
        try {
            StreamDefinition streamDefinition = eventStreamService.getStreamDefinition(streamName, streamVersion);
            if (streamDefinition != null) {
                return true;
            }
        } catch (EventStreamConfigurationException e) {
            throw new EventReceiverConfigurationException("Error while validating stream definition with store : " + e.getMessage(), e);
        }
        return false;

    }

}
