using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;

namespace View_by_Distance.Shared.Models.Stateless.Methods;

internal abstract class Property
{

    internal static (int Season, string seasonName) GetSeason(int dayOfYear)
    {
        (int Season, string seasonName) result = dayOfYear switch
        {
            < 78 => new(0, "Winter"),
            < 171 => new(1, "Spring"),
            < 264 => new(2, "Summer"),
            < 354 => new(3, "Fall"),
            _ => new(4, "Winter")
        };
        return result;
    }

    internal static int GetDeterministicHashCode(byte[] value)
    {
        int result;
        unchecked
        {
            int hash1 = (5381 << 16) + 5381;
            int hash2 = hash1;
            for (int i = 0; i < value.Length; i += 2)
            {
                hash1 = ((hash1 << 5) + hash1) ^ value[i];
                if (i == value.Length - 1)
                    break;
                hash2 = ((hash2 << 5) + hash2) ^ value[i + 1];
            }
            result = hash1 + (hash2 * 1566083941);
        }
        return result;
    }

    internal static int GetDeterministicHashCode(string value)
    {
        int result;
        unchecked
        {
            int hash1 = (5381 << 16) + 5381;
            int hash2 = hash1;
            for (int i = 0; i < value.Length; i += 2)
            {
                hash1 = ((hash1 << 5) + hash1) ^ value[i];
                if (i == value.Length - 1)
                    break;
                hash2 = ((hash2 << 5) + hash2) ^ value[i + 1];
            }
            result = hash1 + (hash2 * 1566083941);
        }
        return result;
    }

    internal static (bool?, string[]) IsWrongYear(string[] segments, string year)
    {
        bool? result;
        string[] results = (
            from l
            in segments
            where l?.Length > 2
            && (
                l[..2] is "18" or "19" or "20"
            || (l.Length == 5 && l.Substring(1, 2) is "18" or "19" or "20" && (l[0] is '~' or '=' or '-' or '^' or '#'))
            || (l.Length == 6 && l[..2] is "18" or "19" or "20" && l[4] == '.')
            || (l.Length == 7 && l.Substring(1, 2) is "18" or "19" or "20" && l[5] == '.')
            )
            select l
        ).ToArray();
        string[] matches = (
            from l
            in results
            where l == year
            || (l.Length == 5 && l.Substring(1, 4) == year && (l[0] is '~' or '=' or '-' or '^' or '#'))
            || (l.Length == 6 && l[..4] == year && l[4] == '.')
            || (l.Length == 7 && l.Substring(1, 4) == year && l[5] == '.')
            select l
        ).ToArray();
        if (!results.Any())
            result = null;
        else
            result = !matches.Any();
        return new(result, results);
    }

    internal static DateTime? GetDateTimeFromName(Models.FileHolder fileHolder)
    {
        DateTime? result = null;
        int length;
        string format;
        string fullFormat;
        StringBuilder value = new();
        const string ticksExample = "##################";
        string[][] dateFormats = new string[][]
        {
            new string[] { string.Empty, "yyyyMMdd_HHmmss", string.Empty },
            new string[] { string.Empty, "yyyyMMddHHmmssfff", string.Empty },
            new string[] { string.Empty, "yyyyMMdd_", ticksExample },
            new string[] { string.Empty, "yyyy-MM-dd_", ticksExample },
            new string[] { string.Empty, "yyyy-MM-dd.", ticksExample },
            new string[] { string.Empty, "yyyy-MM-dd.", $"{ticksExample}.{fileHolder.Length}" },
            new string[] { string.Empty, "yyyy-MM-dd HH.mm.ss", string.Empty },
            new string[] { string.Empty, "yyyyMMdd_HHmmss", "_LLS" },
            new string[] { string.Empty, "yyyyMMdd_HHmmss", "_HDR" },
            new string[] { "WIN_", "yyyyMMdd_HH_mm_ss", "_Pro" },
            new string[] { "IMG_", "yyyyMMdd_HHmmss", string.Empty },
            new string[] { "IMG#####-", "yyyyMMdd-HHmm", string.Empty },
            new string[] { "CameraZOOM-", "yyyyMMddHHmmss", string.Empty },
            new string[] { "VideoCapture_", "yyyyMMdd-HHmmss ", string.Empty }
        };
        foreach (string[] dateFormat in dateFormats)
        {
            _ = value.Clear();
            if (dateFormat.Length != 3)
                throw new Exception();
            fullFormat = string.Join(string.Empty, dateFormat);
            if (fileHolder.NameWithoutExtension.Length != fullFormat.Length)
                continue;
            format = dateFormat[1];
            length = dateFormat[0].Length + dateFormat[1].Length;
            for (int i = dateFormat[0].Length; i < length; i++)
                _ = value.Append(fileHolder.NameWithoutExtension[i]);
            if (value.Length != format.Length)
                continue;
            if (DateTime.TryParseExact(value.ToString(), format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime checkDateTime))
            {
                if (fileHolder.NameWithoutExtension.Length < ticksExample.Length || !long.TryParse(fileHolder.NameWithoutExtension[^ticksExample.Length..], out long ticks))
                    result = checkDateTime;
                else
                    result = new DateTime(ticks);
                break;
            }
        }
        return result;
    }

    internal static List<DateTime> GetDateTimes(DateTime creationTime, DateTime lastWriteTime, DateTime? dateTime, DateTime? dateTimeDigitized, DateTime? dateTimeFromName, DateTime? dateTimeOriginal, DateTime? gpsDateStamp)
    {
        List<DateTime> results = new()
        {
            creationTime,
            lastWriteTime
        };
        if (dateTime.HasValue)
            results.Add(dateTime.Value);
        if (dateTimeDigitized.HasValue)
            results.Add(dateTimeDigitized.Value);
        if (dateTimeFromName.HasValue)
            results.Add(dateTimeFromName.Value);
        if (dateTimeOriginal.HasValue)
            results.Add(dateTimeOriginal.Value);
        if (gpsDateStamp.HasValue)
            results.Add(gpsDateStamp.Value);
        return results;
    }

    internal static DateTime GetDateTime(Models.Property? property)
    {
        DateTime result;
        if (property is null)
            result = DateTime.MinValue;
        else
        {
            List<DateTime> dateTimes = new()
            {
                property.CreationTime,
                property.LastWriteTime
            };
            if (property.DateTime.HasValue)
                dateTimes.Add(property.DateTime.Value);
            if (property.DateTimeDigitized.HasValue)
                dateTimes.Add(property.DateTimeDigitized.Value);
            if (property.DateTimeFromName.HasValue)
                dateTimes.Add(property.DateTimeFromName.Value);
            if (property.DateTimeOriginal.HasValue)
                dateTimes.Add(property.DateTimeOriginal.Value);
            if (property.GPSDateStamp.HasValue)
                dateTimes.Add(property.GPSDateStamp.Value);
            result = dateTimes.Min();
        }
        return result;
    }

    internal static DateTime GetMinimumDateTime(Models.Property? property)
    {
        DateTime result;
        List<DateTime> dateTimes;
        if (property is null)
            result = DateTime.MinValue;
        else
        {
            dateTimes = IProperty.GetDateTimes(property);
            result = dateTimes.Min();
        }
        return result;
    }

    internal static string GetDiffRootDirectory(string diffPropertyDirectory)
    {
        string result = string.Empty;
        string results = "-Results";
        string? checkDirectory = diffPropertyDirectory;
        for (int i = 0; i < int.MaxValue; i++)
        {
            checkDirectory = Path.GetDirectoryName(checkDirectory);
            if (string.IsNullOrEmpty(checkDirectory))
                break;
            if (checkDirectory.EndsWith(results))
            {
                result = checkDirectory[..^results.Length];
                break;
            }
        }
        return result;
    }

    internal static double GetStandardDeviation(IEnumerable<long> values, double average)
    {
        double result = 0;
        if (!values.Any())
            throw new Exception("Collection must have at least one value!");
        double sum = values.Sum(l => (l - average) * (l - average));
        result = Math.Sqrt(sum / values.Count());
        return result;
    }

    private static long GetThreeStandardDeviationHigh(ref List<long> ticksCollection, long min)
    {
        long result;
        ticksCollection = (from l in ticksCollection select l - min).ToList();
        double sum = ticksCollection.Sum();
        double average = sum / ticksCollection.Count;
        double standardDeviation = GetStandardDeviation(ticksCollection, average);
        result = (long)Math.Ceiling(average + min + (standardDeviation * 3));
        return result;
    }

    internal static TimeSpan GetThreeStandardDeviationHigh(int minimum, Models.Container container)
    {
        TimeSpan result;
        DateTime? minimumDateTime;
        List<long> ticksCollection = new();
        foreach (Models.Item item in container.Items)
        {
            if (item.Property is null)
                continue;
            minimumDateTime = GetMinimumDateTime(item.Property);
            if (minimumDateTime is null)
                continue;
            ticksCollection.Add(minimumDateTime.Value.Ticks);
        }
        long threeStandardDeviationHigh;
        long min;
        if (!ticksCollection.Any())
            min = 0;
        else
            min = ticksCollection.Min();
        if (ticksCollection.Count < minimum)
            threeStandardDeviationHigh = long.MaxValue;
        else
            threeStandardDeviationHigh = GetThreeStandardDeviationHigh(ref ticksCollection, min);
        result = new TimeSpan(threeStandardDeviationHigh - min);
        return result;
    }

    internal static (int, List<DateTime>, List<Models.Item>) Get(Models.Container container, TimeSpan threeStandardDeviationHigh, int i)
    {
        List<Models.Item> results = new();
        int j = i;
        long? ticks;
        Models.Item item;
        TimeSpan timeSpan;
        Models.Item nextItem;
        DateTime? minimumDateTime;
        DateTime? nextMinimumDateTime;
        List<DateTime> dateTimes = new();
        for (; j < container.Items.Count; j++)
        {
            ticks = null;
            item = container.Items[j];
            if (item.Property is null)
                continue;
            minimumDateTime = GetMinimumDateTime(item.Property);
            if (minimumDateTime is null)
                continue;
            for (int k = j + 1; k < container.Items.Count; k++)
            {
                nextItem = container.Items[k];
                if (nextItem.Property is null)
                    continue;
                nextMinimumDateTime = GetMinimumDateTime(nextItem.Property);
                if (nextMinimumDateTime is null)
                    continue;
                ticks = nextMinimumDateTime.Value.Ticks;
                break;
            }
            results.Add(item);
            dateTimes.Add(minimumDateTime.Value);
            if (ticks.HasValue)
            {
                timeSpan = new(ticks.Value - minimumDateTime.Value.Ticks);
                if (timeSpan > threeStandardDeviationHigh)
                    break;
            }
        }
        return new(j, dateTimes, results);
    }

    internal static bool Any(Models.Container[] containers)
    {
        bool result = false;
        foreach (Models.Container container in containers)
        {
            if (!container.Items.Any())
                continue;
            if ((from l in container.Items where l.Any() select true).Any())
            {
                result = true;
                break;
            }
        }
        return result;
    }

    internal static bool NameWithoutExtensionIsIdFormat(Models.FileHolder fileHolder)
    {
        bool result;
        if (fileHolder.NameWithoutExtension.Length < 5)
            result = false;
        else
        {
            bool skipOneAllAreNumbers = fileHolder.NameWithoutExtension[1..].All(l => char.IsNumber(l));
            result = (skipOneAllAreNumbers && fileHolder.NameWithoutExtension[0] == '-') || (skipOneAllAreNumbers && char.IsNumber(fileHolder.NameWithoutExtension[0]));
        }
        return result;
    }

#pragma warning disable CA1416

    internal static (DateTime?[], int?, string?) Get(Models.FileHolder fileHolder, bool isIgnoreExtension, bool isValidImageFormatExtension)
    {
        int? id = null;
        string? message = null;
        DateTime? dateTime = null;
        DateTime? gpsDateStamp = null;
        DateTime? dateTimeOriginal = null;
        DateTime? dateTimeDigitized = null;
        if (!isIgnoreExtension && isValidImageFormatExtension)
        {
            try
            {
                byte[] bytes;
                string value;
                DateTime checkDateTime;
                PropertyItem? propertyItem;
                ASCIIEncoding asciiEncoding = new();
                string dateTimeFormat = IProperty.DateTimeFormat();
                using Image image = Image.FromFile(fileHolder.FullName);
                using Bitmap bitmap = new(image);
                Rectangle rectangle = new(0, 0, image.Width, image.Height);
                BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat);
                IntPtr intPtr = bitmapData.Scan0;
                int length = bitmapData.Stride * bitmap.Height;
                bytes = new byte[length];
                Marshal.Copy(intPtr, bytes, 0, length);
                bitmap.UnlockBits(bitmapData);
                id = IProperty.GetDeterministicHashCode(bytes);
                bitmap.Dispose();
                if (image.PropertyIdList.Contains((int)IExif.Tags.DateTime))
                {
                    propertyItem = image.GetPropertyItem((int)IExif.Tags.DateTime);
                    if (propertyItem?.Value is not null)
                    {
                        value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1);
                        if (value.Length > dateTimeFormat.Length)
                            value = value[..dateTimeFormat.Length];
                        if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime))
                            dateTime = checkDateTime;
                    }
                }
                if (image.PropertyIdList.Contains((int)IExif.Tags.DateTimeDigitized))
                {
                    propertyItem = image.GetPropertyItem((int)IExif.Tags.DateTimeDigitized);
                    if (propertyItem?.Value is not null)
                    {
                        value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1);
                        if (value.Length > dateTimeFormat.Length)
                            value = value[..dateTimeFormat.Length];
                        if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime))
                            dateTimeDigitized = checkDateTime;
                    }
                }
                if (image.PropertyIdList.Contains((int)IExif.Tags.DateTimeOriginal))
                {
                    propertyItem = image.GetPropertyItem((int)IExif.Tags.DateTimeOriginal);
                    if (propertyItem?.Value is not null)
                    {
                        value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1);
                        if (value.Length > dateTimeFormat.Length)
                            value = value[..dateTimeFormat.Length];
                        if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime))
                            dateTimeOriginal = checkDateTime;
                    }
                }
                if (image.PropertyIdList.Contains((int)IExif.Tags.GPSDateStamp))
                {
                    propertyItem = image.GetPropertyItem((int)IExif.Tags.GPSDateStamp);
                    if (propertyItem?.Value is not null)
                    {
                        value = asciiEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1);
                        if (value.Length > dateTimeFormat.Length)
                            value = value[..dateTimeFormat.Length];
                        if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime))
                            gpsDateStamp = checkDateTime;
                    }
                }
                image.Dispose();
            }
            catch (Exception)
            {
                message = string.Concat(new StackFrame().GetMethod()?.Name, " <", fileHolder.FullName, ">");
            }
        }
        DateTime?[] dateTimes = new DateTime?[] { fileHolder.LastWriteTime, fileHolder.CreationTime, dateTime, dateTimeDigitized, dateTimeOriginal, gpsDateStamp };
        return new(dateTimes, id, message);
    }

#pragma warning restore CA1416

}