/*
 *
 * Copyright (c) 2006 The Apache Software Foundation
 *
 * 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.apache.qpid.server.protocol;

import org.apache.qpid.AMQException;
import org.apache.qpid.codec.AMQCodecFactory;
import org.apache.qpid.framing.*;
import org.apache.qpid.server.exchange.ExchangeRegistry;
import org.apache.qpid.server.queue.QueueRegistry;
import org.apache.qpid.server.registry.ApplicationRegistry;
import org.apache.qpid.server.registry.IApplicationRegistry;
import org.apache.qpid.server.transport.ConnectorConfiguration;
import org.apache.qpid.ssl.BogusSSLContextFactory;
import org.apache.log4j.Logger;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.filter.SSLFilter;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.util.SessionUtil;

import java.io.IOException;


/**
 * The protocol handler handles "protocol events" for all connections. The state
 * associated with an individual connection is accessed through the protocol session.
 *
 * We delegate all frame (message) processing to the AMQProtocolSession which wraps
 * the state for the connection.
 *
 */
public class AMQPFastProtocolHandler extends IoHandlerAdapter implements ProtocolVersionList
{
    private static final Logger _logger = Logger.getLogger(AMQPFastProtocolHandler.class);

    /**
     * The registry of all queues. This is passed to frame listeners when frame
     * events occur.
     */
    private final QueueRegistry _queueRegistry;

    /**
     * The registry of all exchanges. This is passed to frame listeners when frame
     * events occur.
     */
    private final ExchangeRegistry _exchangeRegistry;

    private boolean _useSSL;

    public AMQPFastProtocolHandler(Integer applicationRegistryInstance)
    {
        IApplicationRegistry registry = ApplicationRegistry.getInstance(applicationRegistryInstance);

        _queueRegistry = registry.getQueueRegistry();
        _exchangeRegistry = registry.getExchangeRegistry();
        _logger.debug("AMQPFastProtocolHandler created");
    }

    public AMQPFastProtocolHandler(QueueRegistry queueRegistry,
                                   ExchangeRegistry exchangeRegistry)
    {
        _queueRegistry = queueRegistry;
        _exchangeRegistry = exchangeRegistry;

        _logger.debug("AMQPFastProtocolHandler created");
    }

    protected AMQPFastProtocolHandler(AMQPFastProtocolHandler handler)
    {
        this(handler._queueRegistry, handler._exchangeRegistry);
    }

    public void sessionCreated(IoSession protocolSession) throws Exception
    {
        SessionUtil.initialize(protocolSession);
        final AMQCodecFactory codecFactory = new AMQCodecFactory(true);

        createSession(protocolSession, _queueRegistry, _exchangeRegistry, codecFactory);
        _logger.info("Protocol session created");

        final ProtocolCodecFilter pcf = new ProtocolCodecFilter(codecFactory);

        ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance().
                getConfiguredObject(ConnectorConfiguration.class);
        if (connectorConfig.enableExecutorPool)
        {
            if (_useSSL)
            {
                protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter",
                                                          new SSLFilter(BogusSSLContextFactory.getInstance(true)));
            }
            protocolSession.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf);
        }
        else
        {
            protocolSession.getFilterChain().addLast("protocolFilter", pcf);
        }
    }

    /**
     * Separated into its own, protected, method to allow easier reuse
     */
    protected void createSession(IoSession session, QueueRegistry queues, ExchangeRegistry exchanges, AMQCodecFactory codec) throws AMQException
    {
        new AMQMinaProtocolSession(session, queues, exchanges, codec);
    }

    public void sessionOpened(IoSession protocolSession) throws Exception
    {
        _logger.info("Session opened");
    }

    public void sessionClosed(IoSession protocolSession) throws Exception
    {
        _logger.info("Protocol Session closed");
        final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);
        amqProtocolSession.closeSession();
    }

    public void sessionIdle(IoSession session, IdleStatus status) throws Exception
    {
        _logger.debug("Protocol Session [" + this + "] idle: " + status);
        if(IdleStatus.WRITER_IDLE.equals(status))
        {
            //write heartbeat frame:
            session.write(HeartbeatBody.FRAME);
        }
        else if(IdleStatus.READER_IDLE.equals(status))
        {
            //failover:
            throw new IOException("Timed out while waiting for heartbeat from peer.");
        }

    }

    public void exceptionCaught(IoSession protocolSession, Throwable throwable) throws Exception
    {
        AMQProtocolSession session = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);
        if (throwable instanceof AMQProtocolHeaderException)
        {
            /* Find last protocol version in protocol version list. Make sure last protocol version
            listed in the build file (build-module.xml) is the latest version which will be returned
            here. */
            int i = pv.length - 1;
            protocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR]));
            protocolSession.close();
            _logger.error("Error in protocol initiation " + session + ": " + throwable.getMessage(), throwable);
        }
        else if(throwable instanceof IOException)
        {
            _logger.error("IOException caught in" + session + ", session closed implictly: " + throwable, throwable);            
        }
        else
        {
            protocolSession.write(ConnectionCloseBody.createAMQFrame(0, 200, throwable.getMessage(), 0, 0));
            _logger.error("Exception caught in" + session + ", closing session explictly: " + throwable, throwable);
            protocolSession.close();
        }
    }

    /**
     * Invoked when a message is received on a particular protocol session. Note that a
     * protocol session is directly tied to a particular physical connection.
     * @param protocolSession the protocol session that received the message
     * @param message the message itself (i.e. a decoded frame)
     * @throws Exception if the message cannot be processed
     */
    public void messageReceived(IoSession protocolSession, Object message) throws Exception
    {
        final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession);

        if (message instanceof AMQDataBlock)
        {
            amqProtocolSession.dataBlockReceived((AMQDataBlock) message);
        }
        else if (message instanceof ByteBuffer)
        {
            throw new IllegalStateException("Handed undecoded ByteBuffer buf = " + message);
        }
        else
        {
            throw new IllegalStateException("Handed unhandled message. message.class = " + message.getClass() + " message = " + message);
        }
    }

    /**
     * Called after a message has been sent out on a particular protocol session
     * @param protocolSession the protocol session (i.e. connection) on which this
     * message was sent
     * @param object the message (frame) that was encoded and sent
     * @throws Exception if we want to indicate an error
     */
    public void messageSent(IoSession protocolSession, Object object) throws Exception
    {
        if (_logger.isDebugEnabled())
        {
            _logger.debug("Message sent: " + object);
        }
    }

    public boolean isUseSSL()
    {
        return _useSSL;
    }

    public void setUseSSL(boolean useSSL)
    {
        _useSSL = useSSL;
    }
}
