/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.cluster.handler;

import org.apache.qpid.AMQException;
import org.apache.qpid.framing.AMQFrame;
import org.apache.qpid.framing.AMQMethodBody;
import org.apache.qpid.framing.BasicCancelBody;
import org.apache.qpid.framing.BasicConsumeBody;
import org.apache.qpid.framing.BasicPublishBody;
import org.apache.qpid.framing.ChannelCloseBody;
import org.apache.qpid.framing.ChannelFlowBody;
import org.apache.qpid.framing.ChannelOpenBody;
import org.apache.qpid.framing.ClusterJoinBody;
import org.apache.qpid.framing.ClusterLeaveBody;
import org.apache.qpid.framing.ClusterMembershipBody;
import org.apache.qpid.framing.ClusterPingBody;
import org.apache.qpid.framing.ClusterSuspectBody;
import org.apache.qpid.framing.ConnectionCloseBody;
import org.apache.qpid.framing.ConnectionOpenBody;
import org.apache.qpid.framing.ConnectionSecureOkBody;
import org.apache.qpid.framing.ConnectionStartOkBody;
import org.apache.qpid.framing.ConnectionTuneOkBody;
import org.apache.qpid.framing.ExchangeDeclareBody;
import org.apache.qpid.framing.ExchangeDeleteBody;
import org.apache.qpid.framing.QueueBindBody;
import org.apache.qpid.framing.QueueDeclareBody;
import org.apache.qpid.framing.QueueDeleteBody;
import org.apache.qpid.framing.ClusterSynchBody;
import org.apache.qpid.framing.BasicQosBody;
import org.apache.qpid.framing.TxSelectBody;
import org.apache.qpid.framing.TxCommitBody;
import org.apache.qpid.framing.TxRollbackBody;
import org.apache.qpid.server.cluster.ClusterCapability;
import org.apache.qpid.server.cluster.ClusteredProtocolSession;
import org.apache.qpid.server.cluster.GroupManager;
import org.apache.qpid.server.cluster.LoadTable;
import org.apache.qpid.server.cluster.MemberHandle;
import org.apache.qpid.server.cluster.MethodHandlerFactory;
import org.apache.qpid.server.cluster.MethodHandlerRegistry;
import org.apache.qpid.server.cluster.SimpleMemberHandle;
import org.apache.qpid.server.exchange.ExchangeRegistry;
import org.apache.qpid.server.handler.ChannelCloseHandler;
import org.apache.qpid.server.handler.ChannelFlowHandler;
import org.apache.qpid.server.handler.ChannelOpenHandler;
import org.apache.qpid.server.handler.ConnectionCloseMethodHandler;
import org.apache.qpid.server.handler.ConnectionOpenMethodHandler;
import org.apache.qpid.server.handler.ConnectionSecureOkMethodHandler;
import org.apache.qpid.server.handler.ConnectionStartOkMethodHandler;
import org.apache.qpid.server.handler.ConnectionTuneOkMethodHandler;
import org.apache.qpid.server.handler.ExchangeDeclareHandler;
import org.apache.qpid.server.handler.ExchangeDeleteHandler;
import org.apache.qpid.server.handler.BasicCancelMethodHandler;
import org.apache.qpid.server.handler.BasicPublishMethodHandler;
import org.apache.qpid.server.handler.QueueBindHandler;
import org.apache.qpid.server.handler.QueueDeleteHandler;
import org.apache.qpid.server.handler.BasicQosHandler;
import org.apache.qpid.server.handler.TxSelectHandler;
import org.apache.qpid.server.handler.TxCommitHandler;
import org.apache.qpid.server.handler.TxRollbackHandler;
import org.apache.qpid.server.protocol.AMQMethodEvent;
import org.apache.qpid.server.protocol.AMQProtocolSession;
import org.apache.qpid.server.queue.QueueRegistry;
import org.apache.qpid.server.state.AMQState;
import org.apache.qpid.server.state.AMQStateManager;
import org.apache.qpid.server.state.StateAwareMethodListener;

public class ClusterMethodHandlerFactory implements MethodHandlerFactory
{
    private final GroupManager _groupMgr;
    private final LoadTable _loadTable;

    public ClusterMethodHandlerFactory(GroupManager groupMgr, LoadTable loadTable)
    {
        _groupMgr = groupMgr;
        _loadTable = loadTable;
    }

    public MethodHandlerRegistry register(AMQState state, MethodHandlerRegistry registry)
    {
        switch (state)
        {
            case CONNECTION_NOT_STARTED:
                return registry.addHandler(ConnectionStartOkBody.class, ConnectionStartOkMethodHandler.getInstance());
            case CONNECTION_NOT_AUTH:
                return registry.addHandler(ConnectionSecureOkBody.class, ConnectionSecureOkMethodHandler.getInstance());
            case CONNECTION_NOT_TUNED:
                return registry.addHandler(ConnectionTuneOkBody.class, ConnectionTuneOkMethodHandler.getInstance());
            case CONNECTION_NOT_OPENED:
                //connection.open override:
                return registry.addHandler(ConnectionOpenBody.class, new ConnectionOpenHandler());
            case CONNECTION_OPEN:
                return registerConnectionOpened(registry);
        }
        return registry;
    }

    private MethodHandlerRegistry registerConnectionOpened(MethodHandlerRegistry registry)
    {
        //new cluster method handlers:
        registry.addHandler(ClusterJoinBody.class, new JoinHandler());
        registry.addHandler(ClusterLeaveBody.class, new LeaveHandler());
        registry.addHandler(ClusterSuspectBody.class, new SuspectHandler());
        registry.addHandler(ClusterMembershipBody.class, new MembershipHandler());
        registry.addHandler(ClusterPingBody.class, new PingHandler());
        registry.addHandler(ClusterSynchBody.class, new SynchHandler());

        //connection.close override:
        registry.addHandler(ConnectionCloseBody.class, new ConnectionCloseHandler());

        //replicated handlers:
        registry.addHandler(ExchangeDeclareBody.class, replicated(ExchangeDeclareHandler.getInstance()));
        registry.addHandler(ExchangeDeleteBody.class, replicated(ExchangeDeleteHandler.getInstance()));

        ChannelQueueManager channelQueueMgr = new ChannelQueueManager();


        LocalQueueDeclareHandler handler = new LocalQueueDeclareHandler(_groupMgr);
        registry.addHandler(QueueDeclareBody.class,
                            chain(new QueueNameGenerator(handler),
                                  channelQueueMgr.createQueueDeclareHandler(),
                                  new ReplicatingHandler<QueueDeclareBody>(_groupMgr, handler)));

        registry.addHandler(QueueBindBody.class, chain(channelQueueMgr.createQueueBindHandler(), replicated(QueueBindHandler.getInstance())));
        registry.addHandler(QueueDeleteBody.class, chain(channelQueueMgr.createQueueDeleteHandler(), replicated(alternate(new QueueDeleteHandler(false), new QueueDeleteHandler(true)))));
        registry.addHandler(BasicConsumeBody.class, chain(channelQueueMgr.createBasicConsumeHandler(), new ReplicatingConsumeHandler(_groupMgr)));

        //other modified handlers:
        registry.addHandler(BasicCancelBody.class, alternate(new RemoteCancelHandler(), BasicCancelMethodHandler.getInstance()));

        //other unaffected handlers:
        registry.addHandler(BasicPublishBody.class, BasicPublishMethodHandler.getInstance());
        registry.addHandler(BasicQosBody.class, BasicQosHandler.getInstance());
        registry.addHandler(ChannelOpenBody.class, ChannelOpenHandler.getInstance());
        registry.addHandler(ChannelCloseBody.class, ChannelCloseHandler.getInstance());
        registry.addHandler(ChannelFlowBody.class, ChannelFlowHandler.getInstance());
        registry.addHandler(TxSelectBody.class, TxSelectHandler.getInstance());
        registry.addHandler(TxCommitBody.class, TxCommitHandler.getInstance());
        registry.addHandler(TxRollbackBody.class, TxRollbackHandler.getInstance());


        return registry;
    }

    private class SynchHandler implements StateAwareMethodListener<ClusterSynchBody>
    {
        public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
                                   ExchangeRegistry exchangeRegistry, AMQProtocolSession session,
                                   AMQMethodEvent<ClusterSynchBody> evt) throws AMQException
        {
            _groupMgr.handleSynch(ClusteredProtocolSession.getSessionPeer(session));
        }
    }

    private class JoinHandler implements StateAwareMethodListener<ClusterJoinBody>
    {
        public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
                                   ExchangeRegistry exchangeRegistry, AMQProtocolSession session,
                                   AMQMethodEvent<ClusterJoinBody> evt) throws AMQException
        {
            _groupMgr.handleJoin(new SimpleMemberHandle(evt.getMethod().broker));
        }
    }

    private class LeaveHandler implements StateAwareMethodListener<ClusterLeaveBody>
    {
        public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
                                   ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
                                   AMQMethodEvent<ClusterLeaveBody> evt) throws AMQException
        {
            _groupMgr.handleLeave(new SimpleMemberHandle(evt.getMethod().broker));
        }
    }

    private class SuspectHandler implements StateAwareMethodListener<ClusterSuspectBody>
    {
        public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
                                   ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession,
                                   AMQMethodEvent<ClusterSuspectBody> evt) throws AMQException
        {
            _groupMgr.handleSuspect(new SimpleMemberHandle(evt.getMethod().broker));
        }
    }

    private class MembershipHandler implements StateAwareMethodListener<ClusterMembershipBody>
    {
        public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
                                   ExchangeRegistry exchangeRegistry, AMQProtocolSession session,
                                   AMQMethodEvent<ClusterMembershipBody> evt) throws AMQException
        {
            ClusterMembershipBody body = evt.getMethod();
            _groupMgr.handleMembershipAnnouncement(new String(body.members));
        }
    }

    private class PingHandler implements StateAwareMethodListener<ClusterPingBody>
    {
        public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry,
                                   ExchangeRegistry exchangeRegistry, AMQProtocolSession session,
                                   AMQMethodEvent<ClusterPingBody> evt) throws AMQException
        {
            MemberHandle peer = new SimpleMemberHandle(evt.getMethod().broker);
            _groupMgr.handlePing(peer, evt.getMethod().load);
            if (evt.getMethod().responseRequired)
            {
                evt.getMethod().load = _loadTable.getLocalLoad();
                session.writeFrame(new AMQFrame(evt.getChannelId(), evt.getMethod()));
            }
        }
    }

    private class ConnectionOpenHandler extends ExtendedHandler<ConnectionOpenBody>
    {
        ConnectionOpenHandler()
        {
            super(ConnectionOpenMethodHandler.getInstance());
        }

        void postHandle(AMQStateManager stateMgr, AMQProtocolSession session, AMQMethodEvent<ConnectionOpenBody> evt)
        {
            String capabilities = evt.getMethod().capabilities;
            if (ClusterCapability.contains(capabilities))
            {
                ClusteredProtocolSession.setSessionPeer(session, ClusterCapability.getPeer(capabilities));
            }
            else
            {
                _loadTable.incrementLocalLoad();
            }
        }
    }

    private class ConnectionCloseHandler extends ExtendedHandler<ConnectionCloseBody>
    {
        ConnectionCloseHandler()
        {
            super(ConnectionCloseMethodHandler.getInstance());
        }

        void postHandle(AMQStateManager stateMgr, AMQProtocolSession session, AMQMethodEvent<ConnectionCloseBody> evt)
        {
            if (!ClusteredProtocolSession.isPeerSession(session))
            {
                _loadTable.decrementLocalLoad();
            }
        }
    }

    private <B extends AMQMethodBody> ReplicatingHandler<B> replicated(StateAwareMethodListener<B> handler)
    {
        return new ReplicatingHandler<B>(_groupMgr, handler);
    }

    private <B extends AMQMethodBody> StateAwareMethodListener<B> alternate(StateAwareMethodListener<B> peer, StateAwareMethodListener<B> client)
    {
        return new PeerHandler<B>(peer, client);
    }

    private <B extends AMQMethodBody> StateAwareMethodListener<B> chain(ClusterMethodHandler<B>... h)
    {
        return new ChainedClusterMethodHandler<B>(h);
    }
}
