using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms.VisualStyles;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.PLC;
using Aitex.Core.Util;

namespace Aitex.Core.RT.IOCore
{
    public class IOGroupManager
    {
        public const int DioBlockLength = 200;
        public const int AioBlockLength = 200;

        private Dictionary<string, IO_DEFINE> _ioDefine = new Dictionary<string, IO_DEFINE>();
        private object _iomapLocker = new object();
        private Dictionary<string, object> _ioMap = new Dictionary<string, object>();

        private Dictionary<string, bool[]> _di = new Dictionary<string, bool[]>();
        private Dictionary<string, bool[]> _do = new Dictionary<string, bool[]>();
        private Dictionary<string, float[]> _ai = new Dictionary<string, float[]>();
        private Dictionary<string, float[]> _ao = new Dictionary<string, float[]>();

        private InterlockManager _interlock = new InterlockManager();
        private PeriodicJob _monitorThread;

        private Dictionary<string, int[]> _dicPlcIoBaseAddress = new Dictionary<string, int[]>();

        public void Initialize(Dictionary<string, string> ioDefineXmlFile, string interlockConfigFile, Dictionary<string, int[]> plcIoBaseAddress, List<string> logicDi)
        {
            _dicPlcIoBaseAddress = plcIoBaseAddress;

            try
            {
                foreach (var item in ioDefineXmlFile)
                {
                    if (!File.Exists(item.Value))
                        throw new Exception(string.Format("IO item define file not exist, {0}", item.Value));

                    _ioDefine[item.Key] = CustomXmlSerializer.Deserialize<IO_DEFINE>(new FileInfo(item.Value));

                    _di[item.Key] = new bool[DioBlockLength];
                    _do[item.Key] = new bool[DioBlockLength];
                    _ai[item.Key] = new float[AioBlockLength];
                    _ao[item.Key] = new float[AioBlockLength];

                    IOMapping(item.Key, plcIoBaseAddress[item.Key]);
                }
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
                throw new Exception(string.Format("IO define file found error, \r\n {0}", ex.Message));
            }

            _di["logic"] = new bool[DioBlockLength];
            int index = 0;
            foreach (var diName in logicDi)
            {
                DIAccessor diAccessor = new DIAccessor(diName, index, _di["logic"], _di["logic"]);
                _ioMap[diName] = diAccessor;
                diAccessor.IoTableIndex = index;
                index++;
            }


            string reason = string.Empty;
            if (!_interlock.Initialize(interlockConfigFile, _ioMap, out reason))
            {
                throw new Exception(string.Format("interlock define file found error: \r\n {0}", reason));
            }


            _monitorThread = new PeriodicJob(200, OnTimer, "IO Monitor Thread", true);
        }

        private bool OnTimer()
        {
            try
            {
                _interlock.Monitor();
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }

            return true;
        }

        public void Update<T, V>(string group, IDataBuffer<T, V> buf)
            where T : struct
            where V : struct
        {
            buf.UpdateDI(_di[group]);
            buf.UpdateDO(_do[group]);
            buf.UpdateAI(_ai[group]);
            buf.UpdateAO(_ao[group]);
        }

        public T GetIO<T>(string name) where T : class
        {
            T obj = null;

            lock (_iomapLocker)
            {
                if (_ioMap.ContainsKey(name))
                    obj = _ioMap[name] as T;
            }
            if (obj == null)
            {
                if (!String.IsNullOrWhiteSpace(name))
                    LOG.Error(string.Format("IO not match, can not find {0}", name));
            }

            return obj;
        }

        private void IOMapping(string group, int[] plcIoBaseAddress)
        {
            lock (_iomapLocker)
            {
                //DI 
                foreach (DI_ITEM item in _ioDefine[group].Dig_In)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;

                    int index = GetIndex(item.Addr, plcIoBaseAddress[0]);
                    if (index > 200)
                    {
                        LOG.Write(String.Format("DI {0} mapping index larger than buffer, addr is {1}", item.Index, item.Addr));
                    }

                    if (index >= 0)
                    {
                        DIAccessor diAccessor = new DIAccessor(item.Name, index, _di[group], _di[group]);
                        _ioMap[item.Name] = diAccessor;
                        diAccessor.IoTableIndex = item.Index;
                        //DATA.Subscribe(String.Format("IO.DI.{0}", item.Name), () => { return diAccessor.Value; });
                    }
                    else
                    {
                        LOG.Write(String.Format("DI{0} mapping error, addr is {1}", item.Index, item.Addr));
                    }

                }

                //DO
                foreach (DO_ITEM item in _ioDefine[group].Dig_Out)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;


                    int index = GetIndex(item.Addr, plcIoBaseAddress[1]);

                    if (index > 200)
                    {
                        LOG.Write(String.Format("DO {0} mapping index larger than buffer, addr is {1}", item.Index, item.Addr));
                    }

                    if (index >= 0)
                    {
                        DOAccessor doAccessor = new DOAccessor(item.Name, index, _do[group]);
                        _ioMap[item.Name] = doAccessor;
                        doAccessor.IoTableIndex = item.Index;
                        //DATA.Subscribe(String.Format("IO.DO.{0}", item.Name), () => { return doAccessor.Value; });
                    }
                    else
                    {
                        LOG.Write(String.Format("DO{0} mapping error, addr is {1}", item.Index, item.Addr));
                    }

                }

                //AI
                foreach (AI_ITEM item in _ioDefine[group].Ana_In)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;

                    int index = GetIndex(item.Addr, plcIoBaseAddress[2]);

                    if (index > 200)
                    {
                        LOG.Write(String.Format("AI {0} mapping index larger than buffer, addr is {1}", item.Index, item.Addr));
                    }

                    if (index >= 0)
                    {
                        AIAccessor aiAccessor = new AIAccessor(item.Name, index, _ai[group]);
                        _ioMap[item.Name] = aiAccessor;
                        aiAccessor.IoTableIndex = item.Index;
                        //DATA.Subscribe(String.Format("IO.AI.{0}", item.Name), () => { return aiAccessor.Value; });
                    }
                    else
                    {
                        LOG.Write(String.Format("AI{0} mapping error, addr is {1}", item.Index, item.Addr));
                    }
                }

                //AO
                foreach (AO_ITEM item in _ioDefine[group].Ana_Out)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;

                    int index = GetIndex(item.Addr, plcIoBaseAddress[3]);


                    if (index > 200)
                    {
                        LOG.Write(String.Format("AO {0} mapping index larger than buffer, addr is {1}", item.Index, item.Addr));
                    }

                    if (index >= 0)
                    {
                        AOAccessor aoAccessor = new AOAccessor(item.Name, index, _ao[group]);
                        _ioMap[item.Name] = aoAccessor;
                        aoAccessor.IoTableIndex = item.Index;
                        //DATA.Subscribe(String.Format("IO.AO.{0}", item.Name), () => { return aoAccessor.Value; });
                    }
                    else
                    {
                        LOG.Write(String.Format("AO{0} mapping error, addr is {1}", item.Index, item.Addr));
                    }
                }
            }
        }


        private int GetIndex(string addr, int first)
        {
            if (String.IsNullOrEmpty(addr))
            {
                LOG.Write("GetIndex addr is empty");
                return -1;
            }

            string[] parts = addr.Trim().ToUpper().Split('.');

            int len = parts.Length;

            if (len == 1)
            {
                string ch = parts[0].TrimStart('D');
                int index = Convert.ToUInt16(ch);

                return (index - first) / 2;
            }

            if (len == 2)
            {
                char[] trim = { 'W', 'C', 'I', 'O', ' ' };
                string ch = parts[0].TrimStart(trim);

                int index = Convert.ToUInt16(ch);

                int bit = Convert.ToUInt16(parts[1]);
                return (index - first) * 16 + bit;
            }

            LOG.Info("IOManager GetIndex error");

            return -1;
        }

        public bool CanSetDo(string doName, bool onOff, out string reason)
        {
            return _interlock.CanSetDo(doName, onOff, out reason);
        }

        public List<Tuple<int, int, string>> GetIONameList(string group, IOType ioType)
        {
            List<Tuple<int, int, string>> result = new List<Tuple<int, int, string>>();

            if (!_dicPlcIoBaseAddress.ContainsKey(group))
                return result;

            int[] plcIoBaseAddress = _dicPlcIoBaseAddress[group];


            switch (ioType)
            {
            case IOType.AI:
                foreach (AI_ITEM item in _ioDefine[group].Ana_In)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;

                    result.Add(Tuple.Create(item.Index, GetIndex(item.Addr, plcIoBaseAddress[2]), item.Name));
                }
                break;
            case IOType.AO:
                foreach (AO_ITEM item in _ioDefine[group].Ana_Out)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;

                    result.Add(Tuple.Create(item.Index, GetIndex(item.Addr, plcIoBaseAddress[3]), item.Name));
                }
                break;
            case IOType.DI:
                foreach (DI_ITEM item in _ioDefine[group].Dig_In)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;

                    result.Add(Tuple.Create(item.Index, GetIndex(item.Addr, plcIoBaseAddress[0]), item.Name));
                }
                break;
            case IOType.DO:
                foreach (DO_ITEM item in _ioDefine[group].Dig_Out)
                {
                    if (string.IsNullOrEmpty(item.Name))
                        continue;

                    result.Add(Tuple.Create(item.Index, GetIndex(item.Addr, plcIoBaseAddress[1]), item.Name));
                }
                break;
            }

            return result;
        }

        public void Terminate()
        {
            try
            {
                _monitorThread.Stop();
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }

        }

    }
}