using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Xml;
using Aitex.Common.Util;
using Aitex.Core.Account;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.RecipeCenter;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
using Aitex.Triton160.RT.Module;
using FabConnect.SecsGemInterface.Common;
using FabConnect.SecsGemInterface.Common.ToolModel;
using FabConnect.SecsGemInterface.GemModel;

namespace Aitex.RT.FactoryAutomation
{
    public class FaHost : IFaHost
    {
        public string FaCommunicationState
        {
            get { return _gem.CommunicationState.ToString(); }
        }
        public string FaControlState
        {
            get { return _gem.ControlState.ToString(); }
        }
        public string FaControlSubState
        {
            get
            {
                return _gem.ControlRemoteSwitch.ToString(); 
            }
        }
        public bool IsConnected
        {
            get { return _gem.CommunicationState == CommunicationState.EnabledCommunicating; }
        }

        public int SpoolingState
        {
            get
            {
                return (int)_gem.SpoolingState;
            }
        }

        public string SpoolingActual
        {
            get
            {
                return

              _gem.GetAttribute(GEMVariables.SpoolCountActual, AttributeType.SV)
              ;
            }
        }

        public string SpoolingTotal
        {
            get { return _gem.GetAttribute(GEMVariables.SpoolCountTotal, AttributeType.SV); }
        }

        public string SpoolingFullTime
        {
            get { return _gem.GetAttribute(GEMVariables.SpoolFullTime, AttributeType.SV); }
        }

        public string SpoolingStartTime
        {
            get { return _gem.GetAttribute(GEMVariables.SpoolStartTime, AttributeType.SV); }
        }

        public bool IsSpoolingEnable
        {
            get { return Convert.ToBoolean(_gem.GetAttribute(GEMVariables.SpoolEnabled, AttributeType.EC)); }
        }

        private readonly GEMController _gem = new GEMController();
        public IHostCallback _equipment;
        private HashSet<string> _systemBuildInEc = new HashSet<string>();
        private HashSet<string> _systemBuildInVariables = new HashSet<string>();
        private PeriodicJob _faMonitorThread;
        private FixSizeQueue<FaEventItem> _lstEvent = new FixSizeQueue<FaEventItem>(300);
        FALogFileCleaner _logCleaner = new FALogFileCleaner();

        private ControlState _state = ControlState.Unknown;
        private const string EventTerminalMessage = "TerminalMessage";
        public void Initialize(IHostCallback equipment, string modelFile)
        {
            _equipment = equipment;

            _gem.CommunicationStateChanged += OnCommunicationStateChanged;
            _gem.RemoteCommandS2F49In += _gem_RemoteCommandS2F49In; ;

            _gem.PrimaryMessageIn += _gem_PrimaryMessageIn;
            _gem.ControlStateChanged += _gem_ControlStateChanged;
            _gem.ShowTrialMessageBox = false;

            _gem.AutoPPDataReply = false;

            _gem.Initialize(modelFile, PathManager.GetLogDir());

            _gem.EquipmentModel.GemConnection.HSMS.localIPAddress = SC.GetSCString("System_FaLocalIPAddress").Value;
            _gem.ReInitialize();

            //get system build in ec
            _systemBuildInEc = new HashSet<string>();
            _systemBuildInEc.Add("EstablishCommunicationsTimeout");
            _systemBuildInEc.Add("MaxSpoolTransmit");
            _systemBuildInEc.Add("OverWriteSpool");
            _systemBuildInEc.Add("MaxSpoolCapacity");
            _systemBuildInEc.Add("SpoolEnabled");
            _systemBuildInEc.Add("TimeFormat");

            //initial system build in variable
            _systemBuildInVariables.Add("AlarmsEnabled");
            _systemBuildInVariables.Add("AlarmsSet");
            _systemBuildInVariables.Add("Clock");
            _systemBuildInVariables.Add("ControlState");
            _systemBuildInVariables.Add("EventsEnabled");
            _systemBuildInVariables.Add("PPExecName");
            _systemBuildInVariables.Add("PreviousProcessState");
            _systemBuildInVariables.Add("ProcessState");
            _systemBuildInVariables.Add("SpoolCountActual");
            _systemBuildInVariables.Add("SpoolCountTotal");
            _systemBuildInVariables.Add("SpoolFullTime");
            _systemBuildInVariables.Add("SpoolStartTime");
            _systemBuildInVariables.Add("SpoolState");
            _systemBuildInVariables.Add("SpoolSubstate");

            _logCleaner.Run();

            _faMonitorThread = new PeriodicJob(200, MonitorFaTask, "Monitor FA Thread", true);


            EV.Subscribe(new EventItem("Host", EventTerminalMessage, "{0}", EventLevel.Warning, EventType.EventUI_Notify));
        }

        private void _gem_ControlStateChanged(object sender, SECsEventArgs e)
        {
            if (_state != _gem.ControlState)
            {
                _state = _gem.ControlState;
                if (_state == ControlState.OnlineRemote)
                {
                    EV.PostInfoLog("FA", Aitex.RT.Properties.Resources.HostControlModeChangeTo_stateNotifySystemChangeToAutoMode);
                    Singleton<PMEntity>.Instance.PostMsg((int)PMEntity.MSG.SetAutoMode);
                }
            }
        }

        public void Invoke(string method, object[] args)
        {
            switch (method)
            {
                case "FAEnable":
                    _gem.SetEnable();
                    break;
                case "FADisable":
                    _gem.SetDisable();
                    break;
                case "FAOnline":
                    _gem.SetOnline();
                    break;
                case "FAOffline":
                    _gem.SetOffline();
                    break;
                case "FALocal":
                    _gem.SetLocal();
                    break;
                case "FARemote":
                    _gem.SetRemote();
                    break;
                case "FAEnableSpooling":
                    SetEnableSpooling();
                    break;
                case "FADisableSpooling":
                    SetDisableSpooling();
                    break;
            }
        }

        private bool _gem_RemoteCommandS2F41In(SECsTransaction trans)
        {
            try
            {
                bool ret = true;
                string reason = string.Empty;
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(2, 42, false);
 
 
                reply.DataItem.AddList();
                if (ret)
                {
                    reply.DataItem[0].Add("HCACK", 0, SECsFormat.Binary);
                }
 

                _gem.SendReply(reply, trans.Id);
            }
            catch (Exception ex)
            {
                LOG.Write(string.Format("Handle_S2F41 Exception: {0}", ex.Message));
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(2, 42, false);
                reply.DataItem.AddList();
                reply.DataItem[0].Add("HCACK", 3/*At least one parameter is invalid*/, SECsFormat.Binary);
                reply.DataItem[0].AddList();
                reply.DataItem[0][1].AddList();
                reply.DataItem[0][1].Add("CPVALUE", ex.Message, SECsFormat.Ascii);
                _gem.SendReply(reply, trans.Id);
            }

            return true;
        }

        private bool _gem_RemoteCommandS2F49In(SECsTransaction trans)
        {
            try
            {
                bool ret = true;
                string reason = string.Empty;
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(2, 50, false);

                string remoteCommandName = trans.Primary.DataItem[0][2].ToString();
                switch (remoteCommandName.ToUpper())
                {
                    case "GOREMOTE":
                        break;

                    case "PP-SELECT":
                        {
                            if (trans.Primary.DataItem[0][3][0][0].ToString() != "RecipeId" ||
                                trans.Primary.DataItem[0][3][1][0].ToString() != "LotId" )
                                throw new Exception("PP-SELECT Command Format Not Correct");
                            string recipeName = trans.Primary.DataItem[0][3][0][1].ToString();
                            string lotId = trans.Primary.DataItem[0][3][1][1].ToString();

                            string jobId = "";

                            if (trans.Primary.DataItem[0][3][2][0].ToString() == "JobId")
                            {
                                jobId = trans.Primary.DataItem[0][3][2][1].ToString();
                            }

                            ret = _equipment.RunRecipe(lotId, jobId, recipeName,  out reason);
                        }
                        break;
                }

                reply.DataItem.AddList();
                if (ret)
                {
                    reply.DataItem[0].Add("HCACK", 0, SECsFormat.Binary);
                }
                else
                {
                    reply.DataItem[0].Add("HCACK", 3, SECsFormat.Binary);

                    reply.DataItem[0].Add("CPVALUE", reason, SECsFormat.Ascii);
                }

                _gem.SendReply(reply, trans.Id);
            }
            catch (Exception ex)
            {
                 SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(2, 50, false);
                reply.DataItem.AddList();
                reply.DataItem[0].Add("HCACK", 3/*At least one parameter is invalid*/, SECsFormat.Binary);
                reply.DataItem[0].AddList();
                reply.DataItem[0][1].AddList();
                reply.DataItem[0][1].Add("CPVALUE", ex.Message, SECsFormat.Ascii);
                _gem.SendReply(reply, trans.Id);
            }

            return true;
        }

        private void _gem_PrimaryMessageIn(object sender, SECsPrimaryInEventArgs e)
        {
            string SF = "S" + e.Inputs.Stream.ToString() + "F" + e.Inputs.Function.ToString();
            switch (SF)
            {
                case "S7F1":
                    Handle_S7F1(e);
                    break;
                case "S7F5":
                    Handle_S7F5(e);
                    break;
                case "S7F3"://down load recipe
                    Handle_S7F3(e); //Host must send S7F1/F2 and equipment application must either grant the host requests.
                    break;
                case "S7F17":
                    Handle_S7F17(e);
                    break;
                case "S7F19":
                    Handle_S7F19(e);
                    break;
                case "S7F25":
                    Handle_S7F25(e);
                    break;
                case "S10F3"://Single terminal message
                    Handle_S10F3(e);
                    break;
                case "S10F5"://Multiple terminal message
                    Handle_S10F5(e);
                    break;
            }

            switch (e.EventId)
            {
                case PrimaryEventType.ProcessProgramLoadInquire: //S7F1
                    break;
            }
        }
        /// <summary>
        /// Host requests the equipment to delete the Process Program
        /// L,n (Number of process programs to be deleted)
        ///     1. <PPID1>
        ///     .
        ///     .
        ///     n. <PPIDn>
        /// Exception: If n=0, then delete all.
        /// </summary>
        /// <param name="e"></param>
        private void Handle_S7F17(SECsPrimaryInEventArgs e)
        {
            try
            {
                string reason = string.Empty;

                if (e.Inputs.DataItem[0].Count == 0)
                {
                    LOG.Write("Handle_S7F17, delete all recipes");
                    DeleteAllRecipes(out reason);
                }
                else // Delete the list of selected Process Program
                {
                    List<string> deleteRecipes = new List<string>();
                    for (int i = 0; i < e.Inputs.DataItem[0].Count; i++)
                    {
                        string ppid = e.Inputs.DataItem[0][i].ToString();
                        deleteRecipes.Add(ppid);
                        LOG.Write("Handle_S7F17, delete recipe:" + ppid);
                    }
                    DeleteRecipe(deleteRecipes, out reason);
                }
                //give reply to host
                SECsMessage messageToReply = _gem.Services.ProcessProgram.DeleteProcessProgramAcknowledge(0);
                _gem.SendReply(messageToReply, e.TransactionID);
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S7F17 Exception: " + ex.Message);
                //give reply to host
                SECsMessage messageToReply = _gem.Services.ProcessProgram.DeleteProcessProgramAcknowledge(4/*error*/);
                _gem.SendReply(messageToReply, e.TransactionID);
            }
        }

        protected void DeleteAllRecipes(out string reason)
        {
            reason = string.Empty;


            var recipes = RecipeFileManager.Instance.GetSequenceNameList();


            foreach (var name in recipes)
            {
                if (!RecipeFileManager.Instance.DeleteSequence(name))
                {
                    EV.PostWarningLog("System", $"Can not delete {name}, delete all failed.");
                    return;
                }
            }

            EV.PostInfoLog("System", "All recipe deleted");
        }

        protected void DeleteRecipe(List<string> recipeFiles, out string reason)
        {
            reason = string.Empty;

            foreach (var name in recipeFiles)
            {
                if (!RecipeFileManager.Instance.DeleteSequence(name))
                {
                    EV.PostWarningLog("System", $"Can not delete {name}.");
                    return;
                }
                else
                {
                    EV.PostWarningLog("System", $"Delete recipe {name}.");
                }
            }
        }

        private void Handle_S7F3(SECsPrimaryInEventArgs e)
        {
            try
            {
                LOG.Write("Handle_S7F4, Request down load recipe");

                string ppid = e.Inputs.DataItem[0][0].Value.ToString();
                string reason = string.Empty;

                // Get the format of PPBODY
                SECsFormat bodyType = e.Inputs.DataItem[0][1].Format;
                bool recipeSuccess = false;

                if (bodyType == SECsFormat.Binary)
                {
                    recipeSuccess = DownLoadRecipeInByte(ppid, (byte[])e.Inputs.DataItem[0][1].Value, out reason);
                }
                else
                {
                    recipeSuccess = DownLoadRecipe(ppid, e.Inputs.DataItem[0][1].ToString(), out reason);
                }

                if (recipeSuccess)
                {
                    SECsMessage acknowledge = _gem.Services.ProcessProgram.ProcessProgramAcknowledge(0);
                    _gem.SendReply(acknowledge, e.TransactionID);
                }
                else
                {
                    //1:download recipe body fail:Permission Not Granted
                    //2:download recipe body fail:Length Error
                    //3:download recipe body fail:Matrix Overflow
                    //4:download recipe body fail:PPID Not Found
                    //5:download recipe body fail:Mode Unsupported
                    SECsMessage acknowledge = _gem.Services.ProcessProgram.ProcessProgramAcknowledge(5);
                    _gem.SendReply(acknowledge, e.TransactionID);
                }
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S7F4 exception:" + ex.Message);
                SECsMessage acknowledge = _gem.Services.ProcessProgram.ProcessProgramAcknowledge(1);
                _gem.SendReply(acknowledge, e.TransactionID);
            }
        }

        protected bool DownLoadRecipe(string name, string content, out string reason)
        {
            bool ret = false;
            reason = string.Empty;

            ret = RecipeFileManager.Instance.SaveSequence(name, content, false);

            if (!ret)
            {
                reason = string.Format("save recipe content failed,recipeName:{0}", name);
                LOG.Write(reason);
            }

            return ret;
        }


        protected bool DownLoadRecipeInByte(string name, byte[] content, out string reason)
        {

            bool ret = false;
            reason = string.Empty;

            ret = RecipeFileManager.Instance.SaveSequence(name, System.Text.Encoding.UTF8.GetString(content), false);

            if (!ret)
            {
                reason = string.Format("save recipe content failed,recipeName:{0}", name);
                LOG.Write(reason);
            }

            return ret;
        }

        /// <summary>
        /* 0 = OK
            1 = Already have
            2 = No space
            3 = Invalid PPID
            4 = Busy, try later
            5 = Will not accept
            >5 = Other error
            6-63 Reserved
        */
        ///</summary>
        /// <param name="e"></param>
        private void Handle_S7F1(SECsPrimaryInEventArgs e)
        {
            try
            {

                LOG.Write("Handle_S7F1, Process Program Load Inquire");

                string ppid = e.Inputs.DataItem[0][0].Value.ToString();
                var recipeList = RecipeFileManager.Instance.GetSequenceNameList();
                int ppGrant = 0;
                if (!recipeList.Contains(ppid))
                    ppGrant = 3;

                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 2, false);
                reply.DataItem.Add("PPGNT", ppGrant, SECsFormat.Binary);

                _gem.SendReply(reply, e.TransactionID);
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S7F1 exception:" + ex.Message);
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 0, false);
                _gem.SendReply(reply, e.TransactionID);
            }
        }


        private void Handle_S7F5(SECsPrimaryInEventArgs e)
        {
            try
            {
                LOG.Write("Handle_S7F5,Used to request the transfer of a process program");
                string ppid = e.Inputs.DataItem[0].ToString();
                string reason = string.Empty;

                string data = RecipeFileManager.Instance.GetSequence(ppid, false);

                if (!string.IsNullOrEmpty(data))
                {

                    byte[] ppbody = System.Text.Encoding.UTF8.GetBytes(data);
                    SECsMessage recipetToReply = _gem.Services.ProcessProgram.ProcessProgramData(ppid, ppbody);

                    _gem.SendReply(recipetToReply, e.TransactionID);
                }
                else
                {
                    SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 6, false);
                    reply.DataItem.AddList();
                    reply.DataItem[0].Add("RVIACK", 0x01/*abnormal*/, SECsFormat.Binary);
                    reply.DataItem[0].Add("CPVALUE", reason, SECsFormat.Ascii);
                    _gem.SendReply(reply, e.TransactionID);
                }
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S7F6 exception:" + ex.Message);
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 6, false);
                reply.DataItem.AddList();
                _gem.SendReply(reply, e.TransactionID);
            }
        }


        private void Handle_S7F19(SECsPrimaryInEventArgs e)
        {
            try
            {
                string reason = string.Empty;
                LOG.Write("Handle_S7F19, Request the transmission of the current equipment process program directory");
                var recipeList = RecipeFileManager.Instance.GetSequenceNameList();
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 20, false);
                reply.DataItem.AddList();
                for (int rNum = 0; rNum < recipeList.Count; rNum++)
                {
                    reply.DataItem[0].Add("PPID", recipeList[rNum], SECsFormat.Ascii);
                }
                _gem.SendReply(reply, e.TransactionID);
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S7F19 exception:" + ex.Message);
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 0, false);
                _gem.SendReply(reply, e.TransactionID);
            }
        }

        private void Handle_S7F25(SECsPrimaryInEventArgs e)
        {
            try
            {
                var ppid = e.Inputs.DataItem[0].Value.ToString();

                LOG.Write("Handle_S7F25, request a particular process program from the other");

                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 26, false);
                reply.DataItem.AddList();

                var parameters = GetRecipe(ppid, out string reason);

                if (parameters != null)
                {
                    reply.DataItem[0].Add("PPID", ppid, SECsFormat.Ascii);
                    reply.DataItem[0].Add("MDLN", _gem.EquipmentModel.Nameable.model, SECsFormat.Ascii);
                    reply.DataItem[0].Add("SOFTREV", _gem.EquipmentModel.Nameable.softwareRev, SECsFormat.Ascii);
                    reply.DataItem[0].AddList();
                    reply.DataItem[0][3].AddList();
                    reply.DataItem[0][3][0].Add("CCODE", 1, SECsFormat.U1);
                    reply.DataItem[0][3][0].AddList();

                    for (int i = 0; i < parameters.Count; i++)
                    {
                        reply.DataItem[0][3][0][1].Add($"PPARM{i + 1}", parameters[i], SECsFormat.Ascii);
                    }
                }
                else
                {
                    EV.PostWarningLog("FA", $"Request {ppid} failed, {reason}");
                }

                _gem.SendReply(reply, e.TransactionID);
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S7F25 exception:" + ex.Message);
                SECsMessage reply = _gem.Services.CustomMessage.CreateMessage(7, 0, false);
                _gem.SendReply(reply, e.TransactionID);
            }
        }

        public List<string> GetRecipe(string pathName, out string reason)
        {
            List<string> result = new List<string>();
            reason = string.Empty;

            try
            {

                string content = string.Empty;
                
                if (RecipeFileManager.Instance.CheckSequenceFileExist(pathName))
                    content = RecipeFileManager.Instance.GetSequence(pathName, false);

                if (string.IsNullOrEmpty(content))
                {
                    if (pathName.StartsWith("PMA") && !pathName.StartsWith("PMA\\Process") &&
                        !pathName.StartsWith("PMA\\Clean"))
                        pathName = pathName.Replace("PMA", "PMA\\Process");
                    if (pathName.StartsWith("PMB") && !pathName.StartsWith("PMB\\Process") &&
                        !pathName.StartsWith("PMB\\Clean"))
                        pathName = pathName.Replace("PMB", "PMB\\Process");

                    content = RecipeFileManager.Instance.LoadRecipe("", pathName, false);
                }

                result.Add(content);
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
                reason = ex.Message;
            }


            return result;
        }

        public List<string> GetFormattedSequence(string sequenceName, out string reason)
        {
            List<string> result = new List<string>();

            try
            {
                string sequenceData = RecipeFileManager.Instance.GetSequence(sequenceName, false);
                XmlDocument recipeDoc = new XmlDocument();
                recipeDoc.LoadXml(sequenceData);


                XmlNodeList stepNodeList = recipeDoc.SelectNodes("/Aitex/TableSequenceData/Step");
                for (int i = 0; i < stepNodeList.Count; i++)
                {
                    XmlElement stepNode = stepNodeList[i] as XmlElement;
                    Dictionary<string, string> dic = new Dictionary<string, string>();

                    //遍历Step节点
                    foreach (XmlAttribute att in stepNode.Attributes)
                    {
                        result.Add($"STEP{i + 1}/{att.Name}:{att.Value}");
                    }
                }

                reason = string.Empty;
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
                reason = ex.Message;
            }

            return result;
        }


        public List<string> GetFormattedRecipe(string recipeName, out string reason)
        {
            List<string> result = new List<string>();

            try
            {
                string sequenceData = RecipeFileManager.Instance.LoadRecipe("", recipeName, false);
                XmlDocument recipeDoc = new XmlDocument();
                recipeDoc.LoadXml(sequenceData);


                XmlNodeList stepNodeList = recipeDoc.SelectNodes("/TableRecipeData/Step");
                for (int i = 0; i < stepNodeList.Count; i++)
                {
                    XmlElement stepNode = stepNodeList[i] as XmlElement;
                    Dictionary<string, string> dic = new Dictionary<string, string>();

                    //遍历Step节点
                    foreach (XmlAttribute att in stepNode.Attributes)
                    {
                        result.Add($"STEP{i + 1}/{att.Name}:{att.Value}");
                    }
                    //遍历Step子节点中所有的attribute属性节点
                    foreach (XmlElement subStepNode in stepNode.ChildNodes)
                    {
                        foreach (XmlAttribute att in subStepNode.Attributes)
                        {
                            result.Add($"STEP{i + 1}/{subStepNode.Name}/{att.Name}:{att.Value}");
                        }
                        //遍历Step子节点的子节点中所有的attribute属性节点
                        foreach (XmlElement subsubStepNode in subStepNode.ChildNodes)
                        {
                            foreach (XmlAttribute att in subsubStepNode.Attributes)
                            {
                                result.Add($"STEP{i + 1}/{subStepNode.Name}/{subsubStepNode.Name}/{att.Name}:{att.Value}");
                            }
                        }
                    }

                }

                reason = string.Empty;
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
                reason = ex.Message;
            }

            return result;
        }

        /// <summary>
        /// Single terminal message process
        /// S10F3 Format
        /// L,2
        ///     1. <TID>
        ///     2. <TEXT>
        /// </summary>
        /// <param name="e"></param>
        private void Handle_S10F3(SECsPrimaryInEventArgs e)
        {
            try
            {
                LOG.Write("Process host single terminal message");
                string terminalMessage = e.Inputs.DataItem["Ln"]["TEXT"].ToString();
                // notify equipment to show terminal message
                OnReceivedSingleTerminalMessage(terminalMessage);
                // acknowledge Host
                SECsMessage reply = _gem.Services.TerminalDisplay.TerminalDisplayAcknowledge(0);
                reply.Function = 4;
                _gem.SendReply(reply, e.TransactionID);
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S10F3 Exception: " + ex.Message);
                SECsMessage reply = _gem.Services.TerminalDisplay.TerminalDisplayAcknowledge(1/*will not display*/);
                reply.Function = 4;
                _gem.SendReply(reply, e.TransactionID);
            }
        }

        /// <summary>
        /// Multiple terminal message process
        /// S10F5 Format
        /// L,2
        ///      1. <TID>
        ///      2. L,N
        ///         1. <TEXT1>
        ///         .
        ///         .
        ///         n.<TEXTn>
        /// </summary>
        /// <param name="e"></param>
        private void Handle_S10F5(SECsPrimaryInEventArgs e)
        {
            try
            {
                LOG.Write("Process host multiple terminal message");
                List<string> receivedMultipleMessages = new List<string>();
                string message = string.Empty;
                for (int i = 0; i < e.Inputs.DataItem[0][1].Count; i++)
                {
                    receivedMultipleMessages.Add((string)e.Inputs.DataItem[0][1][i].Value);
                }
                // notify equipment to show terminal message
                OnReceivedMultipleTerminalMessage(receivedMultipleMessages);
                // acknowledge Host
                SECsMessage reply = _gem.Services.TerminalDisplay.TerminalDisplayAcknowledge(0);
                reply.Function = 6;
                _gem.SendReply(reply, e.TransactionID);
            }
            catch (Exception ex)
            {
                LOG.Write("Handle_S10F5 Exception: " + ex.Message);
                SECsMessage reply = _gem.Services.TerminalDisplay.TerminalDisplayAcknowledge(1/*will not display*/);
                reply.Function = 6;
                _gem.SendReply(reply, e.TransactionID);
            }
        }

        /// <summary>
        /// On single terminal message received
        /// </summary>
        /// <param name="terminalMessage"></param>
        protected virtual void OnReceivedSingleTerminalMessage(string terminalMessage)
        {
            EV.Notify("Host", EventTerminalMessage, terminalMessage);
        }

        /// <summary>
        /// On multiple terminal message received
        /// </summary>
        /// <param name="terminalMessage"></param>
        protected virtual void OnReceivedMultipleTerminalMessage(List<string> terminalMessage)
        {
            foreach (var message in terminalMessage)
            {
                EV.Notify("Host", EventTerminalMessage, message);
            }
        }
        public void Terminate()
        {
            _faMonitorThread.Stop();
            _logCleaner.Stop();

        }

        public void Enable()
        {
            _gem.SetEnable();
        }

        public void Disable()
        {
             _gem.SetDisable();
        }

        public void NotifyEvent(string eventName, Dictionary<string, string> dvid, Dictionary<string, object> objDvid)
        {
            _lstEvent.Enqueue(new FaEventItem() { dvid = dvid, objDvid = objDvid, EventName = eventName, IsAlarm = false });
        }

        public void NotifyAlarm(string alarmName, Dictionary<string, string> dvid, Dictionary<string, object> objDvid, string text)
        {
            _lstEvent.Enqueue(new FaEventItem() { dvid = dvid, objDvid = objDvid, EventName = alarmName, IsAlarm = true, Text = text});
        }

        public void NotifyEvent(string eventName, Dictionary<string, string> dvid)
        {
            _lstEvent.Enqueue(new FaEventItem(){dvid = dvid,EventName = eventName,IsAlarm = false});
        }

        public void NotifyAlarm(string alarmName, Dictionary<string, string> dvid, string text)
        {
            _lstEvent.Enqueue(new FaEventItem() { dvid = dvid, EventName = alarmName, IsAlarm = true, Text = text});
        }

        public void SetLocalControl()
        {
            Task.Factory.StartNew(() =>
            {
                _gem.SetLocal();
            });
        }

        public void SetRemoteControl()
        {
            Task.Factory.StartNew(() =>
            {
                _gem.SetRemote(); 
            });
        }

        public void SetEnableSpooling()
        {
            Task.Factory.StartNew(() =>
            {
                _gem .SetAttribute(GEMVariables.SpoolEnabled, AttributeType.EC, "true");
            });
        }
        public void SetDisableSpooling()
        {
            Task.Factory.StartNew(() =>
            {
                _gem .SetAttribute(GEMVariables.SpoolEnabled, AttributeType.EC, "false");
            });
        }

        public bool MonitorFaTask()
        {
            try
            {
                SynchronizeSVIDValue();

                FaEventItem ev;
                while (_lstEvent.TryDequeue(out ev))
                {
                    if (ev.dvid != null)
                    {
                        foreach (var dvid in ev.dvid)
                        {
                            SetDVIDValue(dvid.Key, dvid.Value);
                        }
                    }

                    if (ev.objDvid != null)
                    {
                        foreach (var dvid in ev.objDvid)
                        {
                            SetDVIDValue(dvid.Key, dvid.Value);
                        }
                    }
                    SetDVIDValue(DVIDName.EventName, ev.EventName);
                    if (ev.IsAlarm)
                    {
                        SetAlarm(ev.EventName, ev.Text);
                    }
                    else
                    {
                        SendEvent(ev.EventName);
                    }

                    //Thread.Sleep(500);
                }


            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex);
            }

            return true;
        }



        private void OnCommunicationStateChanged(object sender, SECsEventArgs e)
        {
            if (_gem.CommunicationState == CommunicationState.EnabledCommunicating)
            {
                _gem.SetOnline();
                _gem.SetRemote();
            }
        }


        /// <summary>
        /// Send terminal message to host
        /// </summary>
        /// <param name="message"></param>
        public void SendTerminalMessageToHost(string message)
        {
            if (!_gem.IsConnected || _gem.CommunicationState == CommunicationState.Disabled
                                  || _gem.CommunicationState == CommunicationState.WaitDelay
                                  || _gem.CommunicationState == CommunicationState.EnabledNotCommunicating)
            {
                EV.PostWarningLog("FA", "Host not connected, send terminal message failed.");
                return;
            }
            LOG.Write("Send terminal message to host:" + message);
            Task.Factory.StartNew(() =>
            {
                SECsMessage secsMsg = _gem.Services.TerminalDisplay.TerminalRequest(message);
                _gem.Send(secsMsg);
            });
        }

        public void SynchronizeSVIDValue()
        {
            try
            {
                foreach (SVID sv in _gem.EquipmentModel.StatusVariables.SVIDCollection)
                {
                    if (sv != null && !_systemBuildInVariables.Contains(sv.logicalName))
                    {
                        if (sv.valueType == SECSFormats.List)
                        {
                            List<string> svData = _equipment.GetListSvidValue(sv.logicalName);
                            if (svData != null && svData.Count > 0)
                            {
                                SECsDataItem data = new SECsDataItem(SECsFormat.List);
                                foreach (var item in svData)
                                {
                                    data.Add(item, item);
                                }

                                _gem.SetListAttribute(sv.logicalName, AttributeType.SV, data);
                            }
                            else
                            {
                                SECsDataItem data = new SECsDataItem(SECsFormat.List);
                                data.Clear();

                                _gem.SetListAttribute(sv.logicalName, AttributeType.SV, data);
                            }
                        }
                        else
                        {
                            string svDataValue = _equipment.GetSvidValue(sv.logicalName);
                            if (!string.IsNullOrEmpty(svDataValue))
                            {
                                if (sv.valueType == SECSFormats.Boolean) svDataValue = ConvertToBoolean(svDataValue).ToString();
                                _gem.SetAttribute(sv.logicalName, AttributeType.SV, svDataValue);
                            }
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                LOG.Write("Synchronize FA Model Data exception:" + ex.Message);
            }
        }
 

        private bool SetDVIDValue(string localName, string value)
        {
            if (value == null) return false;
            //if (!string.IsNullOrEmpty(value))
            //{
            if (_gem.EquipmentModel == null || !_gem.EquipmentModel.DataVariables.DVIDCollection.IsExistLogicalName(localName))
                return false;


            foreach (VariableType dv in _gem.EquipmentModel.DataVariables.DVIDCollection)
            {
                if (dv.logicalName == localName)
                {
                    if (dv.valueType == SECSFormats.Boolean)
                    {
                        value = ConvertToBoolean(value).ToString();
                    }

                    break;
                }
            }

            _gem.SetAttribute(localName, AttributeType.DV, value);
            //}

            return true;
        }

        private bool ConvertToBoolean(string value)
        {
            if (value == "0" || value.ToLower() == "false") return false;
            else return true;
        }


        private void SendEvent(string eventName)
        {
            try
            {
                if (_gem.EquipmentModel != null)
                {
                    var allEvents = _gem.GetAllEnabledEvents();
                    if (allEvents.Contains(eventName))
                    {
                        _gem.SendCollectionEvent(eventName);
                    }
                    else
                    {
                        //LOG.Write(string.Format("sendEvent failed,not find:", eventName));
                    }
                    //LOG.Write(string.Format("【FA2SendEvent--{0}】", eventName));
                }
            }
            catch (Exception ex)
            {
                LOG.Write("SendEvent Error:" + ex.Message);
            }
        }

        public void SetAlarm(string alarmTag, string text)
        {
            Task.Factory.StartNew(() =>
            {
                //if (_gem.IsAlarmSet(alarmTag))
                //{
                //    _gem.ClearAlarm(alarmTag);
                //}
                var allAlarms = _gem.GetAllEnabledAlarms();
                if (allAlarms.Contains(alarmTag))
                {
                    _gem.EquipmentModel.Alarms[alarmTag].description = text;

                    _gem.SetAlarm(alarmTag);
                }
                else
                {
                    //WriteLog(string.Format("sendAlarm failed,not find:", alarmTag));
                }
                //WriteLog(string.Format("【FA3SetAlarm--{0}】", alarmTag));
            });
        }

        private bool SetDVIDValue(string localName, object value)
        {
            if (value == null) return false;

            if (_gem.EquipmentModel == null || !_gem.EquipmentModel.DataVariables.DVIDCollection.IsExistLogicalName(localName))
                return false;

            foreach (VariableType dv in _gem.EquipmentModel.DataVariables.DVIDCollection)
            {
                if (dv.logicalName == localName)
                {
                    if (dv.valueType == SECSFormats.Boolean)
                    {
                        value = ConvertToBoolean(Convert.ToString(value)).ToString();
                    }

                    break;
                }
            }

            _gem.SetAttribute(localName, AttributeType.DV, Convert.ToString(value));


            return true;
        }

        public void ClearAlarm(string alarmTag)
        {
            try
            {
                var allsetalarms = _gem.GetAllSetAlarms();
                if (allsetalarms != null && allsetalarms.Count > 0 && allsetalarms.Contains(alarmTag))
                    _gem.ClearAlarm(alarmTag);

                if (string.IsNullOrEmpty(alarmTag))
                {
                    foreach (var allsetalarm in allsetalarms)
                    {
                        _gem.ClearAlarm(allsetalarm.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                LOG.Write($"Clear Alarm Error : {ex.Message}");
            }
        }

    }
}