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 (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.Length == 0)
            result = null;
        else
            result = matches.Length == 0;
        return new(result, results);
    }

    internal static (bool?, string[]) IsWrongYear(Models.FileHolder fileHolder, DateTime? dateTimeOriginal, List<DateTime> dateTimes)
    {
        string[] results = Array.Empty<string>();
        bool? result = null;
        string year;
        string directoryName;
        string[] directorySegments;
        List<DateTime> collection = new();
        string? check = Path.GetFullPath(fileHolder.FullName);
        string? pathRoot = Path.GetPathRoot(fileHolder.FullName);
        if (string.IsNullOrEmpty(pathRoot))
            throw new Exception();
        if (dateTimeOriginal is not null)
            collection.Add(dateTimeOriginal.Value);
        else
        {
            foreach (DateTime dateTime in dateTimes)
                collection.Add(dateTime);
        }
        foreach (DateTime dateTime in collection)
        {
            year = dateTime.ToString("yyyy");
            for (int i = 0; i < int.MaxValue; i++)
            {
                check = Path.GetDirectoryName(check);
                if (string.IsNullOrEmpty(check) || check == pathRoot)
                    break;
                directoryName = Path.GetFileName(check);
                directorySegments = directoryName.Split(' ');
                (result, results) = IsWrongYear(directorySegments, year);
                if (result is not null)
                    break;
            }
            if (result is not null && !result.Value)
                break;
        }
        return new(result, results);
    }

    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.Count == 0)
            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.Count == 0)
                continue;
            if ((from l in container.Items where l.Any() select true).Any())
            {
                result = true;
                break;
            }
        }
        return result;
    }

    internal static bool NameWithoutExtensionIsIdFormat(string fileNameWithoutExtension)
    {
        bool result;
        int intMinValueLength = int.MinValue.ToString().Length;
        if (fileNameWithoutExtension.Length < 5 || fileNameWithoutExtension.Length > intMinValueLength)
            result = false;
        else
        {
            bool skipOneAllAreNumbers = fileNameWithoutExtension[1..].All(l => char.IsNumber(l));
            result = (skipOneAllAreNumbers && fileNameWithoutExtension[0] == '-') || (skipOneAllAreNumbers && char.IsNumber(fileNameWithoutExtension[0]));
        }
        return result;
    }

}