using System;
using System.Threading;
using Aitex.Core.RT.Fsm;
using Aitex.Core.RT.Log;
using Aitex.Core.Utilities;
using MECF.Framework.Common.Fsm;

namespace MECF.Framework.Common.Communications
{
	public class FsmConnection : ActiveFsm
	{
		private enum ConnectionState
		{
			Init = 1000,
			NotConnect = 1001,
			Connecting = 1002,
			Connected = 1003,
			Error = 1004
		}

		private enum MSG
		{
			Connect = 1000,
			Disconnect = 1001,
			ConnectFailed = 1002,
			Connected = 1003,
			Reset = 1004,
			CommunicationError = 1005,
			ReceiveBinaryData = 1006,
			SendBinaryData = 1007,
			ReceiveAsciiData = 1008,
			SendAsciiData = 1009,
			ConfirmConnection = 1010
		}

		private IConnectable _connectable;

		private IConnectionContext _config;

		private string _lastError;

        public FsmConnection()
        {
            
        }
        
        public FsmConnection(string name)
        {
            Name = name;
        }
        

		public bool IsConnected => base.FsmState == 1003;

		public bool IsError => base.FsmState == 1004;

		public string Name { get; set; }

		public event Action<string> OnError;

		public event Action OnConnected;

		public event Action OnDisconnected;

		public virtual void Initialize(int fsmInterval, IConnectable connectable, IConnectionContext config)
		{
			_connectable = connectable;
			_config = config;
			_connectable.OnBinaryDataReceived += _connectable_OnBinaryDataChanged;
			_connectable.OnAsciiDataReceived += _connectable_OnAsciiDataChanged;
			_connectable.OnCommunicationError += ConnectableOnCommunicationError;
			EnumLoop<ConnectionState>.ForEach(delegate(ConnectionState item)
			{
				MapState((int)item, item.ToString());
			});
			EnumLoop<MSG>.ForEach(delegate(MSG item)
			{
				MapMessage((int)item, item.ToString());
			});
			EnterExitTransition<ConnectionState, FSM_MSG>(ConnectionState.Init, FsmEnterInit, FSM_MSG.NONE, null);
			Transition(ConnectionState.Init, MSG.Disconnect, null, ConnectionState.NotConnect);
			Transition(ConnectionState.Init, MSG.Connect, null, ConnectionState.Connecting);
			EnterExitTransition<ConnectionState, FSM_MSG>(ConnectionState.Connecting, FsmEnterConnecting, FSM_MSG.NONE, null);
			Transition(ConnectionState.Connecting, MSG.ConnectFailed, null, ConnectionState.Error);
			Transition(ConnectionState.Connecting, MSG.Connected, null, ConnectionState.Connected);
			Transition(ConnectionState.Connecting, MSG.Disconnect, FsmDisconnect, ConnectionState.NotConnect);
			Transition(ConnectionState.Connecting, FSM_MSG.TIMER, FsmMonitorConnecting, ConnectionState.Connecting);
			EnterExitTransition<ConnectionState, FSM_MSG>(ConnectionState.Connected, FsmEnterConnected, FSM_MSG.NONE, FsmExitConnected);
			Transition(ConnectionState.Connected, MSG.Disconnect, FsmDisconnect, ConnectionState.NotConnect);
			Transition(ConnectionState.Connected, MSG.CommunicationError, null, ConnectionState.Error);
			Transition(ConnectionState.Connected, MSG.ConnectFailed, null, ConnectionState.Error);
			Transition(ConnectionState.Connected, FSM_MSG.TIMER, FsmMonitorConnected, ConnectionState.Connected);
			Transition(ConnectionState.Connected, MSG.ConfirmConnection, FsmMonitorConnected, ConnectionState.Connected);
			Transition(ConnectionState.Connected, MSG.ReceiveBinaryData, FsmReceiveBinaryData, ConnectionState.Connected);
			Transition(ConnectionState.Connected, MSG.SendBinaryData, FsmSendBinaryData, ConnectionState.Connected);
			Transition(ConnectionState.Connected, MSG.ReceiveAsciiData, FsmReceiveAsciiData, ConnectionState.Connected);
			Transition(ConnectionState.Connected, MSG.SendAsciiData, FsmSendAsciiData, ConnectionState.Connected);
			Transition(ConnectionState.NotConnect, MSG.Connect, null, ConnectionState.Connecting);
			EnterExitTransition<ConnectionState, FSM_MSG>(ConnectionState.Error, FsmEnterError, FSM_MSG.NONE, null);
			Transition(ConnectionState.Error, MSG.Disconnect, null, ConnectionState.NotConnect);
			Transition(ConnectionState.Error, MSG.Reset, null, ConnectionState.Connecting);
			StartFsm(Name, fsmInterval, 1000);
		}

		public virtual void Terminate()
		{
			StopFsm();
			try
			{
				_connectable.Disconnect(out var _);
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
		}

		protected virtual void OnReceiveData(string message)
		{
		}

		protected virtual void OnReceiveData(byte[] message)
		{
		}

		protected virtual void OnConnect()
		{
		}

		protected virtual void OnDisconnect()
		{
		}

		protected virtual void OnConnectMonitor()
		{
		}

		private bool FsmSendAsciiData(object[] param)
		{
			if (!_connectable.SendAsciiData((string)param[0]))
			{
				PostMsg(MSG.ConfirmConnection);
			}
			return true;
		}

		private bool FsmReceiveAsciiData(object[] param)
		{
			try
			{
				OnReceiveData((string)param[0]);
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
			return true;
		}

		private bool FsmSendBinaryData(object[] param)
		{
			if (!_connectable.SendBinaryData((byte[])param[0]))
			{
				PostMsg(MSG.ConfirmConnection);
			}
			return true;
		}

		private bool FsmReceiveBinaryData(object[] param)
		{
			try
			{
				OnReceiveData((byte[])param[0]);
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
			return true;
		}

		private void ConnectableOnCommunicationError(string obj)
		{
			if (!CheckToPostMessage(1005, out var reason, obj))
			{
				LOG.Write(reason);
			}
		}

		private void _connectable_OnAsciiDataChanged(string obj)
		{
			if (!CheckToPostMessage(1008, out var reason, obj))
			{
				LOG.Write(reason);
			}
		}

		private void _connectable_OnBinaryDataChanged(byte[] obj)
		{
			if (!CheckToPostMessage(1006, out var reason, obj))
			{
				LOG.Write(reason);
			}
		}

		private bool FsmDisconnect(object[] param)
		{
			if (!_connectable.Disconnect(out var reason))
			{
				LOG.Write(reason);
				return false;
			}
			return true;
		}

		private bool FsmExitConnected(object[] param)
		{
			if (this.OnDisconnected != null)
			{
				this.OnDisconnected();
			}
			OnDisconnect();
			return true;
		}

		private bool FsmEnterError(object[] param)
		{
			if (this.OnError != null)
			{
				this.OnError(_lastError);
			}
			return true;
		}

		private bool FsmEnterConnecting(object[] param)
		{
			if (!_connectable.Connect(out var reason))
			{
				_lastError = reason;
				PostMsg(MSG.ConnectFailed);
			}
			return true;
		}

		private bool FsmMonitorConnecting(object[] param)
		{
			if (_connectable.CheckIsConnected())
			{
				PostMsg(MSG.Connected);
				return true;
			}
			if (!PeformConnect())
			{
				_lastError = "Can not connect with " + Name;
				PostMsg(MSG.ConnectFailed);
				return true;
			}
			return true;
		}

		private bool FsmMonitorConnected(object[] param)
		{
			if (_config.EnableCheckConnection && !_connectable.CheckIsConnected() && !PeformConnect())
			{
				_lastError = "Can not connect with " + Name;
				PostMsg(MSG.ConnectFailed);
				return false;
			}
			OnConnectMonitor();
			return true;
		}

		private bool PeformConnect()
		{
			int num = 0;
			do
			{
				if (!_connectable.Connect(out var reason))
				{
					_lastError = reason;
					return false;
				}
				Thread.Sleep(100);
				if (_connectable.CheckIsConnected())
				{
					return true;
				}
				if (num < _config.MaxRetryConnectCount)
				{
					num++;
					LOG.Write($"{Name} retry connect {num} /{_config.MaxRetryConnectCount} time");
					Thread.Sleep(_config.RetryConnectIntervalMs);
				}
			}
			while (num < _config.MaxRetryConnectCount);
			if (!_connectable.CheckIsConnected())
			{
				LOG.Error("Can not connect with " + Name);
				return false;
			}
			return true;
		}

		private bool FsmEnterConnected(object[] param)
		{
			if (this.OnConnected != null)
			{
				this.OnConnected();
			}
			OnConnect();
			return true;
		}

		private bool FsmEnterInit(object[] param)
		{
			if (_config.IsEnabled)
			{
				PostMsg(MSG.Connect);
			}
			else
			{
				PostMsg(MSG.Disconnect);
			}
			return true;
		}

		public void InvokeSendData<T>(T data)
		{
			if (!CheckToPostMessage(1009, out var reason, data))
			{
				LOG.Write(reason);
			}
		}

		public void InvokeConnect()
		{
			if (!CheckToPostMessage(1000, out var reason))
			{
				LOG.Write(reason);
			}
		}

		public void InvokeDisconnect()
		{
			if (!CheckToPostMessage(1001, out var reason))
			{
				LOG.Write(reason);
			}
		}

		public void InvokeError()
		{
			if (!CheckToPostMessage(1005, out var reason))
			{
				LOG.Write(reason);
			}
		}

		public void InvokeReset()
		{
			if (!CheckToPostMessage(1004, out var reason))
			{
				LOG.Write(reason);
			}
		}
	}
}
