using System;
using System.Collections.Generic;
using Wayne.ForecourtControl.Fusion.StatusStateMachine.States;
using Wayne.ForecourtControl.Vir.Fusion;
using Wayne.Lib;
using Wayne.Lib.Log;
using Wayne.Lib.StateEngine;

namespace Wayne.ForecourtControl.Fusion.ReadDeviceStatus
{
    public class ReadDeviceStatusController : IDisposable, Wayne.Lib.IIdentifiableEntity
    {
        private readonly FUSIONForecourtControl _forecourtControl;
        public StateMachine StateMachine { get; private set; }
        // Dictionary that holds the event types for the event handlers registered by CreateEventHandler<TAsyncCompleted> method.
        private readonly Dictionary<object, object> _userTokenEventTypeDict = new Dictionary<object, object>();
        private readonly object _eventTypeDictSyncObj = new object();
        public IFSFManager IfsfManager
        {
            get { return _forecourtControl.manager.ifsfManager; }
        }

        public ReadDeviceStatusController(IIdentifiableEntity parentEntity, FUSIONForecourtControl forecourtControl)
        {
            Id = forecourtControl.Id;
            _forecourtControl = forecourtControl;
            ParentEntity = parentEntity;
            IServiceContainer serviceContainer = ServiceContainerFactory.Create();

            StateMachine = StateMachine.Create("", StateMachineType.Synchronous, new DebugLogger(this, true), "StateMachine");
            StateMachine.AddStateFactory(new StateFactory<ReadDeviceStatusController>(this, serviceContainer));
            StateMachine.OnFinalStateEntered += StateMachineOnOnFinalStateEntered;
            StateMachine.LogNameKind = StateNameKind.InstanceName;
        }

        private void StateMachineOnOnFinalStateEntered(object sender, EventArgs eventArgs)
        {
            using (var debugLogger = new DebugLogger(this))
            {
                if (debugLogger.IsActive(DebugLogLevel.Detailed))
                    debugLogger.Add("ReadDevice Status final state entered", DebugLogLevel.Detailed);
            }

            Dispose();
        }

        /// <summary>
        /// Initializes the manager. Initializes the state machine.
        /// </summary>
        public void Initialize()
        {
            var stateConfigurator = new StateConfigurator();
            stateConfigurator.Config(StateMachine.StateTransitionLookup);
            StateMachine.Initialize();
            if (!StateMachine.Started)
            {
                StateMachine.Start();
            }
        }

        public void Start()
        {
            StateMachine.IncomingEvent(new StateEngineEvent(EventType.ReadDeviceStatus));
        }

        public void GetConfiguration(EventHandler<AsyncCompletedEventArgs<ConfigurationSet>> readCompleted)
        {
            //Fail the ongoing reading and replace with new.
            var currentResponseDelgate = CurrentReadConfigurationResponseDelegate;
            currentResponseDelgate.Fire(this, new AsyncCompletedEventArgs<ConfigurationSet>(false, null, null));

            CurrentReadConfigurationResponseDelegate = readCompleted;

            StateMachine.IncomingEvent(new StateEngineEvent(EventType.GetConfiguration));
        }

        public EventHandler<AsyncCompletedEventArgs<ConfigurationSet>> CurrentReadConfigurationResponseDelegate { get; set; }

        public void SetConnectedStatus()
        {
            _forecourtControl.WritableConnectionState = DeviceConnectionState.Connected;
        }

        public void GetDeviceState(string deviceType, EventHandler<AsyncCompletedEventArgs> eventHandler, object userToken)
        {
            _forecourtControl.manager.ifsfManager.GetDeviceState(deviceType, -1, eventHandler, userToken, null);
        }

        public EventHandler<TEventArgs> EventHander<TEventArgs>() where TEventArgs : EventArgs
        {
            return (sender, e) => StateMachine.IncomingEvent(GenericEvent.Create(EventType.Response, sender, e));
        }

        public void GetFuelSaleTrx(EventHandler<AsyncCompletedEventArgs> eventHandler, object userToken)
        {
            _forecourtControl.manager.ifsfManager.GetAvailableFuelSaleTrxs(-1, eventHandler, userToken, null);
        }

        #region Generic asynchronous event handler

        /// <summary>
        /// Method for automatically creating a asynchronous completion event handler. It connects to a private method
        /// that generically encapsulates the event argument to a state engine generic event.
        /// </summary>
        /// <typeparam name="TAsyncCompleted">Type of the event argument.</typeparam>
        /// <param name="eventType">State engine event type that should be set for the generic event in the state machine.</param>
        /// <param name="userToken">The method creates a user token that should be used for the asynchronous call, so it can look up the eventtype properly.</param>
        /// <returns></returns>
        public EventHandler<TAsyncCompleted> CreateEventHandler<TAsyncCompleted>(object eventType, out object userToken) where TAsyncCompleted : Wayne.Lib.UserTokenEventArgs
        {
            lock (_eventTypeDictSyncObj)
            {
                userToken = new object();
                _userTokenEventTypeDict.Add(userToken, eventType);

                return new EventHandler<TAsyncCompleted>(GenericHandleEvent<TAsyncCompleted>);
            }
        }

        /// <summary>
        /// Internal generic event handler that is the target of the event handlers created in the CreateEventHandler &lt;TAsyncCompleted&gt; method.
        /// </summary>
        /// <typeparam name="TAsyncCompleted"></typeparam>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GenericHandleEvent<TAsyncCompleted>(object sender, TAsyncCompleted e) where TAsyncCompleted : Wayne.Lib.UserTokenEventArgs
        {
            GenericEvent<TAsyncCompleted> genericEvent = null;
            lock (_eventTypeDictSyncObj)
            {
                if (_userTokenEventTypeDict.ContainsKey(e.UserToken))
                {
                    var eventType = _userTokenEventTypeDict[e.UserToken];
                    _userTokenEventTypeDict.Remove(e.UserToken);
                    genericEvent = new GenericEvent<TAsyncCompleted>(eventType, sender, e);
                }
            }
            if (genericEvent != null)
            {
                StateMachine.IncomingEvent(genericEvent);
            }
        }

        /// <summary>
        /// Cancel the automatically created event handler. If the event handler is not cancelled, it will remain in memory forever, since it might never be used.        
        /// </summary>
        /// <param name="userToken"></param>
        public void CancelEventHandler(object userToken)
        {
            lock (_eventTypeDictSyncObj)
            {
                if (_userTokenEventTypeDict.ContainsKey(userToken))
                    _userTokenEventTypeDict.Remove(userToken);
            }
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Finalizer
        /// </summary>
        ~ReadDeviceStatusController()
        {
            Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
                StateMachine.Dispose();
        }

        #endregion

        #region Implementation of IIdentifiableEntity

        /// <summary>
        /// The ID of the entity.
        /// </summary>
        public int Id { get; private set; }

        /// <summary>
        /// The main type of entity.
        /// </summary>
        public string EntityType
        {
            get { return "DeviceStatusReader"; }
        }

        /// <summary>
        /// This is used by the logger and should never be set by inheriting classes
        /// </summary>
        public string FullEntityName { get; set; }

        /// <summary>
        /// A more refined type of the entity, e.g. a specific implementation or brand.
        /// </summary>
        public string EntitySubType
        {
            get { return "FusionFC"; }
        }

        /// <summary>
        /// Reference to a possible parent device.
        /// </summary>
        public IIdentifiableEntity ParentEntity { get; private set; }

        public ConfigurationSet Configuration { get; set; }

        #endregion

        public bool HasVirs()
        {
            return FUSIONVirFactory.Virs.Count > 0;
        }
    }
}