using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using System.Xml;
using Aitex.Core.RT.Log;
using Caliburn.Micro.Core;
using DocumentFormat.OpenXml.Office2010.Drawing;
using MECF.Framework.Common.Account.Extends;
using MECF.Framework.Common.Account.Permissions;
using MECF.Framework.Common.DataCenter;
using MECF.Framework.Common.MECF.Framework.Common.RecipeCenter.Recipe;
using MECF.Framework.UI.Client.CenterViews.Editors;
using MECF.Framework.UI.Client.CenterViews.Editors.Recipe;
using MECF.Framework.UI.Client.ClientBase;
using MECF.Framework.UI.Client.RecipeEditorLib.DGExtension.CustomColumn;
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params;
using Sicentury.Core;
using Sicentury.Core.Collections;

namespace MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel
{
    /// <summary>
    /// 配方对象。
    /// </summary>
    /// <remarks>
    /// 每个配方由<see cref="RecipeStep"/>配方步骤的数组组成。
    /// </remarks>
    public class RecipeData : PropertyChangedBase
    {
        #region Variables

        /// <summary>
        /// 当调用<see cref="Validate"/>方法后引发此事件。
        /// </summary>
        public event EventHandler OnValidated;

        /// <summary>
        /// 当参数访问白名单发生变化时引发此事件。
        /// </summary>
        public event EventHandler<int> OnAccessibleWhitelistChanged;

        private IRecipeGasFlowCalculator _gasFlowCalculator;
        private readonly RecipeFormatBuilder _formatBuilder;

        private XmlDocument _doc;
        private bool _isSavedDesc;
        private string _name;
        private string _chamberType;
        private string _recipeVersion;
        private string _prefixPath;
        private string _creator;
        private DateTime _createTime;
        private string _description;
        private string _devisor;
        private DateTime _deviseTime;
        private int _recipeTotalTime;
        private bool _isHideParamValueNotInWhitelist;
        private Dictionary<string, List<string>> _highlightDic;
        private bool _hasHighLight;
        #endregion

        #region Constructors

        /// <summary>
        /// 创建配方对象的实例。
        /// </summary>
        /// <param name="gasFlowCalculator">
        /// 配方步骤气体流量计算器，请参考：<see cref="IRecipeGasFlowCalculator"/>。
        /// </param>
        /// <param name="type">Recipe类型</param>
        public RecipeData(IRecipeGasFlowCalculator gasFlowCalculator, RecipeType type)
        {
            RecipeType = type;
            _formatBuilder = new RecipeFormatBuilder();

            ValidationErrorInfo = new ObservableRangeCollection<RecipeStepValidationInfo>();

            Steps = new RecipeStepCollection(this);
            Steps.OnSavedStateChanged += (sender, e) =>
            {
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsChanged)));
            };
            Steps.HighlightStateChanged += (sender, e) =>
            {
                if (IsAccessibleWhitelistEditMode)
                {
                    // 当参数高亮发生变化时，统计高亮的参数总数
                    var cnt = CountAccessibleWhitelistParams();
                    OnAccessibleWhitelistChanged?.Invoke(this, cnt);
                }
            };
            Steps.GasFlowCalculated += (sender, args) =>
            {
                // 当流量计算完成时，立即校验Recipe
                Validate();
            };
            Steps.StepTimeChanged += (sender, args) =>
            {
                RecipeTotalTime = (int)Steps.GetTotalTime();
            };

            StepTolerances = new RecipeStepCollection(this);
            PopSettingSteps = new Dictionary<string, RecipeStepCollection>();
            PopEnable = new Dictionary<string, bool>();
            ConfigItems = new RecipeStep(null);
            IsSavedDesc = true;

            _doc = new XmlDocument();
            var node = _doc.CreateElement("Aitex");
            node.AppendChild(_doc.CreateElement("TableRecipeData"));

            _doc.AppendChild(node);

            _gasFlowCalculator = gasFlowCalculator ?? new RecipeGasFlowCalculatorBase();
            LOG.Info($"Recipe Gas Flow Calculator : {_gasFlowCalculator.GetType().FullName}");
        }

        #endregion

        #region Properties

        /// <summary>
        /// 指示recipe高亮的参数
        /// </summary>
        public Dictionary<string, List<string>> HighlightDic
        {
            get => _highlightDic;
            set => _highlightDic = value;
        }

        /// <summary>
        /// 确认recipe是否被高亮
        /// </summary>
        public bool HasHighLight
        {
            get => _hasHighLight;
            private set => _hasHighLight = value;
        }

        public RecipeType RecipeType { get; }

        public bool IsChanged
        {
            get
            {
                var changed = !IsSavedDesc;

                if (!changed)
                    changed = ChkChanged(Steps) || ChkChanged(PopSettingSteps);

                if (!changed)
                {
                    foreach (var config in ConfigItems)
                    {
                        if (!config.IsSaved)
                        {
                            changed = true;
                            break;
                        }
                    }
                }
                return changed;
            }
        }

        public bool IsSavedDesc
        {
            get => _isSavedDesc;
            set
            {
                _isSavedDesc = value;
                NotifyOfPropertyChange();
            }
        }

        public string Name
        {
            get => _name;
            set
            {
                _name = value;
                NotifyOfPropertyChange();
            }
        }

        public string FullName => $"{PrefixPath}\\{Name}";

        public string RecipeChamberType
        {
            get => _chamberType;
            set
            {
                _chamberType = value;
                NotifyOfPropertyChange();
            }
        }

        public string RecipeVersion
        {
            get => _recipeVersion;
            set
            {
                _recipeVersion = value;
                NotifyOfPropertyChange();
            }
        }

        public string PrefixPath
        {
            get => _prefixPath;
            set
            {
                _prefixPath = value;
                NotifyOfPropertyChange();
            }
        }

        public string Creator
        {
            get => _creator;
            set
            {
                _creator = value;
                NotifyOfPropertyChange();
            }
        }

        public DateTime CreateTime
        {
            get => _createTime;
            set
            {
                _createTime = value;
                NotifyOfPropertyChange();
            }
        }

        public string Description
        {
            get => _description;
            set
            {
                _description = value;
                NotifyOfPropertyChange();
            }
        }

        public string Revisor
        {
            get => _devisor;
            set
            {
                _devisor = value;
                NotifyOfPropertyChange();
            }
        }

        public DateTime ReviseTime
        {
            get => _deviseTime;
            set
            {
                _deviseTime = value;
                NotifyOfPropertyChange();
            }
        }

        public int RecipeTotalTime
        {
            get => _recipeTotalTime;
            set
            {
                _recipeTotalTime = value;
                NotifyOfPropertyChange();
            }
        }

        public List<EditorDataGridTemplateColumnBase> Columns { get; private set; }

        public RecipeStepCollection Steps { get; private set; }

        public Dictionary<string, RecipeStepCollection> PopSettingSteps { get; private set; }

        public RecipeStepCollection StepTolerances { get; private set; }

        public RecipeStep ConfigItems { get; private set; }
        
        /// <summary>
        /// 返回当前Recipe所属的反应腔。
        /// </summary>
        public string Module { get; private set; }

        public bool ToleranceEnable { get; set; }

        public Dictionary<string, bool> PopEnable { get; set; }

        public bool IsCompatibleWithCurrentFormat { get; set; }

        private bool _isAccessibleWhitelistEditMode;

        /// <summary>
        /// 设置或返回当前Recipe是否处于访问白名单编辑模式。
        /// </summary>
        public bool IsAccessibleWhitelistEditMode
        {
            get=> _isAccessibleWhitelistEditMode;
            private set
            {
                _isAccessibleWhitelistEditMode = value;
                NotifyOfPropertyChange();
            }
        }

        /// <summary>
        /// 设置或返回是否启用访问白名单。
        /// </summary>
        /// <remarks>
        /// 当启用访问白名单时，Recipe编辑器中的参数值将显示为***，仅在白名单中的参数显示具体数值。
        /// </remarks>
        public bool IsEnableAccessibleWhitelist
        {
            get => _isHideParamValueNotInWhitelist;
            set
            {
                _isHideParamValueNotInWhitelist = value;
                if (_isHideParamValueNotInWhitelist)
                {
                    // 关闭白名单之外的所有参数的数值显示，用“***”代替。
                    HideValueButAccessibleWhitelist().Wait();
                }
                else
                {
                    // 恢复到角色设定的访问权限
                    var role = BaseApp.Instance.UserContext.Role;
                    UpdatePermission(role);
                }
            }
        }

        /// <summary>
        /// 返回当前配方校验错误信息摘要。
        /// </summary>
        public string ValidationErrorSummary { get; private set; }

        /// <summary>
        /// 返回当前配方校验失败的参数列表。
        /// </summary>
        public ObservableRangeCollection<RecipeStepValidationInfo> ValidationErrorInfo { get; }

        /// <summary>
        /// 返回当前配方校验错误的参数的总数。
        /// </summary>
        public int ValidationErrorCount { get; private set; }

        #endregion

        #region Methods

        public void SetRecipeGasFlowCalculator(IRecipeGasFlowCalculator gasFlowCalculator)
        {
            if (gasFlowCalculator != null )
                _gasFlowCalculator = gasFlowCalculator;
        }

        private void SetAttribute(RecipeStep parameters, XmlElement popSettingStep)
        {
            if (parameters != null)

                foreach (var parameter1 in parameters)
                {
                    if (parameter1.Visible != Visibility.Visible)
                        continue;

                    if (parameter1 is IntParam param)
                        popSettingStep.SetAttribute(param.ControlName, param.Value.ToString());
                    else if (parameter1 is DoubleParam doubleParam)
                        popSettingStep.SetAttribute(doubleParam.ControlName, doubleParam.Value.ToString());
                    else if (parameter1 is StringParam stringParam)
                        popSettingStep.SetAttribute(stringParam.ControlName, stringParam.Value);
                    else if (parameter1 is ComboxParam comboBoxParam)
                        popSettingStep.SetAttribute(comboBoxParam.ControlName, comboBoxParam.Value.ControlName);
                    else if (parameter1 is LoopComboxParam loopComboxParam)
                        popSettingStep.SetAttribute(loopComboxParam.ControlName, loopComboxParam.Value);
                    else if (parameter1 is PositionParam positionParam)
                        popSettingStep.SetAttribute(positionParam.ControlName, positionParam.Value);
                    else if (parameter1 is BoolParam boolParam)
                        popSettingStep.SetAttribute(boolParam.ControlName, boolParam.Value.ToString());
                    else if (parameter1 is StepParam stepParam)
                        popSettingStep.SetAttribute(stepParam.ControlName, stepParam.Value.ToString());
                    else if (parameter1 is MultipleSelectParam selectParam)
                    {
                        var selected1 = new List<string>();
                        selectParam.Options.Apply(
                            opt =>
                            {
                                if (opt.IsChecked)
                                    selected1.Add(opt.ControlName);
                            }
                            );
                        popSettingStep.SetAttribute(selectParam.ControlName, string.Join(",", selected1));
                    }
                }
        }

        private void LoopCellFeedback(Param cell)
        {
            var loopCell = cell as LoopComboxParam;
            var rowIndex = -1;
            var colIndex = -1;

            for (var i = 0; i < Steps.Count; i++)
            {
                for (var j = 0; j < Steps[i].Count; j++)
                {
                    if (Steps[i][j] == loopCell)
                    {
                        rowIndex = i;
                        colIndex = j;
                    }
                }
            }

            if (rowIndex < 0 || colIndex < 0)
                return;

            for (var i = 0; i < Steps.Count; i++)
            {
                loopCell = Steps[i][colIndex] as LoopComboxParam;
                var loopStr = loopCell.Value;

                var isLoopStart = Regex.IsMatch(loopStr, @"^Loop\x20x\d+$");
                var isLoopEnd = Regex.IsMatch(loopStr, @"^Loop End$");
                var isNullOrEmpty = string.IsNullOrWhiteSpace(loopStr);

                if (!isLoopEnd && !isLoopStart && !isNullOrEmpty)
                {
                    loopCell.IsLoopStep = true;
                    loopCell.IsValidLoop = false;
                    continue;
                }

                if (isLoopEnd)
                {
                    loopCell.IsLoopStep = true;
                    loopCell.IsValidLoop = false;
                    continue;
                }

                if (isLoopStart)
                {
                    if (i + 1 == Steps.Count)
                    {
                        loopCell.IsLoopStep = true;
                        loopCell.IsValidLoop = true;
                    }

                    for (var j = i + 1; j < Steps.Count; j++)
                    {
                        var loopCell2 = Steps[j][colIndex] as LoopComboxParam;
                        var loopStr2 = loopCell2.Value;
                        var isLoopStart2 = Regex.IsMatch(loopStr2, @"^Loop\x20x\d+$");
                        var isLoopEnd2 = Regex.IsMatch(loopStr2, @"^Loop End$");
                        var isNullOrEmpty2 = string.IsNullOrWhiteSpace(loopStr2);

                        if (!isLoopEnd2 && !isLoopStart2 && !isNullOrEmpty2)
                        {
                            for (var k = i; k < j + 1; k++)
                            {
                                (Steps[k][colIndex] as LoopComboxParam).IsLoopStep = true;
                                (Steps[k][colIndex] as LoopComboxParam).IsValidLoop = false;
                            }
                            i = j;
                            break;
                        }
                        if (isLoopStart2)
                        {
                            loopCell.IsLoopStep = true;
                            loopCell.IsValidLoop = true;
                            i = j - 1;
                            break;
                        }

                        if (isLoopEnd2)
                        {
                            for (var k = i; k < j + 1; k++)
                            {
                                (Steps[k][colIndex] as LoopComboxParam).IsLoopStep = true;
                                (Steps[k][colIndex] as LoopComboxParam).IsValidLoop = true;
                            }
                            i = j;
                            break;
                        }

                        if (j == Steps.Count - 1)
                        {
                            loopCell.IsLoopStep = true;
                            loopCell.IsValidLoop = true;
                            i = j;
                            break;
                        }

                    }
                    continue;
                }

                loopCell.IsLoopStep = false;
                loopCell.IsValidLoop = false;
            }

        }

        private bool LoadHeader(XmlNode nodeHeader)
        {
            if (nodeHeader == null)
                return false;

            if (nodeHeader.Attributes["CreatedBy"] != null)
                Creator = nodeHeader.Attributes["CreatedBy"].Value;
            if (nodeHeader.Attributes["CreationTime"] != null)
                CreateTime = DateTime.Parse(nodeHeader.Attributes["CreationTime"].Value);
            if (nodeHeader.Attributes["LastRevisedBy"] != null)
                Revisor = nodeHeader.Attributes["LastRevisedBy"].Value;
            if (nodeHeader.Attributes["LastRevisionTime"] != null)
                ReviseTime = DateTime.Parse(nodeHeader.Attributes["LastRevisionTime"].Value);
            if (nodeHeader.Attributes["Description"] != null)
                Description = nodeHeader.Attributes["Description"].Value;
            var chamberType = string.Empty;
            if (nodeHeader.Attributes["RecipeChamberType"] != null)
                chamberType = nodeHeader.Attributes["RecipeChamberType"].Value;

            if (!string.IsNullOrEmpty(chamberType) && chamberType != RecipeChamberType)
            {
                LOG.Write($"{chamberType} is not accordance with {RecipeChamberType}");
                return false;
            }

            var version = string.Empty;
            if (nodeHeader.Attributes["RecipeVersion"] != null)
                version = nodeHeader.Attributes["RecipeVersion"].Value;
            if (!string.IsNullOrEmpty(version) && version != RecipeVersion)
            {
                LOG.Write($"{version} is not accordance with {RecipeVersion}");
                return false;
            }

            return true;
        }

        /// <summary>
        /// 加载Recipe(从本地，PM未进行时)。
        /// </summary>
        /// <param name="columns"></param>
        /// <param name="steps"></param>
        /// <param name="role">角色</param>
        /// <param name="processType"></param>
        /// <param name="dispatcher"></param>
        private async Task LoadSteps(List<EditorDataGridTemplateColumnBase> columns, XmlNodeList steps, Role role,
            string processType = "", Dispatcher dispatcher = null)
        {
            //取消高亮提示
            HasHighLight = false;
            // 当前步骤的前序步骤，用于创建参数链表。
            RecipeStep frontStep = null;

            Steps.Clear();
            PopSettingSteps.Clear();
            StepTolerances.Clear();

            var stepId = 1;
            RecipeTotalTime = 0;

            using (Steps.BeginLoading())
            {
                foreach (XmlNode nodeStep in steps)
                {
                    var step = CreateStep(columns, nodeStep, frontStep, stepId);
                    step.Save();
                    frontStep = step;
                    RecipeStep.SetPermission(step, role);

                    var t = dispatcher?.BeginInvoke(DispatcherPriority.Background,
                        (Action)(() => { Steps.Add(step); }));
                    if (t != null)
                        await t;
                    else
                    {
                        // 确保在UI线程上执行
                        Application.Current?.Dispatcher.Invoke(() => { Steps.Add(step); });
                    }


                    stepId++;
                }

                Validate();
            }

            Steps.EndLoading();
        }

        /// <summary>
        /// 加载Recipe(从本地和内存中加载，PM在工艺中)。
        /// </summary>
        /// <param name="columns"></param>
        /// <param name="stepinfos"></param>
        /// <param name="role"></param>
        /// <param name="processType"></param>
        /// <param name="dispatcher"></param>
        /// <returns></returns>
        private async Task LoadSteps(List<EditorDataGridTemplateColumnBase> columns, XmlNodeList notesteps, List<RecipeStepInfo> stepinfos, Role role,
            string processType = "", Dispatcher dispatcher = null)
        {
            //取消高亮提示
            HasHighLight = false;

            // 当前步骤的前序步骤，用于创建参数链表。
            RecipeStep frontStep = null;

            Steps.Clear();
            PopSettingSteps.Clear();
            StepTolerances.Clear();

            var stepId = 1;
            RecipeTotalTime = 0;

            using (Steps.BeginLoading())
            {
                foreach (XmlNode nodeStep in notesteps)
                {
                    if (nodeStep.Attributes["StepUid"] == null)
                    {
                        continue;
                    }
                    RecipeStepInfo stepinfo = stepinfos.Find(i => i.StepUid == nodeStep.Attributes["StepUid"].Value);
                    var step = Create_CoverStep(columns, nodeStep, stepinfo, frontStep, stepId);
                    step.Save();
                    frontStep = step;
                    RecipeStep.SetPermission(step, role);

                    var t = dispatcher?.BeginInvoke(DispatcherPriority.Background,
                        (Action)(() => { Steps.Add(step); }));
                    if (t != null)
                        await t;
                    else
                    {
                        // 确保在UI线程上执行
                        Application.Current?.Dispatcher.Invoke(() => { Steps.Add(step); });
                    }


                    stepId++;
                }

                Validate();
            }

            Steps.EndLoading();
        }

        private void LoadConfigs(RecipeStep configDefine, XmlNode configNode)
        {
            ConfigItems.Clear();

            foreach (var param in configDefine)
            {
                if (param is DoubleParam param1)
                {
                    var config = new DoubleParam()
                    {
                        ControlName = param.ControlName,
                        Value = param1.Value,
                        DisplayName = param.DisplayName,
                        Minimun = param1.Minimun,
                        Maximun = param1.Maximun,
                        Resolution = param1.Resolution
                    };

                    if (configNode?.Attributes?[param1.ControlName] != null)
                        config.Value = double.Parse(configNode.Attributes[param1.ControlName].Value);

                    ConfigItems.Add(config);
                }

                if (param is StringParam paramString)
                {
                    var config = new StringParam()
                    {
                        ControlName = param.ControlName,
                        Value = paramString.Value,
                        DisplayName = param.DisplayName,
                    };

                    if (configNode?.Attributes?[paramString.ControlName] != null)
                        config.Value = configNode.Attributes[paramString.ControlName].Value;

                    ConfigItems.Add(config);
                }

            }
        }

        /// <summary>
        /// 计算指定配方步骤的气体流量。
        /// </summary>
        /// <param name="step">指定的配方步骤。</param>
        internal void CalculateGasFlow(RecipeStep step)
        {
            _gasFlowCalculator?.Calculate(step, Module);
        }

        public bool ChkChanged(RecipeStepCollection steps)
        {
            return steps.Any() && !steps.IsSaved;
        }

        public bool ChkChanged(Dictionary<string, RecipeStepCollection> popSteps)
        {
            if(popSteps.Count == 0) 
                return false;

            return popSteps.Values.Any(ChkChanged);
        }

        public void BuildFormat(string chamberId, string module, Role role)
        {
            Columns = _formatBuilder.Build(chamberId, module, true, RecipeType);
            UpdatePermission(role);
        }
        
        public void Clear()
        {
            Steps.Clear();
            PopSettingSteps.Clear();
            StepTolerances.Clear();
            ConfigItems.Clear();
            RecipeChamberType = "";
            RecipeVersion = "";
            IsSavedDesc = true;
            Module = "";
        }

        /// <summary>
        /// 将当前配方标记为已保存状态。
        /// </summary>
        public void MarkAsSaved()
        {
            Steps.Save();
            StepTolerances.Save();
            PopSettingSteps.Values.ToList().ForEach(x => x.Save());
            ConfigItems.Save();

            IsSavedDesc = true;
        }

        /// <summary>
        /// 从指定的配方文件加载配方。
        /// </summary>
        /// <param name="prefixPath"></param>
        /// <param name="recipeName"></param>
        /// <param name="recipeContent"></param>
        /// <param name="module"></param>
        /// <param name="role"></param>
        /// <param name="dispatcher"></param>
        public async Task LoadFile(string prefixPath, string recipeName, string recipeContent,
            string module, Role role, Dispatcher dispatcher = null)
        {
            IsCompatibleWithCurrentFormat = false;

            RecipeChamberType = _formatBuilder.RecipeChamberType;
            RecipeVersion = _formatBuilder.RecipeVersion;

            Name = recipeName;
            PrefixPath = prefixPath;
            Module = module;
            try
            {
                _doc = new XmlDocument();
                _doc.LoadXml(recipeContent);

                if (!LoadHeader(_doc.SelectSingleNode("Aitex/TableRecipeData")))
                    return;

                var nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Module[@Name='{module}']/Step");
                if (nodeSteps == null)
                    nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Step");

                var processType = "";

                if (PrefixPath.Contains("Process"))
                {
                    processType = "Process";
                }

                await LoadSteps(Columns, nodeSteps, role, processType, dispatcher);
                ValidLoopData();

                var nodeConfig =
                    _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']/Config");
                if (nodeSteps == null)
                    nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Config");

                LoadConfigs(_formatBuilder.Configs, nodeConfig);

                IsCompatibleWithCurrentFormat = true;
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }
        }

        /// <summary>
        /// 从配方内存区加载配方。
        /// </summary>
        /// <param name="prefixPath"></param>
        /// <param name="recipeName"></param>
        /// <param name="recipeContent"></param>
        /// <param name="module"></param>
        /// <param name="role"></param>
        /// <param name="dispatcher"></param>
        public async Task LoadFile(string prefixPath, string recipeName, string recipeContent, List<RecipeStepInfo> steps,
            string module, Role role, Dispatcher dispatcher = null)
        {
            IsCompatibleWithCurrentFormat = false;

            RecipeChamberType = _formatBuilder.RecipeChamberType;
            RecipeVersion = _formatBuilder.RecipeVersion;

            Name = recipeName;
            PrefixPath = prefixPath;
            Module = module;
            try
            {
                _doc = new XmlDocument();
                _doc.LoadXml(recipeContent);

                if (!LoadHeader(_doc.SelectSingleNode("Aitex/TableRecipeData")))
                    return;

                var nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Module[@Name='{module}']/Step");
                if (nodeSteps == null)
                    nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Step");

                var processType = "";

                if (PrefixPath.Contains("Process"))
                {
                    processType = "Process";
                }

                //先加载本地的文件，再将内存中的数据覆盖
                await LoadSteps(Columns, nodeSteps, steps, role, processType, dispatcher);
                ValidLoopData();

                var nodeConfig =
                    _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']/Config");
                if (steps == null)
                    nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Config");

                LoadConfigs(_formatBuilder.Configs, nodeConfig);

                IsCompatibleWithCurrentFormat = true;
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }
        }
        /// <summary>
        /// 从Recipe History加载配方。
        /// </summary>
        /// <param name="recipeHistory"></param>
        /// <param name="module"></param>
        /// <param name="role"></param>
        /// <param name="dispatcher"></param>
        public async Task LoadFile(RecipeHistory recipeHistory,
            string module, Role role, Dispatcher dispatcher = null)
        {
            IsCompatibleWithCurrentFormat = false;

            RecipeChamberType = _formatBuilder.RecipeChamberType;
            RecipeVersion = _formatBuilder.RecipeVersion;
            Name = recipeHistory.RecipeName;
            Module = module;
            try
            {
                _doc = new XmlDocument();
                _doc.LoadXml(recipeHistory.RecipeFromXML);

                if (!LoadHeader(_doc.SelectSingleNode("Aitex/TableRecipeData")))
                    return;

                var nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Module[@Name='{module}']/Step");
                if (nodeSteps == null)
                    nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Step");

                await LoadSteps(Columns, nodeSteps, role, recipeHistory.ProcessType, dispatcher);
                ValidLoopData();

                var nodeConfig =
                    _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']/Config");
                if (nodeSteps == null)
                    nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Config");

                LoadConfigs(_formatBuilder.Configs, nodeConfig);

                IsCompatibleWithCurrentFormat = true;
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }
        }

        public async Task ChangeChamber(List<EditorDataGridTemplateColumnBase> columnDefine,
            RecipeStep configDefine, string module, Role role, Dispatcher dispatcher)
        {
            Module = module;

            try
            {
                var nodeSteps = _doc.SelectNodes($"Aitex/TableRecipeData/Module[@Name='{module}']/Step") ??
                                _doc.SelectNodes($"Aitex/TableRecipeData/Step");

                await LoadSteps(columnDefine, nodeSteps, role, dispatcher: dispatcher);
                ValidLoopData();

                var nodeConfig =
                    _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']/Config");
                if (nodeSteps == null)
                    nodeConfig = _doc.SelectSingleNode($"Aitex/TableRecipeData/Config");

                LoadConfigs(configDefine, nodeConfig);
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
            }
        }

        public bool SaveTo(string[] moduleList, out string reason)
        {
            if (!GetXmlString(out var xml, out reason))
                return false;

            var nameNodeModule = $"Aitex/TableRecipeData/Module[@Name='{Module}']";
            var nodeModule = _doc.SelectSingleNode(nameNodeModule);
            if (nodeModule == null)
            {
                reason = "recipe not find modules," + Name;
                LOG.Write(reason);
                return false;
            }

            var nodeData = nodeModule.ParentNode;
            if (nodeData == null)
            {
                reason = $"parent node of {nameNodeModule} is null.";
                return false;
            }

            foreach (var module in moduleList)
            {
                if (module == Module)
                {
                    continue;
                }

                var child = _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{module}']");
                if (child != null)
                    nodeData.RemoveChild(child);

                if (nodeModule.Clone() is not XmlElement node)
                {
                    reason = $"unable to clone node {nodeModule.Name}";
                    return false;
                }

                node.SetAttribute("Name", module);
                nodeData.AppendChild(node);
            }

            return true;
        }

        /// <summary>
        /// 创建一个Recipe空步骤。
        /// </summary>
        /// <param name="columns">Recipe列集合</param>
        /// <param name="stepNode"></param>
        /// <param name="frontStep">前序Recipe步骤</param>
        /// <param name="stepId"></param>
        /// <returns></returns>
        public RecipeStep CreateStep(List<EditorDataGridTemplateColumnBase> columns,
            XmlNode stepNode = null, RecipeStep frontStep = null, int? stepId = null)
        {
            var newStep = new RecipeStep(frontStep);

            foreach (var col in columns)
            {
                var value = string.Empty;
                if (!(col is ExpanderColumn) && stepNode != null && !(col is StepColumn) && !(col is PopSettingColumn))
                {
                    if (string.IsNullOrEmpty(col.ModuleName) && stepNode.Attributes[col.ControlName] != null)
                    {
                        value = stepNode.Attributes[col.ControlName].Value;
                    }
                    else
                    {
                        // 因为6号机使用C3H8，但升级到8号机程序后，RecipeFormat.xml使用C2H4，导致8号机用C2H4.SetValue在6号机Recipe中的C3H8.SetValue项时找不到，
                        // 以下代码交换尝试C2H4和C3H8名称搜索Recipe中的项目，解决兼容性问题。
                        // TODO C2H4或者C3H8在系统内部应该统一使用CxHy，RT中不再区分具体名称
                        if (col.ControlName.Contains("C2H4") && stepNode.Attributes[col.ControlName.Replace("C2H4", "C3H8")].Value != null)
                        {
                            var tmpControlName = col.ControlName.Replace("C2H4", "C3H8");
                            value = stepNode.Attributes[tmpControlName].Value;
                        }
                        else if (col.ControlName.Contains("C3H8") && stepNode.Attributes[col.ControlName.Replace("C3H8", "C2H4")].Value != null)
                        {
                            var tmpControlName = col.ControlName.Replace("C3H8", "C2H4");
                            value = stepNode.Attributes[tmpControlName].Value;
                        }
                        else
                        {
                            if (stepNode.Attributes[col.ControlName] != null &&
                                stepNode.SelectSingleNode(col.ModuleName) != null &&
                                stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName] != null)
                                value = stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName].Value;
                        }
                    }
                }

                Param param = null;

                switch (col)
                {
                    case StepColumn _:
                        param = stepId.HasValue ? new StepParam(stepId.Value) : new StepParam();
                        param.EnableTolerance = col.EnableTolerance;
                        break;

                    case RatioColumn colRatio:
                        if (colRatio.MaxSets == 3)
                        {
                            param = new Sets3RatioParam(col.Default)
                            {
                                IsEnabled = col.IsEnable,
                                EnableTolerance = col.EnableTolerance,
                            };

                            // 如果XML中保存的Value格式正确，则将设置为保存的Value。
                            if (Sets3RatioParam.ValidateRatioString(value))
                            {
                                ((Sets3RatioParam)param).Value = value;
                            }
                        }

                        break;

                    case TextBoxColumn _:
                        param = new StringParam(string.IsNullOrEmpty(value)
                            ? (string.IsNullOrEmpty(col.Default) ? "" : col.Default)
                            : value)
                        {
                            IsEnabled = col.IsEnable,
                            EnableTolerance = col.EnableTolerance,
                        };
                        break;

                    case NumColumn colNum:
                        param = new IntParam(
                            (int)GlobalDefs.TryParseToDouble(value, GlobalDefs.TryParseToDouble(colNum.Default, colNum.Minimun)),
                            (int)colNum.Minimun, (int)colNum.Maximun)
                        {
                            IsEnabled = colNum.IsEnable,
                            EnableTolerance = colNum.EnableTolerance,
                        };
                        break;

                    case DoubleColumn colDbl:
                        param = new DoubleParam(
                            GlobalDefs.TryParseToDouble(value, GlobalDefs.TryParseToDouble(colDbl.Default, colDbl.Minimun)),
                            colDbl.Minimun, colDbl.Maximun)
                        {
                            IsEnabled = colDbl.IsEnable,
                            Resolution = colDbl.Resolution,
                            EnableTolerance = colDbl.EnableTolerance,
                        };
                        break;

                    case FlowModeColumn colFlowMode:
                        {
                            param = new FlowModeParam(colFlowMode.Options, col.ControlName, value, colFlowMode.Default)
                            {
                                IsEditable = colFlowMode.IsEditable,
                                EnableTolerance = colFlowMode.EnableTolerance,
                            };
                            break;
                        }

                    case ComboxColumn colCbx:
                        {
                            param = new ComboxParam(colCbx.Options, col.ControlName, value, colCbx.Default)
                            {
                                IsEditable = colCbx.IsEditable,
                                EnableTolerance = colCbx.EnableTolerance,
                            };
                            break;
                        }

                    case LoopComboxColumn colLoopCbx:
                        {
                            var selIndex = 0;
                            param = new LoopComboxParam(string.IsNullOrEmpty(value)
                                ? colLoopCbx.Options[selIndex].DisplayName
                                : value)
                            {
                                Options = colLoopCbx.Options,
                                IsEditable = colLoopCbx.IsEditable,
                                IsLoopStep = false,
                                EnableTolerance = colLoopCbx.EnableTolerance,
                            };
                            break;
                        }

                    case ExpanderColumn _:
                        param = new ExpanderParam();
                        break;

                    case PopSettingColumn _:
                        param = new PopSettingParam()
                        {
                            UnitName = col.UnitName,
                            EnableTolerance = col.EnableTolerance,
                        };
                        break;
                }

                if (param == null)
                    break;

                param.ColumnPermChangedCallback = col.PermissionChangedCallback;

                if (col is LoopComboxColumn)
                {
                    param.ColumnPermChangedCallback = LoopCellFeedback;
                }

                param.DisplayName = col.DisplayName;
                param.ControlName = col.ControlName;
                param.ColumnOwner = col;
                newStep.Add(param);
            }

            // 创建Step时默认有权限
            newStep.Permission = MenuPermissionEnum.MP_READ_WRITE;

            foreach (var param in newStep)
            {
                // 根据列权限设置参数访问权限
                param.ColumnPermChangedCallback?.Invoke(param);
            }

            return newStep;
        }

        /// <summary>
        /// 覆盖原有Step
        /// </summary>
        /// <param name="columns">Recipe列集合</param>
        /// <param name="stepInfo"></param>
        /// <param name="frontStep">前序Recipe步骤</param>
        /// <param name="stepId"></param>
        /// <returns></returns>
        public RecipeStep Create_CoverStep(List<EditorDataGridTemplateColumnBase> columns,
            XmlNode stepNode = null, RecipeStepInfo stepInfo = null, RecipeStep frontStep = null, int? stepId = null)
        {
            var newStep = new RecipeStep(frontStep);

            foreach (var col in columns)
            {
                var value = string.Empty;
                bool ishighlight = stepInfo.HighLightList?.Contains(col.ControlName) ?? false;
                if (ishighlight)
                    HasHighLight = true;
                if (!(col is ExpanderColumn) && stepNode != null && !(col is StepColumn) && !(col is PopSettingColumn))
                {
                    if (string.IsNullOrEmpty(col.ModuleName) && stepNode.Attributes[col.ControlName] != null)
                    {
                        if (stepInfo != null && stepInfo.RecipeCommands.ContainsKey(col.ControlName))
                            value = stepInfo.RecipeCommands[col.ControlName];
                        else
                            value = stepNode.Attributes[col.ControlName].Value;
                    }
                    else
                    {
                        // 因为6号机使用C3H8，但升级到8号机程序后，RecipeFormat.xml使用C2H4，导致8号机用C2H4.SetValue在6号机Recipe中的C3H8.SetValue项时找不到，
                        // 以下代码交换尝试C2H4和C3H8名称搜索Recipe中的项目，解决兼容性问题。
                        // TODO C2H4或者C3H8在系统内部应该统一使用CxHy，RT中不再区分具体名称
                        if (col.ControlName.Contains("C2H4") && stepNode.Attributes[col.ControlName.Replace("C2H4", "C3H8")].Value != null)
                        {
                            var tmpControlName = col.ControlName.Replace("C2H4", "C3H8");
                            if (stepInfo != null && stepInfo.RecipeCommands.ContainsKey(col.ControlName))
                                value = stepInfo.RecipeCommands[tmpControlName];
                            else
                                value = stepNode.Attributes[tmpControlName].Value;

                        }
                        else if (col.ControlName.Contains("C3H8") && stepNode.Attributes[col.ControlName.Replace("C3H8", "C2H4")].Value != null)
                        {
                            var tmpControlName = col.ControlName.Replace("C3H8", "C2H4");
                            if (stepInfo != null && stepInfo.RecipeCommands.ContainsKey(col.ControlName))
                                value = stepInfo.RecipeCommands[tmpControlName];
                            else
                                value = stepNode.Attributes[tmpControlName].Value;
                        }
                        else
                        {
                            if (stepNode.Attributes[col.ControlName] != null &&
                                stepNode.SelectSingleNode(col.ModuleName) != null &&
                                stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName] != null)
                                value = stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName].Value;
                        }
                    }
                }

                Param param = null;

                switch (col)
                {
                    case StepColumn _:
                        param = stepId.HasValue ? new StepParam(stepId.Value) : new StepParam();
                        param.EnableTolerance = col.EnableTolerance;
                        break;

                    case RatioColumn colRatio:
                        if (colRatio.MaxSets == 3)
                        {
                            param = new Sets3RatioParam(col.Default)
                            {
                                IsEnabled = col.IsEnable,
                                EnableTolerance = col.EnableTolerance,
                            };

                            // 如果XML中保存的Value格式正确，则将设置为保存的Value。
                            if (Sets3RatioParam.ValidateRatioString(value))
                            {
                                ((Sets3RatioParam)param).Value = value;
                            }
                        }

                        break;

                    case TextBoxColumn _:
                        param = new StringParam(string.IsNullOrEmpty(value)
                            ? (string.IsNullOrEmpty(col.Default) ? "" : col.Default)
                            : value)
                        {
                            IsEnabled = col.IsEnable,
                            EnableTolerance = col.EnableTolerance,
                        };
                        break;

                    case NumColumn colNum:
                        param = new IntParam(
                            (int)GlobalDefs.TryParseToDouble(value, GlobalDefs.TryParseToDouble(colNum.Default, colNum.Minimun)),
                            (int)colNum.Minimun, (int)colNum.Maximun)
                        {
                            IsEnabled = colNum.IsEnable,
                            EnableTolerance = colNum.EnableTolerance,
                        };
                        break;

                    case DoubleColumn colDbl:
                        param = new DoubleParam(
                            GlobalDefs.TryParseToDouble(value, GlobalDefs.TryParseToDouble(colDbl.Default, colDbl.Minimun)),
                            colDbl.Minimun, colDbl.Maximun)
                        {
                            IsEnabled = colDbl.IsEnable,
                            Resolution = colDbl.Resolution,
                            EnableTolerance = colDbl.EnableTolerance,
                        };
                        break;

                    case FlowModeColumn colFlowMode:
                        {
                            param = new FlowModeParam(colFlowMode.Options, col.ControlName, value, colFlowMode.Default)
                            {
                                IsEditable = colFlowMode.IsEditable,
                                EnableTolerance = colFlowMode.EnableTolerance,
                            };
                            break;
                        }

                    case ComboxColumn colCbx:
                        {
                            param = new ComboxParam(colCbx.Options, col.ControlName, value, colCbx.Default)
                            {
                                IsEditable = colCbx.IsEditable,
                                EnableTolerance = colCbx.EnableTolerance,
                            };
                            break;
                        }

                    case LoopComboxColumn colLoopCbx:
                        {
                            var selIndex = 0;
                            param = new LoopComboxParam(string.IsNullOrEmpty(value)
                                ? colLoopCbx.Options[selIndex].DisplayName
                                : value)
                            {
                                Options = colLoopCbx.Options,
                                IsEditable = colLoopCbx.IsEditable,
                                IsLoopStep = false,
                                EnableTolerance = colLoopCbx.EnableTolerance,
                            };
                            break;
                        }

                    case ExpanderColumn _:
                        param = new ExpanderParam();
                        break;

                    case PopSettingColumn _:
                        param = new PopSettingParam()
                        {
                            UnitName = col.UnitName,
                            EnableTolerance = col.EnableTolerance,
                        };
                        break;
                }

                if (param == null)
                    break;

                param.ColumnPermChangedCallback = col.PermissionChangedCallback;

                if (col is LoopComboxColumn)
                {
                    param.ColumnPermChangedCallback = LoopCellFeedback;
                }

                if (col.StringCellTemplate == "CellTemplateDisplay")
                {
                    newStep.ProcessStatusChangedCallback += param.StepDoneCallback;
                }

                param.DisplayName = col.DisplayName;
                param.ControlName = col.ControlName;
                param.ColumnOwner = col;

                if (ishighlight)
                {
                    param.Highlight();
                }
                newStep.Add(param);
            }

            // 创建Step时默认有权限
            newStep.Permission = MenuPermissionEnum.MP_READ_WRITE;

            foreach (var param in newStep)
            {
                // 根据列权限设置参数访问权限
                param.ColumnPermChangedCallback?.Invoke(param);
            }

            return newStep;
        }

        public bool CreateStepTolerance(ObservableCollection<EditorDataGridTemplateColumnBase> columns,
            Dictionary<string, RecipeStep> popSettingColumns, XmlNode stepNode, out RecipeStep step, out RecipeStep warning, out RecipeStep alarm,
            out Dictionary<string, RecipeStep> popSettingStep)
        {
            step = new RecipeStep(null);
            warning = new RecipeStep(null);
            alarm = new RecipeStep(null);
            popSettingStep = new Dictionary<string, RecipeStep>();

            foreach (var col in columns)
            {
                var warningValue = string.Empty;
                var alarmValue = string.Empty;
                var stepValue = string.Empty;
                var popValues = new Dictionary<string, Dictionary<string, string>>();

                if (!(col is ExpanderColumn) && stepNode != null && !(col is StepColumn) && !(col is PopSettingColumn))
                {
                    var warningNode = stepNode.SelectSingleNode("Warning");
                    if (warningNode != null && warningNode.Attributes[col.ControlName] != null)
                    {
                        warningValue = warningNode.Attributes[col.ControlName].Value;
                    }
                    var alarmNode = stepNode.SelectSingleNode("Alarm");
                    if (alarmNode != null && alarmNode.Attributes[col.ControlName] != null)
                    {
                        alarmValue = alarmNode.Attributes[col.ControlName].Value;
                    }

                    if (string.IsNullOrEmpty(col.ModuleName) && stepNode.Attributes[col.ControlName] != null)
                    {
                        stepValue = stepNode.Attributes[col.ControlName].Value;
                    }
                    else
                    {
                        if (stepNode.Attributes[col.ControlName] != null && stepNode.SelectSingleNode(col.ModuleName) != null && stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName] != null)
                            stepValue = stepNode.SelectSingleNode(col.ModuleName).Attributes[col.ControlName].Value;
                    }
                }

                if (col is PopSettingColumn)
                {
                    foreach (var key in popSettingColumns.Keys)
                    {
                        var popNode = stepNode.SelectSingleNode(key);
                        if (popNode != null)
                        {
                            var values = new Dictionary<string, string>();
                            foreach (var item in popSettingColumns[key])
                            {
                                if (popNode.Attributes[item.ControlName] != null)
                                    values.Add(item.ControlName, popNode.Attributes[item.ControlName].Value);
                            };

                            popValues.Add(key, values);
                        }
                    }
                }


                Param stepCell = new DoubleParam(GlobalDefs.TryParseToDouble(stepValue))
                {
                    ControlName = col.ControlName,
                    DisplayName = col.DisplayName,
                    UnitName = col.UnitName,
                    IsEnabled = false,
                    StepCheckVisibility = Visibility.Hidden,
                };

                stepCell.Parent = step;
                step.Add(stepCell);

                if (col is PopSettingColumn)
                {
                    for (var i = 0; i < popSettingColumns[col.ControlName].Count; i++)
                    {
                        var name = popSettingColumns[col.ControlName][i].ControlName;
                        var value = popValues[col.ControlName].Where(x => x.Key == name).Count() > 0 ?
                            popValues[col.ControlName].First(x => x.Key == name).Value : "";
                        if (popSettingColumns[col.ControlName][i] is DoubleParam)
                        {
                            stepCell = new DoubleParam(GlobalDefs.TryParseToDouble(value))
                            {
                                ControlName = col.ControlName,
                                DisplayName = popSettingColumns[col.ControlName][i].DisplayName,
                                IsEnabled = false,
                                StepCheckVisibility = Visibility.Hidden,
                            };
                        }
                        if (popSettingColumns[col.ControlName][i] is StringParam)
                        {
                            stepCell = new StringParam(value)
                            {
                                ControlName = col.ControlName,
                                DisplayName = popSettingColumns[col.ControlName][i].DisplayName,
                                IsEnabled = false,
                                StepCheckVisibility = Visibility.Hidden,
                            };
                        }

                        if (popSettingColumns[col.ControlName][i] is ComboxParam)
                        {
                            var options = ((ComboxParam)popSettingColumns[col.ControlName][i]).Options;
                            stepCell = new ComboxParam(options, col.ControlName, value, "")
                            {
                                DisplayName = popSettingColumns[col.ControlName][i].DisplayName,
                                IsEditable = !col.IsReadOnly,
                                EnableTolerance = col.EnableTolerance,
                            };
                        }

                        if (!popSettingStep.ContainsKey(col.ControlName))
                        {
                            popSettingStep.Add(col.ControlName, new RecipeStep(null));
                        }
                        stepCell.Parent = popSettingStep[col.ControlName];
                        popSettingStep[col.ControlName].Add(stepCell);
                    }
                }
                Param warningCell = new DoubleParam(col.EnableTolerance ? (GlobalDefs.TryParseToDouble(warningValue)) : double.NaN)
                {
                    ControlName = col.ControlName,
                    DisplayName = col.DisplayName,
                    UnitName = col.UnitName,
                    IsEnabled = col.EnableTolerance && stepValue != "0",
                    StepCheckVisibility = Visibility.Collapsed,
                };


                warningCell.ColumnPermChangedCallback = col.PermissionChangedCallback;
                warningCell.Parent = warning;
                warning.Add(warningCell);

                //Param alarmCell = new DoubleParam(col.EnableTolerance ? (string.IsNullOrEmpty(alarmValue) ? "0" : alarmValue) : "*")
                Param alarmCell = new DoubleParam(col.EnableTolerance ? (GlobalDefs.TryParseToDouble(alarmValue)) : double.NaN)
                {
                    DisplayName = col.DisplayName,
                    UnitName = col.UnitName,
                    IsEnabled = col.EnableTolerance && stepValue != "0",
                    StepCheckVisibility = Visibility.Collapsed,
                };


                alarmCell.ColumnPermChangedCallback = col.PermissionChangedCallback;
                alarmCell.Parent = alarm;
                
                alarm.Add(alarmCell);
            }

            return true;
        }

        public void ValidLoopData()
        {
            if (Steps.Count == 0)
                return;

            for (var j = 0; j < Steps[0].Count; j++)
            {
                if (Steps[0][j] is LoopComboxParam)
                {
                    LoopCellFeedback(Steps[0][j]);
                }
            }
        }

        /// <summary>
        /// 校验当前Recipe的参数值。
        /// </summary>
        /// <remarks>
        /// 校验结果请参考
        /// <see cref="ValidationErrorSummary"/>属性、
        /// <see cref="ValidationErrorCount"/>属性、
        /// <see cref="ValidationErrorInfo"/>属性。
        /// </remarks>
        /// <returns></returns>
        public bool Validate()
        {
            RecipeTotalTime = (int)Steps.GetTotalTime();

            ValidationErrorSummary = string.Empty;

            /*var lstError = new List<string>();*/
            ValidationErrorInfo.Clear();

            Steps.Validate();

            // 校验所有步骤
            foreach (var step in Steps)
            {
                var invalidParams = step
                    .Where(x => x.IsValid == false).ToList();

                ValidationErrorInfo.AddRange(invalidParams
                    .Select(x => new RecipeStepValidationInfo(
                        step,
                        x,
                        x.ValidationError)));
            }

            // 校验通过
            if (ValidationErrorInfo.Count <= 0)
            {
                ValidationErrorCount = 0;
            }
            else
            {
                // 校验失败，生成错误信息摘要
                ValidationErrorCount = ValidationErrorInfo.Count;

                var errCnt = 0;
                var strInfo = new StringBuilder();
                foreach (var t in ValidationErrorInfo)
                {
                    strInfo.AppendLine(t.ToString());
                    errCnt++;

                    if (errCnt > 10)
                        break;
                }

                if (errCnt < ValidationErrorInfo.Count)
                    strInfo.AppendLine("\r\n<please check for more errors...>");

                ValidationErrorSummary = strInfo.ToString();
            }

            OnValidated?.Invoke(this, EventArgs.Empty);

            return ValidationErrorCount <= 0;
        }
        
        public RecipeStep CloneStep(List<EditorDataGridTemplateColumnBase> columns, RecipeStep sourceParams)
        {
            var targetParams = CreateStep(columns);

            for (var i = 0; i < sourceParams.Count; i++)
            {
                var srcParam = sourceParams[i];

                // StepUid列不能复制，插入、粘贴的Step生成新的Uid
                if (srcParam is { ControlName: RecipeFormatBuilder.SPEC_COL_STEPUID } 
                    or {ControlName: RecipeFormatBuilder.SPEC_COL_STEPNO})
                    continue;
                
               
                if (sourceParams[i] is StringParam)
                {
                    ((StringParam)targetParams[i]).Value = ((StringParam)sourceParams[i]).Value;
                }
                else if (sourceParams[i] is Sets3RatioParam)
                {
                    ((Sets3RatioParam)targetParams[i]).Value = ((Sets3RatioParam)sourceParams[i]).Value;
                }
                else if (sourceParams[i] is IntParam)
                {
                    ((IntParam)targetParams[i]).Value = ((IntParam)sourceParams[i]).Value;
                }
                else if (sourceParams[i] is ComboxParam)
                {
                    ((ComboxParam)targetParams[i]).Value = ((ComboxParam)sourceParams[i]).Value;
                }
                else if (sourceParams[i] is LoopComboxParam)
                {
                    ((LoopComboxParam)targetParams[i]).Value = ((LoopComboxParam)sourceParams[i]).Value;
                }
                else if (sourceParams[i] is BoolParam)
                {
                    ((BoolParam)targetParams[i]).Value = ((BoolParam)sourceParams[i]).Value;
                }
                else if (sourceParams[i] is DoubleParam)
                {
                    ((DoubleParam)targetParams[i]).Value = ((DoubleParam)sourceParams[i]).Value;
                }
            }

            return targetParams;
        }

        /// <summary>
        /// 生成RecipeData的Xml格式文本，用于保存。
        /// </summary>
        /// <param name="xml"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public bool GetXmlString(out string xml, out string reason)
        {
            xml = "";
            reason = "";

            try
            {
                var nodeData = _doc.SelectSingleNode($"Aitex/TableRecipeData") as XmlElement;

                nodeData.SetAttribute("CreatedBy", Creator);
                nodeData.SetAttribute("CreationTime", CreateTime.ToString("yyyy-MM-dd HH:mm:ss"));
                nodeData.SetAttribute("LastRevisedBy", Revisor);
                nodeData.SetAttribute("LastRevisionTime", ReviseTime.ToString("yyyy-MM-dd HH:mm:ss"));
                nodeData.SetAttribute("Description", Description);
                nodeData.SetAttribute("RecipeChamberType", RecipeChamberType);
                nodeData.SetAttribute("RecipeVersion", RecipeVersion);

                var nodeModule = _doc.SelectSingleNode($"Aitex/TableRecipeData/Module[@Name='{Module}']");
                if (nodeModule == null)
                {
                    nodeModule = _doc.CreateElement("Module");
                    nodeData.AppendChild(nodeModule);
                }
                nodeModule.RemoveAll();
                (nodeModule as XmlElement).SetAttribute("Name", Module);
                var i = 0;
                foreach (var parameters in Steps)
                {
                    var nodeWarning = _doc.CreateElement("Warning");
                    var nodeAlarm = _doc.CreateElement("Alarm");
                    var nodePop = new Dictionary<string, XmlElement>();
                    foreach (var key in PopEnable.Keys)
                    {
                        nodePop.Add(key, _doc.CreateElement(key));
                    }

                    var nodeStep = _doc.CreateElement("Step");
                    foreach (var parameter in parameters)
                    {
                        if (ToleranceEnable)
                        {
                            if (parameter.EnableTolerance)
                            {
                                nodeWarning.SetAttribute(parameter.ControlName, "1");
                                nodeAlarm.SetAttribute(parameter.ControlName, "7");
                            }
                        }

                        if (parameter is IntParam intParam)
                        {
                            //去除Int型数据前面多余的"0"
                            if (int.TryParse(intParam.Value.ToString(), out var result))
                            {
                                if (result != 0)
                                {
                                    intParam.Value = int.Parse(intParam.Value.ToString().TrimStart('0'));
                                }
                            }

                            nodeStep.SetAttribute(intParam.ControlName, intParam.Value.ToString());
                        }
                        else if (parameter is DoubleParam doubleParam)
                        {
                            //去除Double型数据前面多余的"0"
                            //if (int.TryParse(((DoubleParam)parameter).Value, out var result))
                            //{
                            //    if (result != 0)
                            //    {
                            //        ((DoubleParam)parameter).Value = ((DoubleParam)parameter).Value.TrimStart('0');
                            //    }
                            //}

                            nodeStep.SetAttribute(doubleParam.ControlName, doubleParam.Value.ToString());
                        }
                        else if (parameter is Sets3RatioParam param)
                            nodeStep.SetAttribute(param.ControlName, param.Value);
                        else if (parameter is StringParam stringParam)
                            nodeStep.SetAttribute(stringParam.ControlName, stringParam.Value);
                        else if (parameter is ComboxParam comboxParam)
                        {
                            var option = comboxParam.Options
                                .FirstOrDefault(x => x.Equals(((ComboxParam)parameter).Value));
                            if (option != null)
                                nodeStep.SetAttribute(comboxParam.ControlName, option.ControlName);
                            else
                                throw new InvalidOperationException(
                                    $"the option [{comboxParam.Value}] of {parameter.DisplayName} was not found in format template, the current recipe may be based on an old format template.");
                        }
                        else if (parameter is LoopComboxParam loopComboxParam)
                            nodeStep.SetAttribute(loopComboxParam.ControlName, loopComboxParam.Value);
                        else if (parameter is PositionParam positionParam)
                            nodeStep.SetAttribute(positionParam.ControlName, positionParam.Value);
                        else if (parameter is BoolParam boolParam)
                            nodeStep.SetAttribute(boolParam.ControlName, boolParam.Value.ToString());
                        else if (parameter is StepParam stepParam)
                            nodeStep.SetAttribute(stepParam.ControlName, stepParam.Value.ToString());
                        else if (parameter is MultipleSelectParam selectParam)
                        {
                            var selected = new List<string>();
                            selectParam.Options.Apply(
                                 opt =>
                                 {
                                     if (opt.IsChecked)
                                         selected.Add(opt.ControlName);
                                 }
                                                        );
                            nodeStep.SetAttribute(selectParam.ControlName, string.Join(",", selected));
                        }
                        else if (parameter is PopSettingParam)
                        {
                            SetAttribute(PopSettingSteps[parameter.ControlName][i], nodePop[parameter.ControlName]);
                        }
                    }

                    if (ToleranceEnable)
                    {
                        nodeStep.AppendChild(nodeWarning);
                        nodeStep.AppendChild(nodeAlarm);
                    }

                    foreach (var key in PopEnable.Keys)
                    {
                        if (PopEnable[key])
                        {
                            nodeStep.AppendChild(nodePop[key]);
                        }
                    }

                    nodeModule.AppendChild(nodeStep);

                    i++;
                }

                var nodeConfig = _doc.CreateElement("Config");
                foreach (var parameter in ConfigItems)
                {
                    if (parameter.Visible == Visibility.Visible)
                    {
                        if (parameter is IntParam intParam)
                            nodeConfig.SetAttribute(intParam.ControlName, intParam.Value.ToString());
                        else if (parameter is DoubleParam doubleParam)
                        {
                            var dValue = doubleParam.Value;
                            var config = ConfigItems.Where(m => m.ControlName == parameter.ControlName).FirstOrDefault();

                            if (config is DoubleParam param1)
                            {
                                if (param1.Minimun == 0 && param1.Maximun == 0)
                                {
                                    //没有设定范围
                                }
                                else if (dValue > param1.Maximun || dValue < param1.Minimun)
                                {
                                    throw new ArgumentOutOfRangeException($"The set value of {doubleParam.DisplayName} is {dValue}, out of the range {param1.Minimun}~{param1.Maximun}");
                                }
                            }

                            nodeConfig.SetAttribute(doubleParam.ControlName, doubleParam.Value.ToString());
                        }
                        else if (parameter is StringParam param)
                            nodeConfig.SetAttribute(param.ControlName, param.Value);
                        else if (parameter is ComboxParam comboxParam)
                            nodeConfig.SetAttribute(comboxParam.ControlName, comboxParam.Value.ControlName);
                        else if (parameter is LoopComboxParam loopComboxParam)
                            nodeConfig.SetAttribute(loopComboxParam.ControlName, loopComboxParam.Value);
                        else if (parameter is PositionParam positionParam)
                            nodeConfig.SetAttribute(positionParam.ControlName, positionParam.Value);
                        else if (parameter is BoolParam boolParam)
                            nodeConfig.SetAttribute(boolParam.ControlName, boolParam.Value.ToString());
                        else if (parameter is StepParam stepParam)
                            nodeConfig.SetAttribute(stepParam.ControlName, stepParam.Value.ToString());
                        else if (parameter is MultipleSelectParam selectParam)
                        {
                            var selected = new List<string>();
                            selectParam.Options.Apply(
                                 opt =>
                                 {
                                     if (opt.IsChecked)
                                         selected.Add(opt.ControlName);
                                 }
                                                        );
                            nodeConfig.SetAttribute(selectParam.ControlName, string.Join(",", selected));
                        }
                    }
                }
                nodeModule.AppendChild(nodeConfig);

                xml = _doc.OuterXml;
                return true;
            }
            catch (Exception ex)
            {
                LOG.Write(ex.Message);
                reason = ex.Message;
                return false;
            }
        }

        /// <summary>
        /// 将step序号及之前的step标记为完成
        /// </summary>
        /// <param name="currentStepNum"></param>
        public void SetProcessDone(int currentStepNum)
        {
            foreach (var step in Steps)
            {
                if (step.StepNo.HasValue)
                {
                    if (step.StepNo < currentStepNum)
                        step.IsProcessed = true;
                    else if (step.StepNo == currentStepNum)
                    {
                        step.IsProcessed = true;
                        foreach (var item in step)
                        {
                            item.Reset();
                        }
                    }
                    else
                    {
                        step.IsProcessed = false;
                    }
                }

            }
        }

        /// <summary>
        /// 将未保存的参数进行高亮并缓存
        /// </summary>
        public void HighLight()
        {
            if (Steps != null)
            {
                HighlightDic = new Dictionary<string, List<string>>();
                foreach (var i in Steps)
                {
                    if (!i.IsSaved)
                    {
                        HighlightDic[i.StepUid] = new List<string>();
                        foreach (var item in i)
                        {
                            if (!item.IsSaved)
                            {
                                item.Highlight();
                                HasHighLight = true;
                                HighlightDic[i.StepUid].Add(item.ControlName);
                            }
                        }
                        i.Save();
                    }
                }
            }
        }

        #region Step Operations

        public void AddStep()
        {
            Steps.Add(CreateStep(Columns));
            Validate();
        }

        public void InsertToRight(RecipeStep step)
        {
            if (step == null)
                return;

            if (!RecipeStep.ValidateStepNo(step.StepNo, out var no))
                return;

            var index = no - RecipeFormatBuilder.STEP_INDEX_BASE;
            if (index < Steps.Count - 1)
                Steps.Insert(index + 1, CreateStep(Columns));
            else
                Steps.Add(CreateStep(Columns));

        }

        public void InsertToLeft(RecipeStep step)
        {
            if (step == null)
                return;

            if (!RecipeStep.ValidateStepNo(step.StepNo, out var no))
                return;

            var index = no - RecipeFormatBuilder.STEP_INDEX_BASE;
            Steps.Insert(index, CreateStep(Columns));

        }

        private readonly List<RecipeStep> _copiedSteps = new List<RecipeStep>();

        public void CopySteps()
        {
            _copiedSteps.Clear();

            if (!Steps.SelectedSteps.Any())
                return;

            foreach (var step in Steps.SelectedSteps)
            {
                _copiedSteps.Add(CloneStep(Columns, step));
            }
        }

        /// <summary>
        /// 粘贴Step。
        /// <para>注意：async Task 标记的方法不能直接应用于Caliburn.Message.Attach 方法。</para>
        /// </summary>
        /// <param name="isToLeft">是否粘贴到当前选中的Step的左侧。</param>
        /// <returns></returns>
        public async Task Paste(bool isToLeft)
        {
            if (Steps.SelectedSteps.Count != 1)
                return;

            if (_copiedSteps.Count <= 0)
                return;

            var stepNo = Steps.SelectedSteps[0].StepNo;
            if (RecipeStep.ValidateStepNo(stepNo, out var vStepNo) == false)
                return;

            foreach (var t in _copiedSteps)
            {
                var cloned = CloneStep(Columns, t);

                var pos = isToLeft ? vStepNo - RecipeFormatBuilder.STEP_INDEX_BASE : vStepNo;

                await Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                    (Action)(() =>
                    {
                        Steps.Insert(pos, cloned);
                    }));
            }

            ValidLoopData();
            Validate();
        }

        public void DeleteSteps()
        {
            if (!Steps.SelectedSteps.Any())
                return;

            for (var i = Steps.SelectedSteps.Count - 1; i >= 0; i--)
            {
                Steps.Remove(Steps.SelectedSteps[i]);
            }

            Validate();
        }

        #endregion
        
        #region Import Export Methods

        private static byte[] GetMagicNumber()
        {
            return Encoding.ASCII.GetBytes("SIC");
        }

        /// <summary>
        /// 导入Recipe
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="xmlContent"></param>
        public static void ImportRecipe(string fileName, out string xmlContent)
        {
            xmlContent = "";

            using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
            {
                using (var br = new BinaryReader(fs))
                {
                    var magicNumDesired = GetMagicNumber();
                    var magicNum = br.ReadBytes(3);

                    // Magic Number incorrect

                    if (!magicNum.SequenceEqual(magicNumDesired))
                        throw new Exception("format error, code -1.");

                    // read hash data
                    var lenHash = br.ReadInt32();
                    var hashInFile = br.ReadBytes(lenHash);

                    // read base64 recipe content
                    var lenContent = br.ReadInt32();
                    var buffContent = br.ReadBytes(lenContent);

                    // check hash
                    using (var sha = SHA256.Create())
                    {
                        var hash = sha.ComputeHash(buffContent);
                        if (!hash.SequenceEqual(hashInFile))
                            throw new Exception("format error, code -2.");
                    }

                    var strBase64 = Encoding.ASCII.GetString(buffContent);
                    var bufRecipe = Convert.FromBase64String(strBase64);
                    var xmlRecipe = Encoding.UTF8.GetString(bufRecipe);

                    xmlContent = xmlRecipe;

                }
            }
        }

        /// <summary>
        /// 导出Recipe
        /// </summary>
        /// <param name="fileName">导出Recipe的文件名</param>
        /// <param name="reason">导出失败的原因</param>
        public bool ExportRecipe(string fileName, out string reason)
        {
            if (!GetXmlString(out var xmlContent, out reason))
                return false;

            // 未加密，仅用Base64对Recipe字串编码，可直接用WinHex查看内容，方便后期调试。
            var ba = Encoding.UTF8.GetBytes(xmlContent);
            var base64 = Convert.ToBase64String(ba);
            ba = Encoding.ASCII.GetBytes(base64);
            using var sha = SHA256.Create();
            var hash = sha.ComputeHash(ba);

            using var fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
            // discard the contents of the file by setting the length to 0
            fs.SetLength(0);

            using var bw = new BinaryWriter(fs);
            var magicNum = GetMagicNumber();
            bw.Write(magicNum, 0, magicNum.Length);
            bw.Write(hash.Length);
            bw.Write(hash);
            bw.Write(ba.Length);
            bw.Write(ba);
            bw.Flush();

            return true;
        }

        #endregion

        #region Param Accessible Whitelist Edit Mode

        private void ResetHighlight()
        {
            Steps.ResetHighlight();
        }

        private async Task HideValueButAccessibleWhitelist()
        {
            Steps.ToList().ForEach(s=>s.Permission = MenuPermissionEnum.MP_NONE);
            await ShowAccessibleWhitelistParamValues();
        }

        /// <summary>
        /// 恢复所有参数的访问权限到指定的角色。
        /// </summary>
        /// <param name="role"></param>
        public void UpdatePermission(Role role)
        {
            //2025 - 3 - 13HQQ, 取消权限功能，显示所有Step
            return;


            if (RecipeType != RecipeType.Process)
                return;
            
            // 更新RecipeFormatBuilder中Columns的权限
            _formatBuilder.UpdateColumnsPermission(role);

            // 更新当前RecipeData中Columns的权限
            foreach (var col in Columns)
            {
                RecipeFormatBuilder.SetPermission(col, role);
            }

            // 更新当前RecipeData中Steps的权限
            foreach (var step in Steps)
            {
                foreach (var param in step)
                {
                    param.ColumnPermChangedCallback?.Invoke(param);
                }
                
                RecipeStep.SetPermission(step, role);
            }
        }

        /// <summary>
        /// 从数据库读取参数访问白名单。
        /// </summary>
        /// <returns></returns>
        private Task<DataTable> GetAccessibleWhitelist(string recipeFullName)
        {
            var cmd = $"select * from recipe_cell_access_permission_whitelist where \"recipeName\" = '{recipeFullName}'";
            var dt = QueryDataClient.Instance.Service.QueryData(cmd);
            return Task.FromResult(dt);
        }
        
        /// <summary>
        /// 高亮访问白名单中的参数。
        /// </summary>
        private async Task HighlightParamsInAccessibleWhitelist()
        {
            var dt = await GetAccessibleWhitelist(FullName);

            if (dt != null && dt.Rows.Count > 0)
            {
                foreach (DataRow dataRow in dt.Rows)
                {
                    var stepUid = dataRow["stepUid"].ToString();
                    var controlName = dataRow["columnName"].ToString();

                    var step = Steps.FirstOrDefault(s => s.StepUid == stepUid);
                    var param = step?.FirstOrDefault(p => p.ControlName == controlName);
                    Application.Current?.Dispatcher.BeginInvoke(() =>
                    {
                        param?.Highlight();
                    }, DispatcherPriority.Background);
                }
            }
        }

        /// <summary>
        /// 计算访问白名单中的参数的数量。
        /// </summary>
        /// <returns></returns>
        private int CountAccessibleWhitelistParams()
        {
            var total = Steps.ToList().Sum(x => x.GetHighlightedParams().Count);
            return total;
        }

        /// <summary>
        /// 保存Cell访问权限白名单。
        /// </summary>
        private async Task SaveAccessibleWhitelist()
        {
            var cmdStr = new StringBuilder();
            var insertExp =
                "INSERT into recipe_cell_access_permission_whitelist (\"uid\", \"recipeName\",\"stepUid\",\"columnName\",\"whoSet\",\"whenSet\") VALUES ";

            var whenSet = DateTime.Now;

            // 枚举所有高亮的Recipe参数。
            foreach (var step in Steps)
            {
                var lst = step.GetHighlightedParams();
                lst.ForEach(p =>
                {
                    cmdStr.AppendLine($"({(new RecipeCellAccessPermissionWhitelistInfo(FullName, step.StepUid, p.ControlName, whenSet))}),");
                });
            }

            // 是否有需要保存的Cell信息？如果没有，cmdStr留空，则之前的白名单会被删除。
            if (cmdStr.Length > 0)
                cmdStr.Insert(0, insertExp);

            // 刷新白名单
            QueryDataClient.Instance.Service.ExcuteTransAction(new List<string>(new string[]
            {
                // 删除原有的白名单
                $"delete from recipe_cell_access_permission_whitelist where \"recipeName\" = '{FullName}'",
                
                // 写入新的白名单
                cmdStr.ToString().TrimEnd('\n').TrimEnd('\r').TrimEnd(',')
            }));

            await Task.CompletedTask;
        }

        /// <summary>
        /// 显示访问白名单中的参数的数值。
        /// </summary>
        private async Task ShowAccessibleWhitelistParamValues()
        {
            var dt = await GetAccessibleWhitelist(FullName);

            if (dt != null && dt.Rows.Count > 0)
            {
                foreach (DataRow dataRow in dt.Rows)
                {
                    var stepUid = dataRow[RecipeFormatBuilder.SPEC_COL_STEPUID].ToString();
                    var controlName = dataRow["columnName"].ToString();

                    var step = Steps.FirstOrDefault(s => s.StepUid == stepUid);
                    var param = step?.FirstOrDefault(p => p.ControlName == controlName);
                    if (param != null)
                    {
                        Application.Current?.Dispatcher.BeginInvoke(() =>
                        {
                            param.ColumnPermission = MenuPermissionEnum.MP_READ_WRITE;
                            param.StepPermission = MenuPermissionEnum.MP_READ_WRITE;
                        }, DispatcherPriority.Background);

                    }
                }
            }
        }

        /// <summary>
        /// 进入访问白名单编辑模式。
        /// </summary>
        /// <returns></returns>
        public async Task<(bool isSucceeded, string reason)> EnterAccessibleWhitelistEditMode()
        {
            ChkChanged(Steps);
            if (IsChanged)
            {
                var reason = "the recipe has been changed.";
                return await Task.FromResult((false, reason));
            }

            
            ResetHighlight();
            await HighlightParamsInAccessibleWhitelist();
            
            IsAccessibleWhitelistEditMode = true;
            return await Task.FromResult((true, ""));
        }

        /// <summary>
        /// 退出访问白名单编辑模式。
        /// </summary>
        public async Task LeaveAccessibleWhitelistEditMode()
        {
            await SaveAccessibleWhitelist();
            ResetHighlight();

            if (IsEnableAccessibleWhitelist)
                await HideValueButAccessibleWhitelist();

            IsAccessibleWhitelistEditMode = false;

            await Task.CompletedTask;
        }

        /// <summary>
        /// 删除Cell访问权限白名单。
        /// </summary>
        public void DeleteAccessibleWhiteList()
        {
            // 刷新白名单
            QueryDataClient.Instance.Service.ExcuteTransAction(new List<string>(new string[]
            {
                // 删除原有的白名单
                $"delete from recipe_cell_access_permission_whitelist where \"recipeName\" = '{FullName}'",
            }));
        }

        #endregion

        #endregion
    }
}
