using ShellProgressBar;
using System.Text.Json;
using View_by_Distance.FaceRecognitionDotNet;
using View_by_Distance.Map.Models;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Properties;

namespace View_by_Distance.Distance.Models;

public class MapLogicSupport : Shared.Models.Methods.IMapLogicSupport
{

    private int _Area;
    private int _Days;
    private int _Distance;
    private int _Confidence;

    private readonly int _FaceConfidencePercent;
    private readonly int _FaceDistancePermyriad;
    private readonly int[] _RangeDaysDeltaTolerance;
    private readonly int[] _RangeFaceAreaPermilleTolerance;
    private readonly int _SortingMaximumPerFaceShouldBeHigh;

    public MapLogicSupport(int faceConfidencePercent, int faceDistancePermyriad, int[] rangeDaysDeltaTolerance, int[] rangeFaceAreaPermilleTolerance, int sortingMaximumPerFaceShouldBeHigh)
    {
        _FaceConfidencePercent = faceConfidencePercent;
        _FaceDistancePermyriad = faceDistancePermyriad;
        _RangeDaysDeltaTolerance = rangeDaysDeltaTolerance;
        _RangeFaceAreaPermilleTolerance = rangeFaceAreaPermilleTolerance;
        _SortingMaximumPerFaceShouldBeHigh = sortingMaximumPerFaceShouldBeHigh;
    }

    public static void SaveFaceDistances(Property.Models.Configuration configuration, SortingContainer[] sortingContainers)
    {
        string eDistanceContentCollectionDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(configuration, nameof(E_Distance), "([])");
        if (!Directory.Exists(eDistanceContentCollectionDirectory))
            _ = Directory.CreateDirectory(eDistanceContentCollectionDirectory);
#pragma warning disable
        string[] results = (from l in sortingContainers select l.ToString()).ToArray();
#pragma warning restore
        string eDistanceContentFileName = Path.Combine(eDistanceContentCollectionDirectory, $"{configuration.ResultAllInOne}.tvs");
        File.WriteAllLines(eDistanceContentFileName, results);
    }

    private List<SortingContainer> GetSortingContainers(Configuration mapConfiguration, Face face, FaceDistance faceDistanceEncoding, List<Sorting> sortingCollection, int? useFiltersCounter)
    {
        List<SortingContainer> results = new();
        SortingContainer sortingContainer;
        Sorting[] collection = Shared.Models.Stateless.Methods.ISorting.Sort(sortingCollection);
        double a;
        double b;
        double c;
        double d;
        double faceAreaPermille;
        double faceConfidencePercent;
        double faceDistancePermyriad;
        double rangeDaysDeltaTolerance;
        if (useFiltersCounter is null)
        {
            a = 1f;
            b = 1f;
            c = 1f;
            d = 1f;
        }
        else if (useFiltersCounter.Value < 5)
        {
            a = 1.25f;
            b = 0.8f;
            c = a;
            d = b;
        }
        else if (useFiltersCounter.Value < 9)
        {
            a = 1.5f;
            b = 0.667f;
            c = a;
            d = b;
        }
        else if (useFiltersCounter.Value < 13)
        {
            a = 1.75f;
            b = 0.571f;
            c = a;
            d = b;
        }
        else
        {
            a = 2f;
            b = 0.5f;
            c = a;
            d = b;
        }
        if (useFiltersCounter is null)
        {
            rangeDaysDeltaTolerance = _RangeDaysDeltaTolerance[1];
            faceDistancePermyriad = _FaceDistancePermyriad;
            faceConfidencePercent = _FaceConfidencePercent;
            faceAreaPermille = _RangeFaceAreaPermilleTolerance[1];
        }
        else if (useFiltersCounter.Value is 1 or 5 or 9 or 13)
        {
            rangeDaysDeltaTolerance = _RangeDaysDeltaTolerance[1] * a;
            faceDistancePermyriad = _FaceDistancePermyriad * c;
            faceConfidencePercent = _FaceConfidencePercent * d;
            faceAreaPermille = _RangeFaceAreaPermilleTolerance[1] * d;
        }
        else if (useFiltersCounter.Value is 2 or 6 or 10 or 14)
        {
            rangeDaysDeltaTolerance = _RangeDaysDeltaTolerance[1] * c;
            faceDistancePermyriad = _FaceDistancePermyriad * a;
            faceConfidencePercent = _FaceConfidencePercent * d;
            faceAreaPermille = _RangeFaceAreaPermilleTolerance[1] * d;
        }
        else if (useFiltersCounter.Value is 3 or 7 or 11 or 15)
        {
            rangeDaysDeltaTolerance = _RangeDaysDeltaTolerance[1] * c;
            faceDistancePermyriad = _FaceDistancePermyriad * c;
            faceConfidencePercent = _FaceConfidencePercent * b;
            faceAreaPermille = _RangeFaceAreaPermilleTolerance[1] * d;
        }
        else if (useFiltersCounter.Value is 4 or 8 or 12 or 16)
        {
            rangeDaysDeltaTolerance = _RangeDaysDeltaTolerance[1] * c;
            faceDistancePermyriad = _FaceDistancePermyriad * c;
            faceConfidencePercent = _FaceConfidencePercent * d;
            faceAreaPermille = _RangeFaceAreaPermilleTolerance[1] * b;
        }
        else
        {
            rangeDaysDeltaTolerance = int.MaxValue;
            faceDistancePermyriad = int.MaxValue;
            faceAreaPermille = 0;
            faceConfidencePercent = 0;
        }
        foreach (Sorting sorting in collection)
        {
            if (face.Mapping is null || faceDistanceEncoding.NormalizedRectangle is null)
                throw new NotSupportedException();
            if (!mapConfiguration.SaveSortingWithoutPerson && face.Mapping.MappingFromPerson is null)
                continue;
            if (sorting.DaysDelta > rangeDaysDeltaTolerance)
            {
                _Days += 1;
                continue;
            }
            if (sorting.DistancePermyriad > faceDistancePermyriad)
            {
                _Distance += 1;
                continue;
            }
            if (face.Mapping.MappingFromLocation.ConfidencePercent < faceConfidencePercent)
            {
                _Confidence += 1;
                continue;
            }
            if (face.Mapping.MappingFromLocation.AreaPermille < faceAreaPermille)
            {
                _Area += 1;
                continue;
            }
            sortingContainer = new(face.Mapping, sorting);
            results.Add(sortingContainer);
            if (results.Count >= _SortingMaximumPerFaceShouldBeHigh)
                break;
        }
        return results;
    }

    private static List<Sorting> GetSortingCollection(MapLogic mapLogic, List<FaceDistance> faceDistanceEncodings, int i, FaceDistance faceDistanceEncoding)
    {
        List<Sorting> results;
        List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
        results = mapLogic.GetSortingCollection(i, faceDistanceEncoding, faceDistanceLengths);
        return results;
    }

    private static FaceDistanceContainer[] GetFaceDistanceContainers(List<Face> distinctFilteredFaces)
    {
        FaceDistanceContainer[] results;
        FaceDistance faceDistance;
        FaceDistanceContainer faceDistanceContainer;
        List<FaceDistanceContainer> collection = new();
        foreach (Face face in distinctFilteredFaces)
        {
            if (face.Mapping is null)
                throw new NotSupportedException();
            if (face.FaceDistance?.Encoding is not FaceRecognitionDotNet.FaceEncoding faceEncoding)
                continue;
            faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedRectangle);
            faceDistanceContainer = new(face, faceDistance);
            collection.Add(faceDistanceContainer);
        }
        results = collection.ToArray();
        return results;
    }

    private static List<FaceDistance> GetFaceDistanceEncodings(FaceDistanceContainer[] faceDistanceContainers, List<FaceDistanceContainer> missingFaceDistanceContainers)
    {
        List<FaceDistance> faceDistanceEncodings = new();
        foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers)
        {
            if (faceDistanceContainer.FaceDistance.Encoding is null)
                continue;
            faceDistanceEncodings.Add(faceDistanceContainer.FaceDistance);
        }
        foreach (FaceDistanceContainer faceDistanceContainer in missingFaceDistanceContainers)
        {
            if (faceDistanceContainer.FaceDistance.Encoding is null)
                continue;
            faceDistanceEncodings.Add(faceDistanceContainer.FaceDistance);
        }
        return faceDistanceEncodings;
    }

    public SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, Configuration mapConfiguration, long ticks, MapLogic mapLogic, List<Face> distinctFilteredFaces, List<FaceDistanceContainer> missingFaceDistanceContainers, int? useFiltersCounter)
    {
        SortingContainer[] results;
        List<SortingContainer> collection = new();
        int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
        ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
        FaceDistanceContainer[] faceDistanceContainers = GetFaceDistanceContainers(distinctFilteredFaces);
        List<FaceDistance> faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers, missingFaceDistanceContainers);
        string message = $") {faceDistanceContainers.Length:000} Get Sorting Containers Then Set Face Mapping Sorting Collection - {totalSeconds} total second(s)";
        ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
        using ProgressBar progressBar = new(faceDistanceContainers.Length, message, options);
        _ = Parallel.For(0, faceDistanceContainers.Length, parallelOptions, (i, state) =>
        {
            progressBar.Tick();
            FaceDistance faceDistanceEncoding = faceDistanceContainers[i].FaceDistance;
            if (mapLogic.Used(faceDistanceEncoding))
                return;
            Face face = faceDistanceContainers[i].Face;
            if (face.Mapping is null)
                throw new NotSupportedException();
            List<Sorting> sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, i, faceDistanceEncoding);
            if (!sortingCollection.Any())
                return;
            List<SortingContainer> sortingContainers = GetSortingContainers(mapConfiguration, face, faceDistanceEncoding, sortingCollection, useFiltersCounter);
            if (sortingContainers.Any())
            {
                lock (collection)
                    collection.AddRange(sortingContainers);
            }
        });
        if (!collection.Any())
            results = Array.Empty<SortingContainer>();
        else
            results = Shared.Models.Stateless.Methods.ISortingContainer.Sort(collection);
        return results;
    }

    public static Mapping[] GetSelectedMappingCollection(List<Face> distinctFilteredFaces)
    {
        Mapping[] results;
        IEnumerable<Mapping> collection = from l in distinctFilteredFaces orderby l.Mapping?.MappingFromItem.Id select l.Mapping;
        results = (from l in collection where l is not null select l).ToArray();
        return results;
    }

    public static void SetFaceDistances(int maxDegreeOfParallelism, long ticks, List<Face> distinctFilteredFaces)
    {
        int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
        ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
        string message = $") {distinctFilteredFaces.Count:000} Load Face Encoding - {totalSeconds} total second(s)";
        ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
        using ProgressBar progressBar = new(distinctFilteredFaces.Count, message, options);
        _ = Parallel.For(0, distinctFilteredFaces.Count, parallelOptions, (i, state) =>
        {
            Face face = distinctFilteredFaces[i];
            if (face.FaceEncoding is null || face.Mapping is null)
                throw new NotSupportedException();
            if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding)
                return;
            progressBar.Tick();
            faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding);
            FaceDistance faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedRectangle);
            lock (face)
                face.SetFaceDistance(faceDistance);
        });
    }

    void Shared.Models.Methods.IMapLogicSupport.SavePossiblyNewPersonContainers(IPropertyConfiguration propertyConfiguration, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, string? a2PeopleSingletonDirectory, Dictionary<long, PersonContainer> personKeyToPersonContainer, List<(string[], PersonContainer)> possiblyNewPersonDisplayDirectoryNamesAndPersonContainer)
    {
        string json;
        string[] files;
        string checkFile;
        string[] segments;
        const int zero = 0;
        char personCharacter;
        string personKeyFormatted;
        string personDisplayDirectory;
        PersonBirthday personBirthday;
        string personDisplayDirectoryName;
        string checkPersonDisplayDirectory;
        string checkPersonKeyFormattedDirectory;
        JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true };
        foreach ((string[] personDisplayDirectoryNames, PersonContainer personContainer) in possiblyNewPersonDisplayDirectoryNamesAndPersonContainer)
        {
            if (a2PeopleSingletonDirectory is null || personContainer.Key is null || personContainer.Birthdays is null || !personContainer.Birthdays.Any())
                continue;
            personBirthday = personContainer.Birthdays[zero];
            personDisplayDirectoryName = personDisplayDirectoryNames[^1];
            personDisplayDirectory = Path.Combine(personDisplayDirectoryNames);
            personKeyFormatted = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthdayFormat, personBirthday);
            segments = personDisplayDirectoryName.Split(personCharacters);
            if (segments.Length != 2)
                personCharacter = '_';
            else
                personCharacter = personDisplayDirectoryName[segments[zero].Length];
            checkPersonDisplayDirectory = Path.Combine(a2PeopleSingletonDirectory, personCharacter.ToString(), personDisplayDirectoryName);
            checkPersonKeyFormattedDirectory = Path.Combine(checkPersonDisplayDirectory, personKeyFormatted);
            if (Directory.Exists(checkPersonKeyFormattedDirectory))
                continue;
            _ = Directory.CreateDirectory(checkPersonKeyFormattedDirectory);
            checkFile = Path.Combine(checkPersonKeyFormattedDirectory, $"{personKeyFormatted}.json");
            json = JsonSerializer.Serialize(personContainer.Person, jsonSerializerOptions);
            _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: false, compareBeforeWrite: true);
            if (!Directory.Exists(personDisplayDirectory))
                continue;
            files = Directory.GetFiles(personDisplayDirectory, $"*{facesFileNameExtension}", SearchOption.TopDirectoryOnly);
            foreach (string file in files)
            {
                checkFile = Path.Combine(checkPersonDisplayDirectory, Path.GetFileName(file));
                if (File.Exists(checkFile))
                    continue;
                File.Copy(files[0], checkFile);
                break;
            }
        }
    }

    public static Dictionary<int, Dictionary<int, Mapping>> GetIdToNormalizedRectangleToFace(Mapping[] mappingCollection)
    {
        Dictionary<int, Dictionary<int, Mapping>> results = new();
        Dictionary<int, Mapping>? keyValuePairs;
        foreach (Mapping mapping in mappingCollection)
        {
            if (!results.TryGetValue(mapping.MappingFromItem.Id, out keyValuePairs))
            {
                results.Add(mapping.MappingFromItem.Id, new());
                if (!results.TryGetValue(mapping.MappingFromItem.Id, out keyValuePairs))
                    throw new Exception();
            }
            if (keyValuePairs.ContainsKey(mapping.MappingFromLocation.NormalizedRectangle))
                continue;
            keyValuePairs.Add(mapping.MappingFromLocation.NormalizedRectangle, mapping);
        }
        return results;
    }

    string Shared.Models.Methods.IMapLogicSupport.GetCounts()
    {
        string result;
        List<(int Value, string Name)> results = new()
        {
            new(_Area, nameof(_Area)),
            new(_Confidence, nameof(_Confidence)),
            new(_Days, nameof(_Days)),
            new(_Distance, nameof(_Distance))
        };
        result = string.Join(' ', from l in results orderby l.Value descending select $"{l.Name}_{l.Value};");
        return result;
    }

}