/*
 *
 * 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.
 *
 */
using System;
using System.Collections;
using log4net;
using Qpid.Client.Handler;
using Qpid.Client.Protocol;
using Qpid.Client.Protocol.Listener;
using Qpid.Framing;

namespace Qpid.Client.State
{
    public class AMQStateManager : IAMQMethodListener
    {
        private static readonly ILog _logger = LogManager.GetLogger(typeof(AMQStateManager));

        const bool InfoLoggingHack = true;
            
        /// <summary>
        /// The current state
        /// </summary>
        private AMQState _currentState;

        /// <summary>
        /// Maps from an AMQState instance to a Map from Class to StateTransitionHandler.
        /// The class must be a subclass of AMQFrame.
        /// </summary>
        private readonly IDictionary _state2HandlersMap;
        private ArrayList _stateListeners;
        private object _syncLock;
        
        public AMQStateManager()
        {
            _syncLock = new object();
            _state2HandlersMap = new Hashtable();
            _stateListeners = ArrayList.Synchronized(new ArrayList(5));
            _currentState = AMQState.CONNECTION_NOT_STARTED;
            RegisterListeners();
        }

        private void RegisterListeners()
        {
            IStateAwareMethodListener connectionStart = new ConnectionStartMethodHandler();
            IStateAwareMethodListener connectionClose = new ConnectionCloseMethodHandler();
            IStateAwareMethodListener connectionCloseOk = new ConnectionCloseOkHandler();
            IStateAwareMethodListener connectionTune = new ConnectionTuneMethodHandler();
            IStateAwareMethodListener connectionSecure = new ConnectionSecureMethodHandler();
            IStateAwareMethodListener connectionOpenOk = new ConnectionOpenOkMethodHandler();
            IStateAwareMethodListener channelClose = new ChannelCloseMethodHandler();
            IStateAwareMethodListener basicDeliver = new BasicDeliverMethodHandler();
            IStateAwareMethodListener basicReturn = new BasicReturnMethodHandler();
            IStateAwareMethodListener queueDeleteOk = new QueueDeleteOkMethodHandler();
            IStateAwareMethodListener queuePurgeOk = new QueuePurgeOkMethodHandler();
            
            // We need to register a map for the null (i.e. all state) handlers otherwise you get
            // a stack overflow in the handler searching code when you present it with a frame for which
            // no handlers are registered.
            _state2HandlersMap[AMQState.ALL] = new Hashtable();

            {
                Hashtable notStarted = new Hashtable();
                notStarted[typeof(ConnectionStartBody)] = connectionStart;
                notStarted[typeof(ConnectionCloseBody)] = connectionClose;
                _state2HandlersMap[AMQState.CONNECTION_NOT_STARTED] = notStarted;
            }
            {
                Hashtable notTuned = new Hashtable();
                notTuned[typeof(ConnectionTuneBody)] = connectionTune;
                notTuned[typeof(ConnectionSecureBody)] = connectionSecure;
                notTuned[typeof(ConnectionCloseBody)] = connectionClose;
                _state2HandlersMap[AMQState.CONNECTION_NOT_TUNED] = notTuned;
            }
            {
                Hashtable notOpened = new Hashtable();
                notOpened[typeof(ConnectionOpenOkBody)] = connectionOpenOk;
                notOpened[typeof(ConnectionCloseBody)] = connectionClose;
                _state2HandlersMap[AMQState.CONNECTION_NOT_OPENED] = notOpened;
            }
            {
                Hashtable open = new Hashtable();
                open[typeof(ChannelCloseBody)] = channelClose;
                open[typeof(ConnectionCloseBody)] = connectionClose;
                open[typeof(BasicDeliverBody)] = basicDeliver;
                open[typeof(BasicReturnBody)] = basicReturn;
                open[typeof(QueueDeleteOkBody)] = queueDeleteOk;
                open[typeof(QueuePurgeOkBody)] = queuePurgeOk;
                _state2HandlersMap[AMQState.CONNECTION_OPEN] = open;
            }
            {
                Hashtable closing = new Hashtable();
                closing[typeof(ConnectionCloseOkBody)] = connectionCloseOk;
                _state2HandlersMap[AMQState.CONNECTION_CLOSING] = closing;
            }
        }

        public AMQState CurrentState
        {
            get
            {
                return _currentState;
            }
        }

        /// <summary>
        /// Changes the state.
        /// </summary>
        /// <param name="newState">The new state.</param>
        /// <exception cref="AMQException">if there is an error changing state</exception>
        public void ChangeState(AMQState newState)
        {
            if (InfoLoggingHack)
            {
                _logger.Debug("State changing to " + newState + " from old state " + _currentState);
            }
            _logger.Debug("State changing to " + newState + " from old state " + _currentState);
            AMQState oldState = _currentState;
            _currentState = newState;

            lock ( _syncLock )
            {
               foreach ( IStateListener l in _stateListeners )
               {
                  l.StateChanged(oldState, newState);
               }
            }
        }

        public void Error(Exception e)
        {
            _logger.Debug("State manager receive error notification: " + e);
            lock ( _syncLock )
            {
               foreach ( IStateListener l in _stateListeners )
               {
                  l.Error(e);
               }
            }
        }

        public bool MethodReceived(AMQMethodEvent evt)
        {
            _logger.Debug(String.Format("Finding method handler. currentState={0} type={1}", _currentState, evt.Method.GetType()));            
            IStateAwareMethodListener handler = FindStateTransitionHandler(_currentState, evt.Method);
            if (handler != null)
            {
                handler.MethodReceived(this, evt);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Finds the state transition handler.
        /// </summary>
        /// <param name="currentState">State of the current.</param>
        /// <param name="frame">The frame.</param>
        /// <returns></returns>
        /// <exception cref="IllegalStateTransitionException">if the state transition if not allowed</exception>
        private IStateAwareMethodListener FindStateTransitionHandler(AMQState currentState,
                                                                     AMQMethodBody frame)                
        {
            Type clazz = frame.GetType();
            if (_logger.IsDebugEnabled)
            {
                _logger.Debug("Looking for state transition handler for frame " + clazz);
            }
            IDictionary classToHandlerMap = (IDictionary) _state2HandlersMap[currentState];

            if (classToHandlerMap == null)
            {
                // if no specialised per state handler is registered look for a
                // handler registered for "all" states
                return FindStateTransitionHandler(AMQState.ALL, frame);
            }
            IStateAwareMethodListener handler = (IStateAwareMethodListener) classToHandlerMap[clazz];
            if (handler == null)
            {
                if (currentState == AMQState.ALL)
                {
                    _logger.Debug("No state transition handler defined for receiving frame " + frame);
                    return null;
                }
                else
                {
                    // if no specialised per state handler is registered look for a
                    // handler registered for "all" states
                    return FindStateTransitionHandler(AMQState.ALL, frame);
                }
            }
            else
            {
                return handler;
            }
        }

        public void AddStateListener(IStateListener listener)
        {
            _logger.Debug("Adding state listener");
            lock ( _syncLock )
            {
               _stateListeners.Add(listener);
            }
        }

        public void RemoveStateListener(IStateListener listener)
        {
           lock ( _syncLock )
           {
              _stateListeners.Remove(listener);
           }
        }

        public void AttainState(AMQState s)
        {
            if (_currentState != s)
            {
                StateWaiter sw = null;
                try
                {
                    _logger.Debug("Adding state wait to reach state " + s);
                    sw = new StateWaiter(s);
                    AddStateListener(sw);
                    sw.WaituntilStateHasChanged();
                    // at this point the state will have changed.
                }
                finally
                { 
                    RemoveStateListener(sw);
                }
            }
        }        
    }
}
