/*
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors.
 *
 * 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.switchyard.component.jca.deploy;

import java.util.List;
import java.util.Properties;

import javax.xml.namespace.QName;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.ResourceAdapter;
import javax.transaction.TransactionManager;

import org.switchyard.component.jca.JCAMessages;
import org.switchyard.component.jca.JCAConstants;
import org.switchyard.component.jca.config.model.ActivationSpecModel;
import org.switchyard.component.jca.config.model.BatchCommitModel;
import org.switchyard.component.jca.config.model.ConnectionModel;
import org.switchyard.component.jca.config.model.ConnectionSpecModel;
import org.switchyard.component.jca.config.model.EndpointModel;
import org.switchyard.component.jca.config.model.InboundConnectionModel;
import org.switchyard.component.jca.config.model.InboundInteractionModel;
import org.switchyard.component.jca.config.model.InteractionSpecModel;
import org.switchyard.component.jca.config.model.JCABindingModel;
import org.switchyard.component.jca.config.model.ListenerModel;
import org.switchyard.component.jca.config.model.OutboundConnectionModel;
import org.switchyard.component.jca.config.model.OutboundInteractionModel;
import org.switchyard.component.jca.config.model.ProcessorModel;
import org.switchyard.component.jca.config.model.ResourceAdapterModel;
import org.switchyard.component.jca.endpoint.AbstractInflowEndpoint;
import org.switchyard.component.jca.processor.AbstractOutboundProcessor;
import org.switchyard.config.model.composite.BindingModel;
import org.switchyard.deploy.BaseActivator;
import org.switchyard.deploy.ServiceHandler;
import org.jboss.jca.core.spi.rar.Activation;
import org.jboss.jca.core.spi.rar.MessageListener;
import org.jboss.jca.core.spi.rar.ResourceAdapterRepository;
import org.jboss.util.propertyeditor.PropertyEditors;
import org.jboss.as.connector.util.ConnectorServices;


/**
 * Activator for JCA message inflow binding.
 * 
 * @author <a href="mailto:tm.igarashi@gmail.com">Tomohisa Igarashi</a>
 *
 */
public class JCAActivator extends BaseActivator {

    private static final String JBOSS_TRANSACTION_MANAGER = "java:jboss/TransactionManager";

    static final String[] TYPES = new String[] {"jca"};
    
    private final ClassLoader _appClassLoader;
    
    private TransactionManager _transactionManager;
    
    private ResourceAdapterRepository _raRepository;

    /**
     * Sole constructor .
     */
    public JCAActivator() {
        super(TYPES);
        _appClassLoader = Thread.currentThread().getContextClassLoader();
    }
    
    @Override
    public ServiceHandler activateBinding(QName name, BindingModel config) {
        if (_raRepository == null) {
            throw JCAMessages.MESSAGES.resourceAdapterRepositoryMustBeInjectedToActivateJCAComponent();
        }

        if (_transactionManager == null) {
            try {
                _transactionManager = (TransactionManager)new InitialContext().lookup(JBOSS_TRANSACTION_MANAGER);
            } catch (NamingException e) {
                throw JCAMessages.MESSAGES.unableToFindTransactionManagerInJNDIAt(JBOSS_TRANSACTION_MANAGER, e);
            }
        }

        if (config.isServiceBinding()) {
            return handleServiceBinding((JCABindingModel)config, name);
        } else {
            return handleReferenceBinding((JCABindingModel)config, name);
        }
    }

    @Override
    public void deactivateBinding(QName name, ServiceHandler handler) {
        // Nothing to do here
    }
    
    /**
     * set ResourceAdapterRepository.
     * 
     * @param repository ResourceAdapterRepository to set
     */
    public void setResourceAdapterRepository(ResourceAdapterRepository repository) {
        _raRepository = repository;
    }
    
    private InboundHandler handleServiceBinding(JCABindingModel config, QName name) {
        
        JCABindingModel jcaconfig = (JCABindingModel)config;
        InboundConnectionModel inboundConnectionModel = jcaconfig.getInboundConnection();
        if (inboundConnectionModel == null) {
            throw JCAMessages.MESSAGES.noInboundConnectionConfigured();
        }
        ResourceAdapterModel resourceAdapterModel = inboundConnectionModel.getResourceAdapter();
        if (resourceAdapterModel == null) {
            throw JCAMessages.MESSAGES.noResourceAdapterConfigured();
        }
        String raName = resourceAdapterModel.getName();
        if (raName == null) {
            throw JCAMessages.MESSAGES.noResourceAdapterNameConfigured();
        }
        String raid = ConnectorServices.getRegisteredResourceAdapterIdentifier(raName);
        if (raid == null && raName.endsWith(".rar")) {
            // AS7 registers adapter's name with stripping .rar suffix unlike WildFly
            raid = ConnectorServices.getRegisteredResourceAdapterIdentifier(stripDotRarSuffix(raName));
        }
        if (raid == null) {
            throw JCAMessages.MESSAGES.uniqueKeyForResourceAdapter(raName);
        }
        
        Properties raProps = resourceAdapterModel.getProperties();
        Properties activationProps = null;
        ActivationSpecModel activationSpecModel = inboundConnectionModel.getActivationSpec();
        if (activationSpecModel != null) {
            activationProps = activationSpecModel.getProperties();
        }
        InboundInteractionModel inboundInteractionModel = jcaconfig.getInboundInteraction();
        if (inboundInteractionModel == null) {
            throw JCAMessages.MESSAGES.noInboundInteractionConfigured();
        }
        
        String listener = null;
        ListenerModel listenerModel = inboundInteractionModel.getListener();
        if (listenerModel != null) {
            listener = listenerModel.getClassName();
        }
        Class<?> listenerType = null;
        try {
            listenerType = _appClassLoader.loadClass(listener != null ? listener : JCAConstants.DEFAULT_LISTENER_CLASS);
        } catch (Exception e) {
            throw JCAMessages.MESSAGES.noListenerClassFound(listener, e);
        }
        
        EndpointModel endpointModel = inboundInteractionModel.getEndpoint();
        if (endpointModel == null) {
            throw JCAMessages.MESSAGES.noEndpointConfigured();
        }
        String endpointClassName = endpointModel.getEndpointClassName(); 
        Properties endpointProps = endpointModel.getProperties();
        
        ActivationSpec activationSpec = null;
        ResourceAdapter resourceAdapter = null;
        MessageListener listenerContainer = null;
        try {
            List<MessageListener> listeners = _raRepository.getMessageListeners(raid);
            for (MessageListener l : listeners) {
                if (listenerType.equals(l.getType())) {
                    listenerContainer = l;
                }
            }
            if (listenerContainer == null) {
                throw JCAMessages.MESSAGES.listenerTypeIsNotSupportedByResourceAdapter(listenerType.getName(), raName);
            }
            
            Activation activation = listenerContainer.getActivation();
            activationSpec = activation.createInstance();
            if (activationProps != null && !activationProps.isEmpty()) {
                PropertyEditors.mapJavaBeanProperties(activationSpec, activationProps);
            }

            resourceAdapter = _raRepository.getResourceAdapter(raid);
            if (!raProps.isEmpty()) {
                PropertyEditors.mapJavaBeanProperties(resourceAdapter, raProps);
            }
        } catch (Exception e) {
            throw JCAMessages.MESSAGES.couldnTAcquireTheResourceAdapter(raName, e);
        }
        
         AbstractInflowEndpoint endpoint = null;
         Class<?> endpointClass = null;
         try {
             endpointClass = (Class<?>)_appClassLoader.loadClass(endpointClassName);
             endpoint = (AbstractInflowEndpoint) endpointClass.newInstance();
             if (!endpointProps.isEmpty()) {
                 PropertyEditors.mapJavaBeanProperties(endpoint, endpointProps);
             }
         } catch (Exception e) {
             throw JCAMessages.MESSAGES.endpointClass(endpointClassName, e);
         }

         boolean transacted = inboundInteractionModel.isTransacted();
         endpoint.setApplicationClassLoader(_appClassLoader)
                     .setServiceDomain(getServiceDomain())
                     .setServiceQName(name)
                     .setDeliveryTransacted(transacted)
                     .setJCABindingModel(jcaconfig);

        JCAInflowDeploymentMetaData inflowMetaData = new JCAInflowDeploymentMetaData()
                                                        .setActivationSpec(activationSpec)
                                                        .setApplicationClassLoader(_appClassLoader)
                                                        .setListenerInterface(listenerType)
                                                        .setMessageEndpoint(endpoint)
                                                        .setResourceAdapter(resourceAdapter)
                                                        .setTransactionManager(_transactionManager)
                                                        .setDeliveryTransacted(transacted);

        BatchCommitModel batchCommit = inboundInteractionModel.getBatchCommit();
        if (transacted && batchCommit != null) {
            inflowMetaData.setUseBatchCommit(true);
            inflowMetaData.setBatchTimeout(batchCommit.getBatchTimeout());
            inflowMetaData.setBatchSize(batchCommit.getBatchSize());
        }

        return new InboundHandler(inflowMetaData, getServiceDomain());
        
    }
    
    private OutboundHandler handleReferenceBinding(JCABindingModel config, QName name) {
        JCABindingModel jcaconfig = (JCABindingModel)config;
        OutboundConnectionModel outboundConnectionModel = jcaconfig.getOutboundConnection();
        if (outboundConnectionModel == null) {
            throw JCAMessages.MESSAGES.noOutboundConnectionConfigured();
        }
        boolean managed = outboundConnectionModel.isManaged();
        if (!managed) {
            throw JCAMessages.MESSAGES.nonManagedScenarioIsNotSupportedYet();
        }
        
        ResourceAdapterModel resourceAdapterModel = outboundConnectionModel.getResourceAdapter();
        if (resourceAdapterModel != null) {
            Properties raProps = resourceAdapterModel.getProperties();
            if (!raProps.isEmpty()) {
                String raName = resourceAdapterModel.getName();
                String raid = ConnectorServices.getRegisteredResourceAdapterIdentifier(stripDotRarSuffix(raName));
                if (raid == null) {
                    throw JCAMessages.MESSAGES.uniqueKeyForResourceAdapter(raName);
                }

                ResourceAdapter resourceAdapter = null;
                try {
                    resourceAdapter = _raRepository.getResourceAdapter(raid);
                    PropertyEditors.mapJavaBeanProperties(resourceAdapter, raProps);
                } catch (Exception e) {
                    throw JCAMessages.MESSAGES.couldnTAcquireTheResourceAdapter(raName, e);
                }
            }
        }

        OutboundInteractionModel outboundInteractionModel = jcaconfig.getOutboundInteraction();
        if (outboundInteractionModel == null) {
            throw JCAMessages.MESSAGES.noOutboundInteractionConfigured();
        }
        ProcessorModel processorModel = outboundInteractionModel.getProcessor();
        if (processorModel == null) {
            throw JCAMessages.MESSAGES.noProcessorConfigured();
        }
        Properties processorProps = processorModel.getProperties();
        AbstractOutboundProcessor processor = null;
        String processorClassName = processorModel.getProcessorClassName();
        Class<?> processorClass = null;
        try {
            processorClass = (Class<?>)_appClassLoader.loadClass(processorClassName);
            processor = (AbstractOutboundProcessor) processorClass.newInstance();
            if (!processorProps.isEmpty()) {
                PropertyEditors.mapJavaBeanProperties(processor, processorProps);
            }
        } catch (Exception e) {
            throw JCAMessages.MESSAGES.outboundProcessorClass(processorClassName, e);
        }
        
        ConnectionModel connectionModel = outboundConnectionModel.getConnection();
        if (connectionModel == null) {
            throw JCAMessages.MESSAGES.noConnectionConfigured();
        }
        String cfJndiName = connectionModel.getConnectionFactoryJNDIName();
        Properties connProps = connectionModel.getProperties();
        processor.setApplicationClassLoader(_appClassLoader)
                    .setMCFProperties(connProps)
                    .setConnectionFactoryJNDIName(cfJndiName)
                    .setJCABindingModel(jcaconfig);

        ConnectionSpecModel connectionSpecModel = outboundInteractionModel.getConnectionSpec();
        if (connectionSpecModel != null) {
            String connSpecClassName = connectionSpecModel.getConnectionSpecClassName();
            Properties connSpecProps = connectionSpecModel.getProperties();
            processor.setConnectionSpec(connSpecClassName, connSpecProps);
        }
        InteractionSpecModel interactionSpecModel = outboundInteractionModel.getInteractionSpec();
        if (interactionSpecModel != null) {
            String interactSpecClassName = interactionSpecModel.getInteractionSpecClassName();
            Properties interactSpecProps = interactionSpecModel.getProperties();
            processor.setInteractionSpec(interactSpecClassName, interactSpecProps);
        }

        return new OutboundHandler(processor, getServiceDomain());
    }
    
    private String stripDotRarSuffix(final String raName) {
        if (raName == null) {
            return null;
        }
        // See RaDeploymentParsingProcessor
        if (raName.endsWith(".rar")) {
            return raName.substring(0, raName.indexOf(".rar"));
        }
        return raName;
    }
    
}
