using MainShell.Common;
using MainShell.Filewritable;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;

namespace MainShell.Process
{
    public class FlowStageMappingRule
    {
        public string WorkflowName { get; set; }
        public string ParentActivityName { get; set; }
        public string ActivityName { get; set; }
        public FlowDisplayStage Stage { get; set; }
        public string DisplayFlowName { get; set; }
    }

    public class FlowStageMappingConfig
    {
        public List<FlowStageMappingRule> Rules { get; } = new List<FlowStageMappingRule>();

        public static string DefaultConfigPath => Path.Combine(Paths.BasePath, "Configuration", "FlowStageMapping.json");

        public FlowDisplayStage Resolve(string workflowName, string parentActivityName, string activityName)
        {
            var rule = ResolveRule(workflowName, parentActivityName, activityName);

            return rule?.Stage ?? FlowDisplayStage.Align;
        }

        public string ResolveDisplayFlowName(string workflowName, string parentActivityName, string activityName)
        {
            var rule = ResolveRule(workflowName, parentActivityName, activityName);
            if (rule != null && !string.IsNullOrWhiteSpace(rule.DisplayFlowName))
            {
                return rule.DisplayFlowName;
            }

            if (!string.IsNullOrWhiteSpace(parentActivityName)) return parentActivityName;
            return workflowName;
        }

        private FlowStageMappingRule ResolveRule(string workflowName, string parentActivityName, string activityName)
        {
            return Rules.FirstOrDefault(r =>
                Match(r.WorkflowName, workflowName) &&
                Match(r.ParentActivityName, parentActivityName) &&
                Match(r.ActivityName, activityName));
        }

        private static bool Match(string expected, string actual)
        {
            if (string.IsNullOrWhiteSpace(expected) || expected == "*") return true;
            if (expected.IndexOf('*') >= 0)
            {
                var source = actual ?? string.Empty;
                var parts = expected.Split(new[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
                var index = 0;
                foreach (var part in parts)
                {
                    var found = source.IndexOf(part, index, StringComparison.OrdinalIgnoreCase);
                    if (found < 0) return false;
                    index = found + part.Length;
                }
                return true;
            }
            return string.Equals(expected, actual ?? string.Empty, StringComparison.OrdinalIgnoreCase);
        }

        public static FlowStageMappingConfig LoadOrCreate(string filePath = null)
        {
            var path = string.IsNullOrWhiteSpace(filePath) ? DefaultConfigPath : filePath;
            var dir = Path.GetDirectoryName(path);
            if (!string.IsNullOrWhiteSpace(dir))
            {
                Directory.CreateDirectory(dir);
            }

            if (!File.Exists(path))
            {
                var def = CreateDefault();
                def.Save(path);
                return def;
            }

            try
            {
                var loaded = Load(path);
                if (loaded == null || loaded.Rules.Count == 0)
                {
                    throw new InvalidDataException("FlowStageMapping config is empty.");
                }
                loaded.Normalize();
                return loaded;
            }
            catch
            {
                var def = CreateDefault();
                def.Save(path);
                return def;
            }
        }

        public void Save(string filePath = null)
        {
            var path = string.IsNullOrWhiteSpace(filePath) ? DefaultConfigPath : filePath;
            var ext = Path.GetExtension(path)?.ToLowerInvariant();

            Normalize();

            var data = new FlowStageMappingFile { Rules = Rules.ToList() };

            if (ext == ".xml")
            {
                var serializer = new XmlSerializer(typeof(FlowStageMappingFile));
                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    serializer.Serialize(fs, data);
                }
                return;
            }

            var json = JsonConvert.SerializeObject(data, Formatting.Indented);
            File.WriteAllText(path, json);
        }

        public static FlowStageMappingConfig Load(string filePath)
        {
            var ext = Path.GetExtension(filePath)?.ToLowerInvariant();
            FlowStageMappingFile data;

            if (ext == ".xml")
            {
                var serializer = new XmlSerializer(typeof(FlowStageMappingFile));
                using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    data = serializer.Deserialize(fs) as FlowStageMappingFile;
                }
            }
            else
            {
                var json = File.ReadAllText(filePath);
                data = JsonConvert.DeserializeObject<FlowStageMappingFile>(json);
            }

            var cfg = new FlowStageMappingConfig();
            if (data != null && data.Rules != null)
            {
                cfg.Rules.AddRange(data.Rules);
            }

            cfg.Normalize();

            return cfg;
        }

        private void Normalize()
        {
            foreach (var rule in Rules)
            {
                if (string.IsNullOrWhiteSpace(rule.DisplayFlowName) &&
                    !string.IsNullOrWhiteSpace(rule.WorkflowName) &&
                    !string.Equals(rule.WorkflowName, ProcessFlowName.AutoProduction, StringComparison.OrdinalIgnoreCase))
                {
                    rule.DisplayFlowName = rule.WorkflowName;
                }
            }
        }

        public static FlowStageMappingConfig CreateDefault()
        {
            var cfg = new FlowStageMappingConfig();

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.SubstrateLoadFlow,
                ParentActivityName = "*",
                ActivityName = "*",
                Stage = FlowDisplayStage.Load,
                DisplayFlowName = ProcessFlowName.SubstrateLoadFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.SubstratePositionFlow,
                ParentActivityName = "*",
                ActivityName = "*",
                Stage = FlowDisplayStage.Align,
                DisplayFlowName = ProcessFlowName.SubstratePositionFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.ChipStraighteningFlow,
                ParentActivityName = "*",
                ActivityName = "*",
                Stage = FlowDisplayStage.Align,
                DisplayFlowName = ProcessFlowName.ChipStraighteningFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.DiePositionFlow,
                ParentActivityName = "*",
                ActivityName = "*",
                Stage = FlowDisplayStage.Align,
                DisplayFlowName = ProcessFlowName.DiePositionFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.DieTransferFlow,
                ParentActivityName = "*",
                ActivityName = "*",
                Stage = FlowDisplayStage.Bond,
                DisplayFlowName = ProcessFlowName.DieTransferFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.DieRecheckFlow,
                ParentActivityName = "*",
                ActivityName = "*",
                Stage = FlowDisplayStage.Inspect,
                DisplayFlowName = ProcessFlowName.DieRecheckFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.SubstrateUnloadFlow,
                ParentActivityName = "*",
                ActivityName = "*",
                Stage = FlowDisplayStage.Unload,
                DisplayFlowName = ProcessFlowName.SubstrateUnloadFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.AutoProduction,
                ParentActivityName = ProcessFlowName.SubstrateLoadFlow,
                ActivityName = "*",
                Stage = FlowDisplayStage.Load,
                DisplayFlowName = ProcessFlowName.SubstrateLoadFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.AutoProduction,
                ParentActivityName = ProcessFlowName.SubstratePositionFlow,
                ActivityName = "*",
                Stage = FlowDisplayStage.Align,
                DisplayFlowName = ProcessFlowName.SubstratePositionFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.AutoProduction,
                ParentActivityName = ProcessFlowName.ChipStraighteningFlow,
                ActivityName = "*",
                Stage = FlowDisplayStage.Align,
                DisplayFlowName = ProcessFlowName.ChipStraighteningFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.AutoProduction,
                ParentActivityName = ProcessFlowName.DiePositionFlow,
                ActivityName = "*",
                Stage = FlowDisplayStage.Align,
                DisplayFlowName = ProcessFlowName.DiePositionFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.AutoProduction,
                ParentActivityName = ProcessFlowName.DieTransferFlow,
                ActivityName = "*",
                Stage = FlowDisplayStage.Bond,
                DisplayFlowName = ProcessFlowName.DieTransferFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.AutoProduction,
                ParentActivityName = ProcessFlowName.DieRecheckFlow,
                ActivityName = "*",
                Stage = FlowDisplayStage.Inspect,
                DisplayFlowName = ProcessFlowName.DieRecheckFlow
            });

            cfg.Rules.Add(new FlowStageMappingRule
            {
                WorkflowName = ProcessFlowName.AutoProduction,
                ParentActivityName = ProcessFlowName.SubstrateUnloadFlow,
                ActivityName = "*",
                Stage = FlowDisplayStage.Unload,
                DisplayFlowName = ProcessFlowName.SubstrateUnloadFlow
            });

            return cfg;
        }

        [XmlRoot("FlowStageMapping")]
        public class FlowStageMappingFile
        {
            [XmlArray("Rules")]
            [XmlArrayItem("Rule")]
            public List<FlowStageMappingRule> Rules { get; set; } = new List<FlowStageMappingRule>();
        }
    }
}
