using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.Event;

namespace Aitex.Core.RT.IOCore
{
    public class Interlock
    {
        string _errMessage;
        bool _isErr = false;
        object _locker = new object();

        Dictionary<IntlkNode, List<List<IntlkNode>>> _interlocks = new Dictionary<IntlkNode, List<List<IntlkNode>>>();

        List<SafePlcInterlockChecker> _safeDoList = new List<SafePlcInterlockChecker>();

        List<Tuple<int, int, string>> _diMap;
        List<Tuple<int, int, string>> _doMap;

        public Interlock()
        {

        }

        public void InitSafeDO(List<Tuple<DOAccessor, DIAccessor>> safedos)
        {
            lock (_locker)
            {
                _safeDoList.Clear();

                for (int i = 0; i < safedos.Count; i++)
                {
                    _safeDoList.Add(new SafePlcInterlockChecker(safedos[i].Item1, safedos[i].Item2));
                }                 
            }
        }

        public Tuple<int, int, string> FindDI(int iodefineIndex)
        {
            return _diMap.Find(item => item.Item1 == iodefineIndex);
        }

        public Tuple<int, int, string> FindDO(int iodefineIndex)
        {
            return _doMap.Find(item => item.Item1 == iodefineIndex);
        }

        public bool Init(string cfgFile, List<Tuple<int, int, string>> diMap, List<Tuple<int, int, string>> doMap, out string reason)
        {
            _interlocks.Clear();
            _errMessage = "";
            _isErr = false;
            _diMap = diMap;
            _doMap = doMap;

           
            if (_isErr)
                _isErr = true;

            try
            {


                //read interlock table from config file
                //example: ADO100:ADI100,ADI50,ADO55,!DI34,!TDI34&!TDO33,DO345
                using (StreamReader sr = new StreamReader(cfgFile))
                {
                    string strLine = sr.ReadLine();
                    while (strLine != null)
                    {
                        strLine = strLine.TrimStart(' ', '\t');
                        if (string.IsNullOrEmpty(strLine) || strLine[0] == '#')
                        {
                            strLine = sr.ReadLine();
                            continue;
                        }

                        string[] str = strLine.Split(new char[] { ':', ',', ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
                        IntlkNode key;
                        ParseIO(str[0], out key);
                        System.Diagnostics.Debug.Assert(!key.IoTypeIsDI);
                        List<List<IntlkNode>> rules = new List<List<IntlkNode>>();
                        for (int i = 1; i < str.Length; i++)
                        {
                            List<IntlkNode> singleRule = new List<IntlkNode>();
                            string[] slist = str[i].Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
                            foreach (var s1 in slist)
                            {
                                IntlkNode nd;
                                ParseIO(s1, out nd);
                                singleRule.Add(nd);
                            }
                            rules.Add(singleRule);
                        }
                        if (_interlocks.Keys.Contains(key))
                        {
                            _errMessage = string.Format("{0} is defined in more than one place.", str[0]);
                            _isErr = true;
                            //Notifier.PostEvent(EventEnum.InterlockFileBad, Reactor.PmName, _errMessage);
                            reason = _errMessage;
                            return false;
                        }
                        _interlocks.Add(key, rules);
                        strLine = sr.ReadLine();
                    }
                }

                //verify PmInterlock.cfg file
                foreach (var interlock in _interlocks)
                {
                    var ruleHead = interlock.Key;
                    if ((ruleHead.IoTypeIsDI && FindDI(ruleHead.Index)==null) ||
                        (!ruleHead.IoTypeIsDI && FindDO(ruleHead.Index)==null))
                    {
                        _errMessage = string.Format("{0}-{1} not exist.", ruleHead.IoTypeIsDI ? "DI" : "DO", ruleHead.Index);
                        _isErr = true;
                        //Notifier.PostEvent(EventEnum.InterlockFileBad, Reactor.PmName, _errMessage);
                        reason = _errMessage;
                        return false;
                    }

                    foreach (var rule in interlock.Value)
                    {
                        foreach (var r1 in rule)
                        {
                            if ((r1.IoTypeIsDI && FindDI(r1.Index) == null) ||
                                (!r1.IoTypeIsDI && FindDO(r1.Index) == null))
                            {
                                _errMessage = string.Format("{0}-{1} not exist", r1.IoTypeIsDI ? "DI" : "DO", r1.Index);
                                _isErr = true;
                                //Notifier.PostEvent(EventEnum.InterlockFileBad, Reactor.PmName, _errMessage);
                                reason = _errMessage;
                                return false;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _errMessage = ex.Message;
                _isErr = true;
                reason = _errMessage;
                LOG.Write(ex, string.Format("erro reading interlock config file {0}", cfgFile));
                return false;
            }
            reason = _errMessage;
            LOG.Write(string.Format("get {0} interlock items", _interlocks.Count));
            return true;
        }

        /// <summary>
        /// IO interlock monitoring
        /// </summary>
        public void Monitor()
        {
            //检查安全逻辑是否动作,如果有动作则撤销当前的DO设定值
            lock (_locker)
            {
                foreach (var safeItem in _safeDoList)
                {
                    safeItem.Monitor();
                }
            }

            //监测当前软件DO点是否存在互锁情况         
            foreach (var interlock in _interlocks)
            {
                try
                {
                    var failReasonList = new List<List<IntlkNode>>();
                    bool isTrigger = false;
                    foreach (var rule in interlock.Value)   //OR遍历
                    {
                        bool ret = true;
                        foreach (var item in rule)   //AND遍历
                        {
                            if (item.IoTypeIsDI && item.IsReversed)
                            {
                                ret &= (!IO.DI[FindDI(item.Index).Item3].RawData);
                            }
                            else if (item.IoTypeIsDI && !item.IsReversed)
                            {
                                ret &= IO.DI[FindDI(item.Index).Item3].RawData;
                            }
                            else if (!item.IoTypeIsDI && item.IsReversed)
                            {
                                ret &= (!IO.DO[FindDO(item.Index).Item3].Value);
                            }
                            else
                            {
                                ret &= IO.DO[FindDO(item.Index).Item3].Value;
                            }
                        }
                        if (ret)
                        {
                            var andList = new List<IntlkNode>(rule);
                            failReasonList.Add(andList);
                            isTrigger = true;
                        }
                    }

                    if (isTrigger)
                    {
                        if (IO.DO[FindDO(interlock.Key.Index).Item3].Value != interlock.Key.IsReversed)                       
                        {
                            string reason = string.Empty;
                                                        
                            reason = string.Format("DO-{0}({1}) can not match {2} interlock condition,force to {3}。",
                                interlock.Key.Index,
                                _doMap[interlock.Key.Index],
                                failReasonList.Count,
                                interlock.Key.IsReversed ? "ON" : "OFF");

                            for (int i = 0; i < failReasonList.Count; i++)
                            {
                                reason += string.Format("{0}{1}. ", i == 0 ? "" : "\n", i + 1);
                                for (int j = 0; j < failReasonList[i].Count; j++)
                                {
                                    var it = failReasonList[i][j];
                                    reason += string.Format("{0}{1}-{2}({3})[{4}]",
                                        j == 0 ? "" : " & ",
                                        it.IoTypeIsDI ? "DI" : "DO",
                                        it.Index,
                                        it.IoTypeIsDI ? _diMap[it.Index] : _doMap[it.Index],
                                        (it.IoTypeIsDI ? IO.DI[FindDI(it.Index).Item3].Value : IO.DO[FindDO(it.Index).Item3].Value) ? "ON" : "OFF");
                                }
                            }
                            //将DO进行强制操作
                            string reason1;
                            IO.DO[FindDO(interlock.Key.Index).Item3].SetValue(interlock.Key.IsReversed, out reason1);

                            //将互锁消息发送给用户界面
                            EV.PostMessage("IO", EventEnum.SwInterlock, "IO", reason);
                        }              
                    }
                }
                catch (Exception ex)
                {
                    LOG.Write(ex);
                }
            }
        }

        public bool CanSetDo(string doName, bool onOff, out string reason)
        {
            foreach (var item in _doMap)
            {
                if (item.Item3 == doName)
                    return CanSetDo(item.Item1, onOff, out reason);
            }
            throw new ApplicationException("Can not find DO:"+doName);
        }

        public bool CanSetDo(int doIndex, bool onOff, out string reason)
        {
            reason = string.Empty;
            bool isTrigger = false;
            var failReasonList = new List<List<IntlkNode>>();
            foreach (var interlock in _interlocks)
            {
                if (!(interlock.Key.Index == doIndex &&
                    interlock.Key.IoTypeIsDI == false))
                {
                    continue;
                }
                if (onOff == interlock.Key.IsReversed)
                {
                    //return true;
                    continue;
                }
                foreach (var rule in interlock.Value)
                {
                    bool ret = true;
                    foreach (var item in rule)
                    {
                        if (item.IoTypeIsDI && item.IsReversed)
                        {
                            ret &= (!IO.DI[FindDI(item.Index).Item3].RawData);
                        }
                        else if (item.IoTypeIsDI && !item.IsReversed)
                        {
                            ret &= IO.DI[FindDI(item.Index).Item3].RawData;
                        }
                        else if (!item.IoTypeIsDI && item.IsReversed)
                        {
                            ret &= (!IO.DO[FindDO(item.Index).Item3].Value);
                        }
                        else
                        {
                            ret &= IO.DO[FindDO(item.Index).Item3].Value;
                        }
                    }
                    if (ret)
                    {
                        var andList = new List<IntlkNode>(rule);
                        failReasonList.Add(andList);
                        isTrigger = true;
                    }
                }
            }
            if (isTrigger)
            {
                //1. DI-1(Earthquake)[OFF] & DI-12(Smoke)[ON]
                //2. DO-105(ServiceMode)[ON]
                for (int i = 0; i < failReasonList.Count; i++)
                {
                    reason += string.Format("{0}{1}. ", i == 0 ? "" : "\n", i + 1);
                    for (int j = 0; j < failReasonList[i].Count; j++)
                    {
                        var it = failReasonList[i][j];
                        reason += string.Format("{0}{1}-{2}({3})[{4}]",
                            j == 0 ? "" : " & ",
                            it.IoTypeIsDI ? "DI" : "DO",
                            it.Index,
                            it.IoTypeIsDI ? _diMap[it.Index] : _doMap[it.Index],
                            (it.IoTypeIsDI ? IO.DI[FindDI(it.Index).Item3].Value : IO.DO[FindDO(it.Index).Item3].Value) ? "ON" : "OFF");
                    }
                }
            }
            return !isTrigger;
        }

        void ParseIO(string ioName, out IntlkNode interlockNode)
        {
            interlockNode = new IntlkNode();
            ioName = ioName.Trim();
            if (ioName[0] == '!')
            {
                ioName = ioName.Remove(0, 1);
                interlockNode.IsReversed = true;
            }
            else
            {
                interlockNode.IsReversed = false;
            }
            switch (ioName.Substring(0, 2))
            {
                case "DO":
                    interlockNode.IoTypeIsDI = false;
                    break;
                case "DI":
                    interlockNode.IoTypeIsDI = true;
                    break;
                default:
                    throw new Exception("interlock file found error, the io name should be DO or DI leading" + ioName);
            }
            ioName = ioName.Remove(0, 2);
            interlockNode.Index = Convert.ToInt32(ioName);
            if (interlockNode.Index < 0 && interlockNode.Index >= 1000)
            {
                throw new Exception(string.Format("{0} index out of range", ioName));
            }
        }
    }
}