//******************************************************************************//
// ExtensionMethods.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 Raytheon.LogDashboard.Model;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Media;

namespace Raytheon.LogDashboard.Helpers
{
    public static class ExtensionMethods
    {
        /// <summary>
        /// Checks whether a FileInfo or DirectoryInfo object is a directory, or intended to be a directory.
        /// </summary>
        /// <param name="fileSystemInfo"></param>
        /// <returns></returns>
        public static bool IsDirectory(this FileSystemInfo fileSystemInfo)
        {
            if(fileSystemInfo == null)
            {
                return false;
            }

            if((int)fileSystemInfo.Attributes != -1)
            {
                // if attributes are initialized check the directory flag
                return fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory);
            }

            // If we get here the file probably doesn't exist yet.  The best we can do is 
            // try to judge intent.  Because directories can have extensions and files
            // can lack them, we can't rely on filename.
            // 
            // We can reasonably assume that if the path doesn't exist yet and 
            // FileSystemInfo is a DirectoryInfo, a directory is intended.  FileInfo can 
            // make a directory, but it would be a bizarre code path.

            return fileSystemInfo is DirectoryInfo;
        }

        public static string FirstCharToUpper(this string input)
        {
            switch(input)
            {
                case null:
                    throw new ArgumentNullException(nameof(input));
                case "":
                    throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input));
                default:
                    return input.First().ToString().ToUpper() + input.Substring(1);
            }
        }

        public static bool Contains(this string source, string toCheck, StringComparison comp)
        {
            return source?.IndexOf(toCheck, comp) >= 0;
        }

        public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
        {
            return source.Skip(Math.Max(0, source.Count() - N));
        }

        public static IEnumerable<TSource> DistinctBy<TSource, TKey>
            (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            HashSet<TKey> seenKeys = new HashSet<TKey>();
            foreach(TSource element in source)
            {
                if(seenKeys.Add(keySelector(element)))
                {
                    yield return element;
                }
            }
        }

        /// <summary>
        /// Convert the Brush to a ARGB - Color. 
        /// </summary>
        /// <param name="brush">your object</param>
        /// <returns>
        /// White = #ffffffff
        /// Green = #ff00ff00
        /// </returns>
        public static string ToARGB(this SolidColorBrush brush)
        {
            if(brush == null)
            {
                throw new ArgumentNullException();
            }

            var c = brush.Color;
            return $"#{c.A:X2}{c.R:X2}{c.G:X2}{c.B:X2}";
        }

        /// <summary>
        /// set the current brush to a new color based on the #argb string
        /// </summary>
        /// <param name="brush">your object</param>
        /// <param name="argb">The #ARGB Color</param>
        /// <returns>the same object as you run the function</returns>
        public static SolidColorBrush FromARGB(this SolidColorBrush brush, string argb)
        {
            if(argb.Length != 9)
            {
                throw new FormatException("we need #aarrggbb as color");
            }

            byte a = Convert.ToByte(int.Parse(argb.Substring(1, 2), NumberStyles.HexNumber));
            byte r = Convert.ToByte(int.Parse(argb.Substring(3, 2), NumberStyles.HexNumber));
            byte g = Convert.ToByte(int.Parse(argb.Substring(5, 2), NumberStyles.HexNumber));
            byte b = Convert.ToByte(int.Parse(argb.Substring(7, 2), NumberStyles.HexNumber));
            Color c = Color.FromArgb(a, r, g, b);
            brush.Color = c;
            return brush;
        }

        public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
        {
            return new HashSet<T>(source.ToList(), comparer);
        }

        /// <summary>
        /// returns true if string contains any of the members in search array
        /// </summary>
        /// <param name="line"></param>
        /// <param name="search"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static bool ContainsAnyOf(this String line, string[] search, bool ignoreCase = false)
        {
            return search.Any(x => ignoreCase ? line.ToUpper().Contains(x.ToUpper()) : line.Contains(x));
        }

        /// <summary>
        /// returns true if string contains any of the members in search list patterns
        /// </summary>
        /// <param name="input"></param>
        /// <param name="patterns"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static bool ContainsAnyOfPattern(this string input, string[] patterns, bool ignoreCase = false)
        {
            foreach(string pattern in patterns)
            {
                if(Regex.IsMatch(input, pattern, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// does the string begins with regex pattern?
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        public static bool StartsWithPattern(this string input, string pattern)
        {
            if(string.IsNullOrEmpty(input))
            {
                return false;
            }
            return Regex.IsMatch(input, $"^{pattern}");
        }

        /// <summary>
        /// truncates DateTime
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="timeSpan"></param>
        /// <returns></returns>
		public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
        {
            if(timeSpan == TimeSpan.Zero)
            {
                return dateTime; // Or could throw an ArgumentException
            }

            if(dateTime == DateTime.MinValue || dateTime == DateTime.MaxValue)
            {
                return dateTime; // do not modify "guard" values
            }

            return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
        }

        /// <summary>
        /// filters log messages from the main list
        /// </summary>
        /// <param name="messages"></param>
        /// <param name="text"></param>
        /// <param name="matchCase"></param>
        /// <param name="matchWholeWord"></param>
        /// <param name="useRegularExp"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        public static IEnumerable<LogMessage> Filter(this IEnumerable<LogMessage> messages,
            string text,
            bool matchCase,
            bool matchWholeWord,
            bool useRegularExp,
            DashboardLogLevel level = DashboardLogLevel.Trace)
        {
            IEnumerable<LogMessage> searchResult = messages.ToList();

            if(!matchCase && !matchWholeWord)
            {
                return searchResult.Where(x => level.HasFlag(x.Level) &&
                (x.Message.ToUpper().Contains(text, StringComparison.OrdinalIgnoreCase) ||
                (useRegularExp && Regex.IsMatch(x.Message.ToUpper(), text, RegexOptions.IgnoreCase))));
            }

            if(matchCase && !matchWholeWord)
            {
                return searchResult.Where(x => level.HasFlag(x.Level) && (x.Message.Contains(text) || (useRegularExp && Regex.IsMatch(x.Message, text))));
            }

            if(matchCase && matchWholeWord)
            {
                return searchResult.Where(x => level.HasFlag(x.Level) && (x.Message.Contains($" {text} ") ||
                                                                      x.Message.StartsWith($"{text} ") ||
                                                                      x.Message.EndsWith($" {text}") ||
                                                                      (x.Message.StartsWith(text) && x.Message.EndsWith(text)) ||
                                                                      (useRegularExp && Regex.IsMatch(x.Message, text))));
            }

            if(!matchCase && matchWholeWord)
            {
                return searchResult.Where(x => (level.HasFlag(x.Level) && (x.Message.Contains($" {text} ", StringComparison.OrdinalIgnoreCase) ||
                                                                      x.Message.StartsWith($"{text} ", StringComparison.OrdinalIgnoreCase) ||
                                                                      x.Message.EndsWith($" {text}", StringComparison.OrdinalIgnoreCase) ||
                                                                      (x.Message.StartsWith(text, StringComparison.OrdinalIgnoreCase) &&
                                                                      x.Message.EndsWith(text, StringComparison.OrdinalIgnoreCase)))) ||
                                                                      (useRegularExp && Regex.IsMatch(x.Message.ToUpper(), text, RegexOptions.IgnoreCase)));
            }

            return searchResult;
        }
    }
}
