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

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.jboss.logging.Logger;
import org.switchyard.Exchange;
import org.switchyard.ExchangeHandler;
import org.switchyard.ExchangeState;
import org.switchyard.HandlerException;
import org.switchyard.Message;
import org.switchyard.runtime.RuntimeLogger;

/**
 * Default handler chain.
 */
public class DefaultHandlerChain implements HandlerChain {
    private static Logger _logger = Logger.getLogger(DefaultHandlerChain.class);
    private final LinkedList<HandlerRef> _chain = new LinkedList<HandlerRef>();
    
    /**
     * Create a new handler chain with no handlers in it.
     */
    public DefaultHandlerChain() {
    }
    
    /**
     * Create a new handler chain with the specified handlers.  This ctor
     * is not intended for external use - it's used by the clone() method.
     * @param handlers
     */
    private DefaultHandlerChain(List<HandlerRef> handlers) {
        _chain.addAll(handlers);
    }

    /**
     * Create a handler chain from a list of ExchangeHandler instances.  The 
     * handlers in the chain are named using the class name of the handler.
     * @param handlers list of handlers
     * @return new HandlerChain containing the list of handlers in order
     */
    public static DefaultHandlerChain fromList(List<ExchangeHandler> handlers) {
        DefaultHandlerChain chain = new DefaultHandlerChain();
        for (ExchangeHandler handler : handlers) {
            chain.addLast(handler.getClass().getName(), handler);
        }
        return chain;
    }

    @Override
    public synchronized void addFirst(String handlerName,
            ExchangeHandler handler) {
        HandlerRef handlerRef = new HandlerRef(handlerName, handler);

        _chain.addFirst(handlerRef);
        if (_logger.isDebugEnabled()) {
            _logger.debug("Added ExchangeHandler instance at start of Handler Chain: " + handlerRef);
        }
    }

    @Override
    public synchronized void addLast(String handlerName,
            ExchangeHandler handler) {
        HandlerRef handlerRef = new HandlerRef(handlerName, handler);

        _chain.addLast(handlerRef);
        if (_logger.isDebugEnabled()) {
            _logger.debug("Added ExchangeHandler instance at end of Handler Chain: " + handlerRef);
        }
    }
    
    @Override
    public synchronized boolean replace(String handlerName, ExchangeHandler handler) {
        for (int i = 0; i < _chain.size(); i++) {
            if (_chain.get(i).getName().equals(handlerName)) {
                _chain.remove(i);
                _chain.add(i, new HandlerRef(handlerName, handler));
                return true;
            }
        }
        
        // no handler with the specified name found
        return false;
    }

    @Override
    public synchronized ExchangeHandler remove(String handlerName) {
        ExchangeHandler handler = null;

        for (HandlerRef ref : _chain) {
            if (ref.getName().equals(handlerName)) {
                handler = ref.getHandler();
                _chain.remove(ref);
                break;
            }
        }

        return handler;
    }

    @Override
    public void handle(Exchange exchange) {
        if (exchange.getState() == ExchangeState.FAULT) {
            handleFault(exchange);
        } else {
            handleMessage(exchange);
        }
    }

    @Override
    public void handleFault(Exchange exchange) {
        for (HandlerRef ref : listHandlers()) {
            try {
                if (_logger.isDebugEnabled()) {
                    _logger.debug("Executing Fault ExchangeHandler (" + ref + ") on message Exchange instance (" + System.identityHashCode(exchange) + ").");
                }
                ref.getHandler().handleFault(exchange);
            } catch (Exception e) {
                RuntimeLogger.ROOT_LOGGER.handlerFailedHandleFault(ref.getName(), e);
            }
        }
    }

    @Override
    public void handleMessage(Exchange exchange) {
        HandlerRef handlerRef = null;
        try {
            for (HandlerRef ref : listHandlers()) {
                handlerRef = ref;
                if (_logger.isDebugEnabled()) {
                    _logger.debug("Executing ExchangeHandler (" + ref + ") on message Exchange instance (" + System.identityHashCode(exchange) + ").");
                }
                ref.getHandler().handleMessage(exchange);

                if (exchange.getState() == ExchangeState.FAULT) {
                    // Exchange state has changed to FAULT.
                    // Stop executing handlers....
                    break;
                }
            }
        } catch (HandlerException handlerEx) {
            StringBuilder errorMsg = new StringBuilder(handlerEx.getClass().getName() + " was thrown by handler(" + handlerRef.getName() + "): " + handlerEx.getMessage());
            Throwable cause = handlerEx;
            while ((cause = cause.getCause()) != null) {
                errorMsg.append(" --- Caused by " + cause.getClass().getName() + ": " + cause.getMessage());
            }
            _logger.error(errorMsg.toString());
            _logger.debug("", handlerEx);

            Message faultMessage = exchange.createMessage().setContent(handlerEx);
            exchange.sendFault(faultMessage);
        }
    }
    
    @Override
    public List<ExchangeHandler> getHandlers() {
        List<ExchangeHandler> handlers = new LinkedList<ExchangeHandler>();
        for (HandlerRef hr : listHandlers()) {
            handlers.add(hr.getHandler());
        }
        return Collections.unmodifiableList(handlers);
    }
    
    /**
     * Create a copy of the default handler chain.
     * @return copy of the default chain.
     */
    public DefaultHandlerChain copy() {
        if (_logger.isDebugEnabled()) {
            _logger.debug("Cloning DefaultHandlerChain from a its list of Handlers: " + listHandlers());
        }
        return new DefaultHandlerChain(listHandlers());
    }

    @Override
    public String toString() {
        return _chain.toString();
    }

    private synchronized List<HandlerRef> listHandlers() {
        return new LinkedList<HandlerRef>(_chain);
    }

    // sweet little struct
    private static final class HandlerRef {
        HandlerRef(String name, ExchangeHandler handler) {
            _handler = handler;
            _name = name;
        }

        public String getName() {
            return _name;
        }

        public ExchangeHandler getHandler() {
            return _handler;
        }

        @Override
        public String toString() {
            return "Name '" + _name + "',  Class '" + _handler.getClass().getName() + "'";
        }

        private final ExchangeHandler _handler;
        private final String _name;
    }
}
