#define TRACE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.IOCore;
using Aitex.Core.RT.Log;
using Aitex.Core.Util;

namespace MECF.Framework.RT.Core.IoProviders
{
	public abstract class IoProvider : IIoProvider
	{
		protected PeriodicJob _thread;

		protected IIoBuffer _buffer;

		protected R_TRIG _trigError = new R_TRIG();

		protected RD_TRIG _trigNotConnected = new RD_TRIG();

		protected string _source;

		protected XmlElement _nodeParameter;

		protected List<IoBlockItem> _blockSections;

        protected readonly Dictionary<int, IoVariable> DiVariables = new();
        protected readonly Dictionary<int, IoVariable> DoVariables = new();
        protected readonly Dictionary<int, IoVariable> AiVariables = new();
        protected readonly Dictionary<int, IoVariable> AoVariables = new();

		public string Module { get; set; }

		public string Name { get; set; }

		public bool IsOpened => State == IoProviderStateEnum.Opened;



        /// <summary>
        /// 根据DI的值判断是否已经和PLC同步数据。
        /// </summary>
        public bool IsSynced
        {
            get
            {
                try
                {
                    var dict = _buffer.GetDiBuffer(_source);
                    if (dict == null)
                        return false;

                    if (dict.TryGetValue(0, out var diBuff) == false)
                        return false;

                    if (diBuff == null)
                        return false;

                    if (Array.TrueForAll(diBuff, x => x == true) ||
                        Array.TrueForAll(diBuff, x => x == false))
                        return false;

                    return true;
                }
                catch (Exception e)
                {
                    return false;
                }
            }
        }


        public IoProviderStateEnum State { get; set; }

		protected abstract void SetParameter(XmlElement nodeParameter);

		protected abstract void Open();

		protected abstract void Close();

		protected abstract bool[] ReadDi(int offset, int size);

		protected abstract short[] ReadAi(int offset, int size);

		protected abstract void WriteDo(int offset, bool[] buffer);

		protected abstract void WriteAo(int offset, short[] buffer);

		protected abstract void ReadDo(int offset, bool[] buffer);

		protected abstract short[] ReadAo(int offset, int size);

		protected virtual float[] ReadAiFloat(int offset, int size)
		{
			return null;
		}

		protected virtual void WriteAoFloat(int offset, float[] buffer)
		{
		}

		public virtual void Initialize(string module, string name, List<IoBlockItem> blocks, IIoBuffer buffer, XmlElement nodeParameter, Dictionary<int, string> ioMappingPathFile)
		{
			Module = module;
			Name = name;
			_source = module + "." + name;
			_buffer = buffer;
			_nodeParameter = nodeParameter;
			_blockSections = blocks;
			buffer.CreateBuffer(_source, blocks);
			buffer.MapIoBuffer(_source, ioMappingPathFile);
			SetParameter(nodeParameter);
			ParseVariables(blocks);
			State = IoProviderStateEnum.Uninitialized;
			_thread = new PeriodicJob(50, OnTimer, name);
		}

		public virtual void Initialize(string module, string name, List<IoBlockItem> blocks, IIoBuffer buffer, XmlElement nodeParameter, string ioMappingPathFile, string ioModule)
		{
			Module = module;
			Name = name;
			_source = module + "." + name;
			_buffer = buffer;
			_nodeParameter = nodeParameter;
			_blockSections = blocks;
			buffer.CreateBuffer(_source, blocks);
			buffer.MapIoBufferByModule(_source, ioMappingPathFile, ioModule);
			SetParameter(nodeParameter);
            ParseVariables(blocks);
            State = IoProviderStateEnum.Uninitialized;
			_thread = new PeriodicJob(50, OnTimer, name);
		}

		protected void SetState(IoProviderStateEnum newState)
		{
			State = newState;
		}

        protected virtual void ParseVariables(List<IoBlockItem> blocks)
        {
            if (blocks == null || !blocks.Any())
                return;

            foreach (var block in blocks)
            {
                var ioVariable = new IoVariable(block.Type)
                {
                    Name = block.Variable,
					Block = block
                };

                switch (block.Type)
                {
                    case IoType.DI:
                        ioVariable.Length = (block.Size + 7) / 8; // Convert bool array length to minimum byte array length
                        DiVariables[block.Index] = ioVariable;
                        break;
                    case IoType.DO:
                        ioVariable.Length = (block.Size + 7) / 8; // Convert bool array length to minimum byte array length
                        DoVariables[block.Index] = ioVariable;
                        break;
                    case IoType.AI:
						ioVariable.Length = (block.Size * 4); // AI and AO are short array, length is number of shorts
                        AiVariables[block.Index] = ioVariable;
                        break;
                    case IoType.AO:
						ioVariable.Length = (block.Size * 4); // AI and AO are short array, length is number of shorts
                        AoVariables[block.Index] = ioVariable;
                        break;
                }
            }
        }

        public void TraceArray(bool[] data)
		{
			string[] array = new string[data.Length];
			for (int i = 0; i < data.Length; i++)
			{
				array[i++] = (data[i] ? "1" : "0");
			}
			Trace.WriteLine(string.Join(",", array));
		}

		protected virtual bool OnTimer()
		{
			if (State == IoProviderStateEnum.Uninitialized)
			{
				SetState(IoProviderStateEnum.Opening);
				Open();
			}
			if (State == IoProviderStateEnum.Opened)
			{
				try
				{
					foreach (IoBlockItem blockSection in _blockSections)
					{
						if (blockSection.Type == IoType.DI)
						{
							var diBuffer = ReadDi(blockSection.Index, blockSection.Size);
							if (diBuffer != null)
								_buffer.SetDiBuffer(_source, blockSection.Index, diBuffer);
						}
						else
						{
							if (blockSection.Type == IoType.AI)
							{
								var aiBuffer = ReadAi(blockSection.Index, blockSection.Size);
								if (aiBuffer != null)
									_buffer.SetAiBuffer(_source, blockSection.Index,
										aiBuffer.Select(s => (float)s).ToArray());
							}
						}
					}

					var aoBuffer = _buffer.GetAoBuffer(_source);
					if (aoBuffer != null)
					{
						foreach (var item in aoBuffer)
							WriteAo(item.Key, item.Value.Select(x => (short)x).ToArray());
					}

					var doBuffer = _buffer.GetDoBuffer(_source);
					if (doBuffer != null)
					{
						foreach (KeyValuePair<int, bool[]> item2 in doBuffer)
							WriteDo(item2.Key, item2.Value);
					}
				}
				catch (Exception ex)
				{
					LOG.Write(ex);
					SetState(IoProviderStateEnum.Error);
					Close();
				}
			}
			_trigError.CLK = State == IoProviderStateEnum.Error;
			if (_trigError.Q)
			{
				EV.PostAlarmLog(Module, _source + " error");
			}
			_trigNotConnected.CLK = State != IoProviderStateEnum.Opened;
			if (_trigNotConnected.T)
			{
				EV.PostInfoLog(Module, _source + " connected");
			}
			if (_trigNotConnected.R)
			{
				EV.PostWarningLog(Module, _source + " not connected");
			}
			return true;
		}

		public virtual void Reset()
		{
			_trigError.RST = true;
			_trigNotConnected.RST = true;
		}

		public void Start()
		{
			if (_thread != null)
			{
				_thread.Start();
			}
		}

		public void Stop()
		{
			SetState(IoProviderStateEnum.Closing);
			Close();
			_thread.Stop();
		}

		public virtual bool SetValue(AOAccessor aoItem, float value)
		{
			return true;
		}
		
		public virtual bool SetValue(DOAccessor doItem, bool value)
		{
			return true;
		}
	}
}
