/*
 * 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.camel.common.handler;

import java.net.URI;

import javax.xml.namespace.QName;

import org.apache.camel.model.RouteDefinition;
import org.switchyard.Exchange;
import org.switchyard.ServiceDomain;
import org.switchyard.SwitchYardException;
import org.switchyard.common.camel.SwitchYardCamelContext;
import org.switchyard.component.camel.common.CamelConstants;
import org.switchyard.component.camel.common.CommonCamelLogger;
import org.switchyard.component.camel.common.CommonCamelMessages;
import org.switchyard.component.camel.common.model.CamelBindingModel;
import org.switchyard.component.camel.common.transaction.TransactionHelper;
import org.switchyard.deploy.BaseServiceHandler;
import org.switchyard.deploy.internal.Deployment;
import org.switchyard.runtime.event.ExchangeCompletionEvent;

/**
 * An ExchangeHandler that acts as a gateway/entrypoint for Camel Components.
 * 
 * This gives access to all component of Apache Camel and works by creating a
 * Camel route that looks something like this 
 * <pre>
 * from("CamelComponentURI").to("switchyard://serviceName"); 
 * </pre>
 * 
 * @author Daniel Bevenius
 * 
 * @param <T> Type of the binding.
 */
public class InboundHandler<T extends CamelBindingModel> extends BaseServiceHandler {

    private static final String NAMESPACE_POLICY_REF = "org.switchyard.namespaceContextPolicy";

    private final T _camelBindingModel;
    private final SwitchYardCamelContext _camelContext;
    private final QName _serviceName;

    /**
     * Sole constructor.
     * 
     * @param camelBindingModel The {@link CamelBindingModel}.
     * @param camelContext The camel context instance.
     * @param serviceName The target service name.
     * @param domain the service domain.
     */
    public InboundHandler(final T camelBindingModel, final SwitchYardCamelContext camelContext, final QName serviceName, final ServiceDomain domain) {
        super(domain);
        _camelBindingModel = camelBindingModel;
        _camelContext = camelContext;
        _serviceName = serviceName;

        Object disable = domain != null ? domain.getProperty(Deployment.DISABLE_AUTO_STARTUP_PROPERTY) : null;
        if (disable == null || !Boolean.parseBoolean((String)disable)) {
            try {
                _camelContext.addRouteDefinition(createRouteDefinition());
            } catch (final Exception e) {
                throw new SwitchYardException(e);
            }
        }
    }

    /**
     * Creates route definition applicable for actual camel binding model.
     * 
     * @return Route definition handling given binding.
     */
    protected RouteDefinition createRouteDefinition() {
        final RouteDefinition route = new RouteDefinition();

        route.routeId(getRouteId()).from(getComponentUri().toString());
        addTransactionPolicy(route);
        addNamespacePolicy(route);
        
        route.setProperty(ExchangeCompletionEvent.GATEWAY_NAME).simple(getBindingModel().getName(), String.class)
            .setProperty(CamelConstants.APPLICATION_NAMESPACE).constant(_serviceName.getNamespaceURI())
            .process(new MessageComposerProcessor(getBindingModel()))
            .process(new OperationSelectorProcessor(getServiceName(), getBindingModel()))
            .to(getSwitchyardEndpointUri());
        
        return route;
    }

    /**
     * Get route id for given binding.
     * 
     * @return Camel route id.
     */
    public String getRouteId() {
        return getBindingModel().getClass().getSimpleName() + "/" + getServiceName().getLocalPart() + "@"
                + getBindingModel().getName() + "#" + getBindingModel().getComponentURI().hashCode();
    }

    protected QName getServiceName() {
        return _serviceName;
    }

    /**
     * Add transacted DSL element to route if necessary.
     * 
     * This method checks if component uri contains transaction manager reference
     * and puts 
     * 
     * @param route Route definition.
     * @param componentURI Component uri.
     * @return 
     */
    protected RouteDefinition addTransactionPolicy(final RouteDefinition route) {
        if (TransactionHelper.useTransactionManager(getComponentUri(), _camelContext)) {
            route.transacted(CamelConstants.TRANSACTED_REF);
        }
        
        return route;
    }
    
    /**
     * Only add a namespace policy if the policy ref can be resolved, which will
     * be the case on EAP but not on Karaf.
     */
    protected RouteDefinition addNamespacePolicy(final RouteDefinition route) {
        if (_camelContext.getRegistry().lookupByName(NAMESPACE_POLICY_REF) != null) {
            route.routePolicyRef(NAMESPACE_POLICY_REF);
        }
        return route;
    }

    protected URI getComponentUri() {
        return getBindingModel().getComponentURI();
    }

    protected String getSwitchyardEndpointUri() {
        return CamelConstants.SWITCHYARD_COMPONENT_NAME + "://" + getServiceName().getLocalPart();
    }

    protected T getBindingModel() {
        return _camelBindingModel;
    }
    
    protected SwitchYardCamelContext getSwitchYardCamelContext() {
        return _camelContext;
    }

    /**
     * Will create the Camel route and add it to the camel context.
     */
    @Override
    protected void doStart() {
        try {
            RouteDefinition route = _camelContext.getRouteDefinition(getRouteId());
            if (route == null) {
                // may have been removed if this was stopped
                route = createRouteDefinition();
                _camelContext.addRouteDefinition(route);
            }
            if (route.isStartable(_camelContext)) {
                _camelContext.startRoute(getRouteId());
            }
        } catch (Exception ex) {
            throw CommonCamelMessages.MESSAGES.failedToStartRouteForService(_serviceName.toString(), ex);
        }
    }

    /**
     * Suspends the route.
     */
    @Override
    protected void doStop() {
        try {
            _camelContext.stopRoute(getRouteId());
        } catch (Exception ex) {
            throw CommonCamelMessages.MESSAGES.failedToStopRouteForService(_serviceName.toString(), ex);
        }
        try {
            _camelContext.removeRoute(getRouteId());
        } catch (Exception ex) {
            CommonCamelLogger.ROOT_LOGGER.failedToRemoveRouteForService(_serviceName.toString(), ex);
        }
    }

    /**
     * This is a noop for this handler. This handler is only responsible for setting up
     * a route in camel and the {@link SwitchyardComponent} will take care of calling
     * the configured SwitchYard service.
     * 
     * @param switchYardExchange Message exchange.
     */
    @Override
    public void handleMessage(final Exchange switchYardExchange) {
    }

    @Override
    public void handleFault(final Exchange exchange) {
        System.out.println(exchange.getMessage().getContent());
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((_camelBindingModel == null) ? 0 : _camelBindingModel.getComponentURI().hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        
        final InboundHandler<?> other = (InboundHandler<?>) obj;
        if (_camelBindingModel == null) {
            if (other._camelBindingModel != null) {
                return false;
            }
        } else if (!_camelBindingModel.getComponentURI().equals(other._camelBindingModel.getComponentURI())) {
            return false;
        }
        return true;
    }

}
