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 E_Distance : Shared.Models.Methods.IFaceDistance
{

    private readonly Serilog.ILogger? _Log;
    private readonly string _ResultAllInOne;
    private readonly int _FaceDistancePermyriad;
    private readonly double _FaceDistanceTolerance;
    private readonly int _SortingDaysDeltaTolerance;
    private readonly bool _DistanceMoveUnableToMatch;
    private readonly int _DistancePixelDistanceTolerance;
    private readonly double _FaceDistanceMinimumConfidence;
    private readonly int _FaceDistanceAreaPermilleTolerance;
    private readonly int _SortingMaximumPerFaceShouldBeHigh;

    public E_Distance(bool distanceMoveUnableToMatch, int distancePixelDistanceTolerance, int faceDistanceAreaPermilleTolerance, double faceDistanceMinimumConfidence, int faceDistancePermyriad, double faceDistanceTolerance, string resultAllInOne, int sortingDaysDeltaTolerance, int sortingMaximumPerFaceShouldBeHigh)
    {
        _ResultAllInOne = resultAllInOne;
        _Log = Serilog.Log.ForContext<E_Distance>();
        _FaceDistancePermyriad = faceDistancePermyriad;
        _FaceDistanceTolerance = faceDistanceTolerance;
        _DistanceMoveUnableToMatch = distanceMoveUnableToMatch;
        _SortingDaysDeltaTolerance = sortingDaysDeltaTolerance;
        _FaceDistanceMinimumConfidence = faceDistanceMinimumConfidence;
        _DistancePixelDistanceTolerance = distancePixelDistanceTolerance;
        _FaceDistanceAreaPermilleTolerance = faceDistanceAreaPermilleTolerance;
        _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 static List<Sorting> GetSortingCollection(MapLogic mapLogic, List<FaceDistance> faceDistanceEncodings, int faceDistanceContainersLength, int i, FaceDistance faceDistanceEncoding)
    {
        List<Sorting> results;
        List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
        if (faceDistanceLengths.Count != faceDistanceContainersLength)
            throw new NotSupportedException();
        results = mapLogic.GetSortingCollection(i, faceDistanceEncoding, faceDistanceLengths);
        return results;
    }

    private List<SortingContainer> GetSortingContainers(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 faceDistancePermyriad;
        double sortingDaysDeltaTolerance;
        double faceDistanceMinimumConfidence;
        double faceDistanceAreaPermilleTolerance;
        if (useFiltersCounter is null)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance;
            faceDistancePermyriad = _FaceDistancePermyriad;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence;
        }
        else if (useFiltersCounter.Value == 1)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5;
            faceDistancePermyriad = _FaceDistancePermyriad;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence;
        }
        else if (useFiltersCounter.Value == 2)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance;
            faceDistancePermyriad = _FaceDistancePermyriad * 1.5;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence;
        }
        else if (useFiltersCounter.Value == 3)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance;
            faceDistancePermyriad = _FaceDistancePermyriad;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5;
        }
        else if (useFiltersCounter.Value == 4)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2;
            faceDistancePermyriad = _FaceDistancePermyriad * 1.5;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5;
        }
        else if (useFiltersCounter.Value == 5)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5;
            faceDistancePermyriad = _FaceDistancePermyriad * 2;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5;
        }
        else if (useFiltersCounter.Value == 6)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5;
            faceDistancePermyriad = _FaceDistancePermyriad * 1.5;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25;
        }
        else if (useFiltersCounter.Value == 7)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2.5;
            faceDistancePermyriad = _FaceDistancePermyriad * 2;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25;
        }
        else if (useFiltersCounter.Value == 8)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2;
            faceDistancePermyriad = _FaceDistancePermyriad * 2.5;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25;
        }
        else if (useFiltersCounter.Value == 9)
        {
            sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2;
            faceDistancePermyriad = _FaceDistancePermyriad * 2;
            faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance;
            faceDistanceMinimumConfidence = 0;
        }
        else
        {
            sortingDaysDeltaTolerance = int.MaxValue;
            faceDistancePermyriad = int.MaxValue;
            faceDistanceAreaPermilleTolerance = 0;
            faceDistanceMinimumConfidence = 0;
        }
        foreach (Sorting sorting in collection)
        {
            if (face.Mapping is null || faceDistanceEncoding.NormalizedPixelPercentage is null)
                throw new NotSupportedException();
            if (sorting.DaysDelta > sortingDaysDeltaTolerance || sorting.DistancePermyriad > faceDistancePermyriad || face.Mapping.MappingFromLocation.Confidence < faceDistanceMinimumConfidence || face.Mapping.MappingFromLocation.AreaPermille < faceDistanceAreaPermilleTolerance)
                continue;
            sortingContainer = new(face, sorting);
            results.Add(sortingContainer);
            if (results.Count >= _SortingMaximumPerFaceShouldBeHigh)
                break;
        }
        return results;
    }

    private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(List<FaceDistanceContainer> collection)
    {
        FaceDistanceContainer[] results;
        results = (from l in collection orderby l.FaceDistance.Encoding is not null select l).ToArray();
        if (results.Any() && results[0].FaceDistance.Encoding is null)
            throw new Exception("Sorting failed!");
        return results;
    }

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

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

    public SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, long ticks, MapLogic mapLogic, Face[] selectedFilteredFaces, 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 = GetOrderedFaceDistanceContainers(selectedFilteredFaces);
        List<FaceDistance> faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers);
        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();
            Face face = faceDistanceContainers[i].Face;
            if (face.Mapping is null)
                throw new NotSupportedException();
            FaceDistance faceDistanceEncoding = faceDistanceContainers[i].FaceDistance;
            if (mapLogic.Used(faceDistanceEncoding))
                return;
            List<Sorting> sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, faceDistanceContainers.Length, i, faceDistanceEncoding);
            if (!sortingCollection.Any())
                return;
            List<SortingContainer> sortingContainers = GetSortingContainers(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 Face[] GetSelectedFilteredFaces(List<Face> distinctFilteredFaces)
    {
        Face[] results = (from l in distinctFilteredFaces orderby l.Mapping is not null, l.Mapping?.MappingFromItem.MinimumDateTime descending select l).ToArray();
        return results;
    }

    public static void SetFaceDistances(int maxDegreeOfParallelism, long ticks, Face[] selectedFilteredFaces)
    {
        int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
        ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
        string message = $") {selectedFilteredFaces.Length:000} Load Face Encoding - {totalSeconds} total second(s)";
        ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
        using ProgressBar progressBar = new(selectedFilteredFaces.Length, message, options);
        _ = Parallel.For(0, selectedFilteredFaces.Length, parallelOptions, (i, state) =>
        {
            Face face = selectedFilteredFaces[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.Confidence, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedPixelPercentage);
            lock (face)
                face.SetFaceDistance(faceDistance);
        });
    }

    void Shared.Models.Methods.IFaceDistance.SavePossiblyNewPersonContainers(IPropertyConfiguration propertyConfiguration, string personBirthdayFormat, string facesFileNameExtension, string? a2PeopleSingletonDirectory, Dictionary<long, PersonContainer> personKeyToPersonContainer, List<(string[], PersonContainer)> possiblyNewPersonDisplayDirectoryNamesAndPersonContainer)
    {
        char @char;
        string json;
        string[] files;
        string checkFile;
        string[] segments;
        const int zero = 0;
        string personKeyFormatted;
        string personDisplayDirectory;
        PersonBirthday personBirthday;
        string personDisplayDirectoryName;
        string checkPersonDisplayDirectory;
        string checkPersonKeyFormattedDirectory;
        char[] chars = Shared.Models.Stateless.Methods.IAge.GetChars();
        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(chars);
            if (segments.Length != 2)
                @char = '_';
            else
                @char = personDisplayDirectoryName[segments[zero].Length];
            checkPersonDisplayDirectory = Path.Combine(a2PeopleSingletonDirectory, @char.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;
            }
        }
    }

    private void MoveUnableToMatch(string eDistanceContentDirectory, string file)
    {
        bool result;
        string? fileName = Path.GetFileName(file);
        string? directoryName = Path.GetDirectoryName(file);
        if (fileName is null || directoryName is null)
            result = false;
        else
        {
            if (string.IsNullOrEmpty(directoryName) || string.IsNullOrEmpty(directoryName) || !directoryName.Contains(eDistanceContentDirectory))
                result = false;
            else
            {
                List<string> directoryNames = new();
                string? checkDirectoryName = directoryName;
                for (int i = 0; i < int.MaxValue; i++)
                {
                    if (string.IsNullOrEmpty(checkDirectoryName))
                        continue;
                    directoryNames.Add(Path.GetFileName(checkDirectoryName));
                    checkDirectoryName = Path.GetDirectoryName(checkDirectoryName);
                    if (string.IsNullOrEmpty(checkDirectoryName))
                        continue;
                    if (checkDirectoryName == eDistanceContentDirectory)
                        break;
                }
                if (string.IsNullOrEmpty(checkDirectoryName) || !directoryNames.Any() || !long.TryParse(directoryNames[^1][1..^1], out long directoryTicks))
                {
                    result = false;
                    File.Delete(file);
                }
                else
                {
                    checkDirectoryName = Path.Combine(checkDirectoryName, $"({directoryTicks}{_FaceDistanceTolerance.ToString()[1..]})");
                    for (int i = directoryNames.Count - 1 - 1; i > -1; i--)
                        checkDirectoryName = Path.Combine(checkDirectoryName, directoryNames[i]);
                    if (!Directory.Exists(checkDirectoryName))
                        _ = Directory.CreateDirectory(checkDirectoryName);
                    File.Move(file, Path.Combine(checkDirectoryName, fileName));
                    result = true;
                }
            }
        }
        if (result)
        { }
    }

    private static string[] GetMatchingDuplicates(string[] mappedFaceFiles, List<string> duplicateMappedFaceFiles, string mappedFaceFile)
    {
        string[] results;
        string checkFile;
        FileInfo fileInfo = new(mappedFaceFile);
        List<(long Length, string FullName)> collection = new();
        if (fileInfo.Exists)
            collection.Add(new(fileInfo.Length, fileInfo.FullName));
        string fileName = Path.GetFileName(mappedFaceFile);
        foreach (string file in mappedFaceFiles)
        {
            if (duplicateMappedFaceFiles.Contains(file))
                continue;
            if (file == mappedFaceFile || !file.EndsWith(fileName))
                continue;
            fileInfo = new(file);
            if (!fileInfo.Exists)
                continue;
            collection.Add(new(fileInfo.Length, fileInfo.FullName));
        }
        collection = collection.OrderBy(l => l.Length).ToList();
        for (int i = 0; i < collection.Count - 1; i++)
        {
            checkFile = string.Concat(collection[i].FullName, ".dup");
            if (File.Exists(checkFile))
                continue;
            File.Move(collection[i].FullName, checkFile);
        }
        results = (from l in collection select l.FullName).ToArray();
        return results;
    }

    public static string? GetFaceEncoding(string file)
    {
        string? result;
        List<string> results = new();
        const string comment = "Comment: ";
        if (File.Exists(file))
        {
            IReadOnlyList<MetadataExtractor.Directory> directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file);
            foreach (MetadataExtractor.Directory directory in directories)
            {
                if (directory.Name != "PNG-tEXt")
                    continue;
                foreach (MetadataExtractor.Tag tag in directory.Tags)
                {
                    if (tag.Name != "Textual Data" || string.IsNullOrEmpty(tag.Description))
                        continue;
                    if (!tag.Description.StartsWith(comment))
                        continue;
                    results.Add(tag.Description);
                }
            }
        }
        result = results.Any() ? results[0][comment.Length..] : null;
        return result;
    }

    private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(MappingFromItem mappingFromItem, List<Face> faces)
    {
        FaceDistanceContainer[] results;
        FaceDistance faceDistance;
        int normalizedPixelPercentage;
        FaceDistanceContainer faceDistanceContainer;
        List<FaceDistanceContainer> collection = new();
        foreach (Face face in faces)
        {
            if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
                throw new NotSupportedException();
            normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
            if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding)
                faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage);
            else
            {
                faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding);
                faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage);
                lock (faces)
                    face.SetFaceDistance(faceDistance);
            }
            faceDistanceContainer = new(face, faceDistance);
            collection.Add(faceDistanceContainer);
        }
        results = GetOrderedFaceDistanceContainers(collection);
        return results;
    }

    private List<(Face Face, double Length)> GetValues(MappingFromItem mappingFromItem, List<Face> faces, string json)
    {
        List<(Face Face, double Length)> results = new();
        Face face;
        FaceDistance faceDistanceLength;
        Shared.Models.FaceEncoding? modelsFaceEncoding = JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
        if (modelsFaceEncoding is null)
            throw new NotSupportedException();
        FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding);
        FaceDistance faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding);
        FaceDistanceContainer[] faceDistanceContainers = GetOrderedFaceDistanceContainers(mappingFromItem, faces);
        int faceDistanceContainersLength = faceDistanceContainers.Length;
        if (faceDistanceContainersLength != faces.Count)
            throw new NotSupportedException();
        List<FaceDistance> faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers);
        if (faceDistanceEncodings.Count != faces.Count)
            throw new NotSupportedException();
        List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
        if (faceDistanceLengths.Count != faceDistanceContainersLength)
            throw new NotSupportedException();
        for (int i = 0; i < faces.Count; i++)
        {
            face = faces[i];
            faceDistanceLength = faceDistanceLengths[i];
            if (faceDistanceLength.Length is null)
                throw new NotSupportedException();
            if (faceDistanceLength.Length.Value > _FaceDistanceTolerance)
                continue;
            results.Add(new(face, faceDistanceLength.Length.Value));
        }
        return results;
    }

    private Face[] GetMatchingFaces(MappingFromItem mappingFromItem, List<Face> faces, string json)
    {
        Face[] results;
        List<(Face Face, double Length)> collection = GetValues(mappingFromItem, faces, json);
        if (!collection.Any())
            results = Array.Empty<Face>();
        else
            results = (from l in collection orderby l.Length select l.Face).Take(1).ToArray();
        return results;
    }

    private static Face[] GetMatchingFaces(int pixelDistanceTolerance, List<Face> faces)
    {
        Face[] results;
        int? x;
        int? y;
        double distance;
        double center = 2f;
        double xCenterValue;
        double yCenterValue;
        int normalizedPixelPercentage;
        string normalizedPixelPercentagePadded;
        List<(double Order, Face Face)> collection = new();
        foreach (Face face in faces)
        {
            if (face.Location is null || face.OutputResolution is null)
                throw new NotSupportedException();
            xCenterValue = (face.Location.Left + face.Location.Right) / center;
            yCenterValue = (face.Location.Top + face.Location.Bottom) / center;
            if (xCenterValue < face.Location.Left || xCenterValue > face.Location.Right)
                throw new Exception();
            if (yCenterValue < face.Location.Top || yCenterValue > face.Location.Bottom)
                throw new Exception();
            normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
            normalizedPixelPercentagePadded = Shared.Models.Stateless.Methods.ILocation.GetLeftPadded(Shared.Models.Stateless.ILocation.Digits, normalizedPixelPercentage);
            (x, y) = Shared.Models.Stateless.Methods.ILocation.GetXY(Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution.Width, face.OutputResolution.Height, normalizedPixelPercentagePadded);
            if (x is null || y is null)
                throw new NotSupportedException();
            distance = Math.Sqrt(Math.Pow(xCenterValue - x.Value, 2) + Math.Pow(yCenterValue - y.Value, 2));
            collection.Add(new(distance, face));
        }
        if (!collection.Any())
            results = Array.Empty<Face>();
        else
            results = (from l in collection orderby l.Order where l.Order < pixelDistanceTolerance select l.Face).Take(1).ToArray();
        return results;
    }

    private static List<Face> GetMatchingFaces(List<Face> faces, string? json)
    {
        List<Face> results = new();
        string check;
        foreach (Face face in faces)
        {
            if (json is null || face.FaceEncoding is null)
                continue;
            if (!json.Contains(face.FaceEncoding.RawEncoding[0].ToString()))
                continue;
            check = JsonSerializer.Serialize(face.FaceEncoding);
            if (check != json)
                continue;
            results.Add(face);
        }
        return results;
    }

    private static FileInfo? CheckFileThenGetFileInfo(string facesFileNameExtension, MappingFromItem mappingFromItem, string mappedFaceFile, List<Face> checkFaces)
    {
        FileInfo? result = null;
        string checkFile;
        string? mappedFaceDirectory;
        string deterministicHashCodeKey;
        foreach (Face face in checkFaces)
        {
            if (checkFaces.Count != 1)
                break;
            if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
                throw new NotSupportedException();
            mappedFaceDirectory = Path.GetDirectoryName(mappedFaceFile);
            if (mappedFaceDirectory is null)
                throw new NotSupportedException();
            deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
            checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{facesFileNameExtension}");
            if (checkFile == mappedFaceFile)
                continue;
            result = new FileInfo(checkFile);
            if (!result.Exists)
                continue;
            File.Delete(result.FullName);
            result = null;
        }
        return result;
    }

    private static List<Face> GetMatchingFaces(List<Face> faces, string[] mappedFaceFiles, List<int> debugChecks, int normalizedPixelPercentageValue, List<int> normalizedPixelPercentages, List<string> duplicateMappedFaceFiles, string mappedFaceFile)
    {
        List<Face> results = new();
        bool check;
        int normalizedPixelPercentage;
        foreach (Face face in faces)
        {
            if (face.Location is null || face.OutputResolution is null)
                throw new NotSupportedException();
            normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
            debugChecks.Add(normalizedPixelPercentage);
            if (normalizedPixelPercentage != normalizedPixelPercentageValue)
                continue;
            if (normalizedPixelPercentages.Contains(normalizedPixelPercentage))
            {
                duplicateMappedFaceFiles.AddRange(GetMatchingDuplicates(mappedFaceFiles, duplicateMappedFaceFiles, mappedFaceFile));
                continue;
            }
            check = true;
            results.Add(face);
            if (!check)
                debugChecks.Add(normalizedPixelPercentage);
        }
        return results;
    }

    public (int, int, int) GetUnableToMatchCountAndRenameMatches(string facesFileNameExtension, string? eDistanceContentDirectory, MappingFromItem mappingFromItem, List<Face> faces, List<(string MappedFaceFile, int normalizedPixelPercentage)> collection)
    {
        int result = 0;
        string? json;
        int renamed = 0;
        FileInfo? fileInfo;
        List<Face> checkFaces = new();
        List<int> debugChecks = new();
        List<int> normalizedPixelPercentages = new();
        List<string> duplicateMappedFaceFiles = new();
        string[] mappedFaceFiles = (from l in collection select l.MappedFaceFile).ToArray();
        foreach ((string mappedFaceFile, int normalizedPixelPercentage) in collection)
        {
            if (duplicateMappedFaceFiles.Contains(mappedFaceFile))
                continue;
            json = null;
            checkFaces.Clear();
            debugChecks.Clear();
            checkFaces.AddRange(GetMatchingFaces(faces, mappedFaceFiles, debugChecks, normalizedPixelPercentage, normalizedPixelPercentages, duplicateMappedFaceFiles, mappedFaceFile));
            if (checkFaces.Count != 1)
            {
                checkFaces.Clear();
                json = GetFaceEncoding(mappedFaceFile);
                if (json is null)
                {
                    result++;
                    if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch)
                        MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile);
                    continue;
                }
                checkFaces.AddRange(GetMatchingFaces(faces, json));
            }
            if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json))
            {
                checkFaces.Clear();
                if (json is null)
                    throw new NotSupportedException();
                checkFaces.AddRange(GetMatchingFaces(mappingFromItem, faces, json));
            }
            if (checkFaces.Count != 1 && _DistancePixelDistanceTolerance > 0)
            {
                checkFaces.Clear();
                checkFaces.AddRange(GetMatchingFaces(_DistancePixelDistanceTolerance, faces));
            }
            if (!checkFaces.Any() && faces.Count == 1)
                checkFaces.AddRange(faces);
            if (!checkFaces.Any())
            {
                result++;
                if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch)
                    MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile);
                continue;
            }
            if (checkFaces.Count != 1)
            {
                result++;
                if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch)
                    MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile);
                continue;
            }
            normalizedPixelPercentages.Add(normalizedPixelPercentage);
            fileInfo = CheckFileThenGetFileInfo(facesFileNameExtension, mappingFromItem, mappedFaceFile, checkFaces);
            if (fileInfo is null)
                continue;
            File.Move(mappedFaceFile, fileInfo.FullName);
            renamed++;
        }
        if (duplicateMappedFaceFiles.Any())
        {
            duplicateMappedFaceFiles.Sort();
        }
        return new(result, duplicateMappedFaceFiles.Count, renamed);
    }

}