//******************************************************************************//
// LogImportTemplateViewModel.cs
// 12/4/2023
//
// RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
// PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
// AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
// UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
// RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
// CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
// OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
// COMPANY.
//
// THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S. GOVERNMENT
//
// UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
//
// WARNING: THIS DOCUMENT CONTAINS TECHNICAL DATA AND / OR TECHNOLOGY WHOSE
// EXPORT OR DISCLOSURE TO NON-U.S. PERSONS, WHEREVER LOCATED, IS RESTRICTED
// BY THE INTERNATIONAL TRAFFIC IN ARMS REGULATIONS (ITAR) (22 C.F.R. SECTION
// 120-130) OR THE EXPORT ADMINISTRATION REGULATIONS (EAR) (15 C.F.R. SECTION
// 730-774). THIS DOCUMENT CANNOT BE EXPORTED (E.G., PROVIDED TO A SUPPLIER
// OUTSIDE OF THE UNITED STATES) OR DISCLOSED TO A NON-U.S. PERSON, WHEREVER
// LOCATED, UNTIL A FINAL JURISDICTION AND CLASSIFICATION DETERMINATION HAS
// BEEN COMPLETED AND APPROVED BY RAYTHEON, AND ANY REQUIRED U.S. GOVERNMENT
// APPROVALS HAVE BEEN OBTAINED. VIOLATIONS ARE SUBJECT TO SEVERE CRIMINAL
// PENALTIES.
//
// DOD 5220.22-M, INDUSTRIAL SECURITY MANUAL, CHAPTER 5, SECTION 1 THROUGH 9 :
// FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN OR DOD 5200.1-R,
// INFORMATION SECURITY PROGRAM, CHAPTER 6. FOR UNCLASSIFIED, LIMITED DOCUMENTS
// DESTROY BY ANY  METHOD THAT WILL PREVENT DISCLOSURE OF CONTENTS OR
// RECONSTRUCTION OF THE DOCUMENT.
// POC: Alex Kravchenko (1118268)
//******************************************************************************//
using NLog.LayoutRenderers;
using NLog.Layouts;
using NLog;
using Raytheon.LogDashboard.Helpers;
using Raytheon.LogDashboard.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Windows;
using System.Xml.Serialization;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using GalaSoft.MvvmLight.Ioc;
using GalaSoft.MvvmLight.Messaging;

namespace Raytheon.LogDashboard.ViewModel
{
    [Serializable]
    [DataContract]
    public class LogImportTemplateViewModel : ViewModelBase
    {
        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
        private string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "LogDashboard", "template_import_settings.xml");

        private string importFilePath = string.Empty;
        private string templateSeparator = ";";
        private string selectedEncoding = "UTF-8";
        private bool? dialogResult;
        private bool needUpdateFile;
        private string templateString = "${longdate};${level};${callsite};${logger};${message};${exception:format=tostring}";
        protected static LogDashViewModelLocator LocatorInstance => (Application.Current.Resources["LogDashLocator"] as LogDashViewModelLocator);

        #region Properties
        [XmlIgnore]
        public Dictionary<string, List<ImportTemplateParameters>> PopularTemplates { get; set; } = new Dictionary<string, List<ImportTemplateParameters>>();

        [XmlElement(Order = 7)]
        public List<ImportTemplateParameters> SelectedPopularTemplate { get; set; }

        [XmlElement(Order = 8)]
        public ObservableCollection<LogTemplateItem> TemplateLogItems { get; set; } = new ObservableCollection<LogTemplateItem>();

        [XmlElement(Order = 1)]
        public bool IsAutomaticDetectTemplateSelected { get; set; } = true;
        [XmlElement(Order = 2)]
        public bool IsPopularTemplateSelected { get; set; }
        [XmlElement(Order = 3)]
        public bool IsUserTemplateSelected { get; set; }
        [XmlElement(Order = 4)]
        public bool IsLayoutStringTemplateSelected { get; set; }

        [XmlIgnore]
        public bool? DialogResult
        {
            get => dialogResult;
            set => Set(() => DialogResult, ref dialogResult, value);
        }

        [XmlIgnore]
        public LogTemplate LogTemplate { get; private set; } = new LogTemplate();

        [XmlElement(Order = 5)]
        public string TemplateString
        {
            get => templateString;
            set => Set(() => TemplateString, ref templateString, value);
        }

        [XmlElement(Order = 6)]
        public string TemplateSeparator
        {
            get => templateSeparator;
            set => Set(() => TemplateSeparator, ref templateSeparator, value);
        }

        [XmlElement(Order = 9)]
        public bool NeedUpdateFile
        {
            get => needUpdateFile;
            set => Set(() => NeedUpdateFile, ref needUpdateFile, value);
        }

        [XmlElement(Order = 10)]
        public string SelectedEncoding
        {
            get => selectedEncoding;
            set => Set(() => SelectedEncoding, ref selectedEncoding, value);
        }

        [XmlIgnore]
        public string ImportPath
        {
            get => importFilePath;
            set => SetPath(value);
        }


        #endregion

        #region Commands

        public RelayCommand OkCommand { get; set; }
        public RelayCommand AddTemplateItemCommand { get; set; }
        public RelayCommand<object> RemoveTemplateItemCommand { get; set; }
        public RelayCommand SaveTemplateSettingsCommand { get; set; }

        #endregion


        [PreferredConstructor]
        public LogImportTemplateViewModel()
        {
            OkCommand = new RelayCommand(Confirm);
            AddTemplateItemCommand = new RelayCommand(AddTemplateItem);
            RemoveTemplateItemCommand = new RelayCommand<object>(RemoveTemplateItem);
            SaveTemplateSettingsCommand = new RelayCommand(SaveTemplateSettingsToFile);
        }

        public void SetPath(string path)
        {
            importFilePath = path;

            PopularTemplates.Add("${date};${level};${other};${processid};${threadid};${сallsite};${logger};${message}",
                new List<ImportTemplateParameters>
                {
                    ImportTemplateParameters.date,
                    ImportTemplateParameters.level,
                    ImportTemplateParameters.other,
                    ImportTemplateParameters.processid,
                    ImportTemplateParameters.threadid,
                    ImportTemplateParameters.сallsite,
                    ImportTemplateParameters.logger,
                    ImportTemplateParameters.message
                });

            PopularTemplates.Add("${date};${level};${threadid};${сallsite};${logger};${message}",
                new List<ImportTemplateParameters>
                {
                    ImportTemplateParameters.date,
                    ImportTemplateParameters.level,
                    ImportTemplateParameters.threadid,
                    ImportTemplateParameters.сallsite,
                    ImportTemplateParameters.logger,
                    ImportTemplateParameters.message
                });

            PopularTemplates.Add("${date};${level};${threadid};${logger};${message}",
                new List<ImportTemplateParameters>
                {
                    ImportTemplateParameters.date,
                    ImportTemplateParameters.level,
                    ImportTemplateParameters.threadid,
                    ImportTemplateParameters.logger,
                    ImportTemplateParameters.message
                });

            PopularTemplates.Add("${date};${level};${сallsite};${logger};${message}",
                new List<ImportTemplateParameters>
                {
                    ImportTemplateParameters.date,
                    ImportTemplateParameters.level,
                    ImportTemplateParameters.сallsite,
                    ImportTemplateParameters.logger,
                    ImportTemplateParameters.message
                });

            PopularTemplates.Add("${date};${level};${logger};${message}",
                new List<ImportTemplateParameters>
                {
                    ImportTemplateParameters.date,
                    ImportTemplateParameters.level,
                    ImportTemplateParameters.logger,
                    ImportTemplateParameters.message
                });

            PopularTemplates.Add("${longdate}|${level:uppercase=true}|${logger}|${message}",
                new List<ImportTemplateParameters>
                {
                    ImportTemplateParameters.longdate,
                    ImportTemplateParameters.level,
                    ImportTemplateParameters.logger,
                    ImportTemplateParameters.message
                });

            SelectedPopularTemplate = PopularTemplates.First().Value;

            if(!File.Exists(settingsPath) && File.Exists($"{AppDomain.CurrentDomain.BaseDirectory}template_import_settings.xml"))
            {
                settingsPath = $"{AppDomain.CurrentDomain.BaseDirectory}template_import_settings.xml";
            }

            if(File.Exists(settingsPath))
            {
                XmlSerializer ser = new XmlSerializer(GetType());
                try
                {
                    using(FileStream fs = new FileStream(settingsPath, FileMode.Open))
                    {
                        LogImportTemplateViewModel litvm = (LogImportTemplateViewModel)ser.Deserialize(fs);
                        IsAutomaticDetectTemplateSelected = litvm.IsAutomaticDetectTemplateSelected;
                        IsLayoutStringTemplateSelected = litvm.IsLayoutStringTemplateSelected;
                        IsPopularTemplateSelected = litvm.IsPopularTemplateSelected;
                        IsUserTemplateSelected = litvm.IsUserTemplateSelected;
                        TemplateSeparator = litvm.TemplateSeparator;
                        TemplateString = litvm.TemplateString;
                        NeedUpdateFile = litvm.NeedUpdateFile;
                        SelectedEncoding = litvm.SelectedEncoding;
                        SelectedPopularTemplate = PopularTemplates.FirstOrDefault(x => x.Value.SequenceEqual(litvm.SelectedPopularTemplate)).Value;
                        TemplateLogItems = new ObservableCollection<LogTemplateItem>(litvm.TemplateLogItems);

                        foreach(var templateLogItem in TemplateLogItems)
                        {
                            var ltii = templateLogItem.TemplateItems
                                .Cast<LogTemplateItemInfo>()
                                .FirstOrDefault(x => x.Parameter == templateLogItem.SelectedTemplateParameter.Parameter);
                            templateLogItem.SelectedTemplateParameter = ltii;
                        }
                    }
                }
                catch(Exception ex)
                {
                    logger.Warn(ex, "An error occurred while read template settings");
                }
            }
        }

        #region Command Handlers

        private void Confirm()
        {
            LogTemplate.Encoding = SelectedEncoding;

            if(IsAutomaticDetectTemplateSelected)
            {
                if(!TryGetLogTemplateByAutodetect())
                {
                    return;
                }
            }
            else if(IsPopularTemplateSelected)
            {
                GetLogTemplateByPopularTemplates();
            }
            else if(IsLayoutStringTemplateSelected)
            {
                GetLogTemplateByParsingLayoutPattern();
            }
            else if(IsUserTemplateSelected)
            {
                if(TryGetLogTemplateByUserGeneratedPattern())
                {
                    return;
                }
            }

            DialogResult = true;
        }

        private void AddTemplateItem()
        {
            TemplateLogItems.Add(new LogTemplateItem());
        }

        private void RemoveTemplateItem(object obj)
        {
            if(obj is LogTemplateItem item)
            {
                TemplateLogItems.Remove(item);
            }
        }

        private void SaveTemplateSettingsToFile()
        {
            try
            {
                XmlSerializer ser = new XmlSerializer(this.GetType());
                Directory.CreateDirectory(Path.GetDirectoryName(settingsPath));
                using(FileStream fs = new FileStream(settingsPath, FileMode.Create))
                {
                    ser.Serialize(fs, this);
                }

                Messenger.Default.Send("Template settings successfully saved.", "DisplayCustomMessage");
            }
            catch(Exception e)
            {
                Messenger.Default.Send($"Unable to Save Template settings.\n{e.Message}", "DisplayCustomMessage");
                logger.Warn(e, "An error occurred while Save settings.");
            }
        }

        #endregion

        #region Templates

        private bool TryGetLogTemplateByAutodetect()
        {
            string firstMessage = GetFirstLogMessage();

            if(string.IsNullOrWhiteSpace(firstMessage))
            {
                Messenger.Default.Send($"An error occurred while trying to automatically match the message template.\nEmpty data file", "DisplayCustomMessage");
                return false;
            }

            if(!TryDetectTemplate(firstMessage))
            {
                Messenger.Default.Send($"An error occurred while trying to automatically match the message template.", "DisplayCustomMessage");
                return false;
            }
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private bool TryGetLogTemplateByUserGeneratedPattern()
        {
            LogTemplate.Separator = TemplateSeparator;
            for(int i = 0; i < TemplateLogItems.Count; i++)
            {
                try
                {
                    LogTemplate.TemplateParameters.Add(TemplateLogItems[i].SelectedTemplateParameter.Parameter, i);
                }
                catch(Exception ex)
                {
                    LogTemplate.TemplateParameters.Clear();
                    Messenger.Default.Send($"Unable to Add Template\n{ex.Message}", "DisplayCustomMessage");
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        private void GetLogTemplateByPopularTemplates()
        {
            for(int i = 0; i < SelectedPopularTemplate.Count; i++)
            {
                LogTemplate.TemplateParameters.Add(SelectedPopularTemplate[i], i);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void GetLogTemplateByParsingLayoutPattern()
        {
            SimpleLayout layout = new SimpleLayout(TemplateString);
            List<LayoutRenderer> elements = layout.Renderers.Where(x => !(x is LiteralLayoutRenderer)).ToList();

            if(elements.Count < 4)
            {
                Messenger.Default.Send($"An error occurred while parsing template string", "DisplayCustomMessage");
                return;
            }

            var separator = layout.Renderers.FirstOrDefault(x => x is LiteralLayoutRenderer);

            LogTemplate.Separator = separator != null ? separator.Render(LogEventInfo.CreateNullEvent()) : ";";

            List<string> tElements = TemplateString.Split(new[] { LogTemplate.Separator }, StringSplitOptions.None)
                .Where(x => !string.IsNullOrEmpty(x)).ToList();

            int j = 0;
            for(int i = 0; i < tElements.Count; i++)
            {
                int modifyIndex = tElements[i].IndexOf(":");
                if(modifyIndex > 0)
                {
                    tElements[i] = tElements[i].Substring(0, modifyIndex);
                }

                tElements[i] = tElements[i].Replace("}", "");

                if(j <= elements.Count)
                {
                    string layoutRender = elements[j].ToString();
                    if(!layoutRender.Contains(tElements[i]))
                    {
                        continue;
                    }
                }

                if(elements[j] is LevelLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.level, i);
                }

                if(elements[j] is CallSiteLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.сallsite, i);
                }

                if(elements[j] is MessageLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.message, i);
                }

                if(elements[j] is ThreadIdLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.threadid, i);
                }

                if(elements[j] is ProcessIdLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.processid, i);
                }

                if(elements[j] is LoggerNameLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.logger, i);
                }

                if(elements[j] is TimeLayoutRenderer || elements[j] is DateLayoutRenderer ||
                    elements[j] is LongDateLayoutRenderer || elements[j] is ShortDateLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.date, i);
                }

                if(elements[j] is TicksLayoutRenderer)
                {
                    LogTemplate.TemplateParameters.Add(ImportTemplateParameters.ticks, i);
                }

                j++;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string GetFirstLogMessage()
        {
            string[] logTypeArraySeparator1 =
            {
                ";Fatal;", ";Error;", ";Warn;", ";Trace;", ";Debug;", ";Info;",
                ";Fatal", ";Error", ";Warn", ";Trace", ";Debug", ";Info",
                "Fatal;", "Error;", "Warn;", "Trace;", "Debug;", "Info;"
            };

            string[] logTypeArraySeparator2 =
            {
                "|Fatal|", "|Error|", "|Warn|", "|Trace|", "|Debug|", "|Info|",
                "|Fatal", "|Error", "|Warn", "|Trace", "|Debug", "|Info",
                "Fatal|", "Error|", "Warn|", "Trace|", "Debug|", "Info|"
            };

            StringBuilder sb = new StringBuilder();
            using(StreamReader reader = new StreamReader(importFilePath))
            {
                string line;
                while((line = reader.ReadLine()) != null)
                {
                    if(line.ContainsAnyOf(logTypeArraySeparator1, true) || line.ContainsAnyOf(logTypeArraySeparator2, true))
                    {
                        if(sb.Length != 0)
                        {
                            break;
                        }

                        sb.Append(line);
                    }
                    else
                    {
                        sb.AppendLine(line);
                    }
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// trying to detect template
        /// </summary>
        /// <param name="log"></param>
        /// <returns></returns>
        private bool TryDetectTemplate(string log)
        {
            if(log.StartsWith("<log4j"))
            {
                return true;
            }

            string[] logSplit = log.Split(';');
            if(logSplit.Length < 4)
            {
                logSplit = log.Split('|');
                LogTemplate.Separator = "|";
            }

            ImportTemplateParameters dataParameter = ImportTemplateParameters.date;

            int dateTimeIndex = GetDateTimeIndex(logSplit, ref dataParameter);
            if(dateTimeIndex == -1)
            {
                return false;
            }

            var intIndexes = GetIntIndexes(logSplit);
            int logLevelIndex = GetLogLevelIndex(logSplit);
            if(dateTimeIndex == -1)
            {
                return false;
            }

            List<int> otherIndexes = new List<int>();

            for(int i = 0; i < logSplit.Length; i++)
            {

                if(i == dateTimeIndex || i == logLevelIndex || intIndexes.Contains(i))
                {
                    continue;
                }

                if(!string.IsNullOrEmpty(logSplit[i]))
                {
                    otherIndexes.Add(i);
                }
            }

            if(otherIndexes.Count < 2)
            {
                return false;
            }

            LogTemplate.TemplateParameters.Add(dataParameter, dateTimeIndex);
            LogTemplate.TemplateParameters.Add(ImportTemplateParameters.level, logLevelIndex);
            LogTemplate.TemplateParameters.Add(ImportTemplateParameters.message, otherIndexes[1]);
            LogTemplate.TemplateParameters.Add(ImportTemplateParameters.logger, otherIndexes.First());

            if(intIndexes.Count > 0)
            {
                LogTemplate.TemplateParameters.Add(ImportTemplateParameters.threadid, intIndexes.Last());
            }

            return true;
        }

        private int GetDateTimeIndex(string[] logSplit, ref ImportTemplateParameters dateTemplateParameter)
        {
            int index = -1;
            for(int i = 0; i < logSplit.Length; i++)
            {
                if(DateTime.TryParse(logSplit[i], out DateTime date))
                {
                    return i;
                }

                if(DateTime.TryParseExact(logSplit[i], "MM-dd-y HH:mm:ss.ffff", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dt))
                {
                    return i;
                }

                if(long.TryParse(logSplit[i], out long tickDateTime))
                {
                    try
                    {
                        DateTime dateFromTicks = new DateTime(tickDateTime);
                        dateTemplateParameter = ImportTemplateParameters.ticks;
                        return i;
                    }
                    catch(Exception e)
                    {
                        logger.Warn(e, "An error occurred while getting date time index.");
                    }
                }
            }
            return index;
        }

        private List<int> GetIntIndexes(string[] logSplit)
        {
            List<int> indexes = new List<int>();
            for(int i = 0; i < logSplit.Length; i++)
            {
                if(int.TryParse(logSplit[i], out int number))
                {
                    indexes.Add(i);
                }
            }

            return indexes;
        }

        private int GetLogLevelIndex(string[] logSplit)
        {
            int index = -1;
            for(int i = 0; i < logSplit.Length; i++)
            {
                if(Enum.TryParse(logSplit[i].ToPascalCase(), out DashboardLogLevel level))
                {
                    return i;
                }
            }
            return index;
        }

        #endregion
    }
}
