using System.Drawing;

namespace View_by_Distance.Shared.Models.Stateless.Methods;

internal abstract class Location
{

    internal static bool Check(int bottom, int left, int right, int top, int zCount, bool throwException)
    {
        bool result = true;
        if (left < 0)
            result = false;
        if (right < 0)
            result = false;
        if (right < left)
            result = false;
        if (top < 0)
            result = false;
        if (bottom < 0)
            result = false;
        if (bottom < top)
            result = false;
        if (zCount < 0)
            result = false;
        if (throwException && !result)
            throw new Exception();
        return result;
    }

    internal static bool Check(int bottom, int height, int left, int right, int top, int width, int zCount, bool throwException)
    {
        bool result = true;
        if (bottom > height)
            result = false;
        if (left > width)
            result = false;
        if (right > width)
            result = false;
        if (top > height)
            result = false;
        if (zCount < 0)
            result = false;
        if (result)
            result = Check(bottom, left, right, top, zCount, throwException);
        if (throwException && !result)
            throw new Exception();
        return result;
    }

    internal static string GetLeftPadded(int locationDigits, string value)
    {
        string result;
        if (value.Length == locationDigits)
            result = value;
        else if (value.Length > locationDigits)
            result = value[..locationDigits];
        else
            result = value.PadLeft(locationDigits, '0');
        return result;
    }

    internal static (decimal?, decimal?, decimal?, decimal?) GetHeightLeftTopWidth(int bottom, int height, int left, int right, int top, int width, int zCount)
    {
        (decimal?, decimal?, decimal?, decimal?) result;
        bool verified = Check(bottom, height, left, right, top, width, zCount, throwException: false);
        decimal t = top;
        decimal l = left;
        decimal w = right - l;
        decimal h = bottom - t;
        decimal xHeightPercentageFactored = h / height;
        decimal xLeftPercentageFactored = l / width;
        decimal xTopPercentageFactored = t / height;
        decimal xWidthPercentageFactored = w / width;
        if (!verified)
            result = new(null, null, null, null);
        else
            result = new(xHeightPercentageFactored, xLeftPercentageFactored, xTopPercentageFactored, xWidthPercentageFactored);
        return result;
    }

    internal static int GetWholePercentages(int bottom, int height, int left, int locationDigits, int right, int top, int width, int zCount)
    {
        int result;
        string check;
        bool verified = Check(bottom, height, left, right, top, width, zCount, throwException: false);
        int checksum = left > top ? 4 : 8;
        if (!verified)
            check = string.Concat(checksum, new string('0', locationDigits - 1));
        else
        {
            decimal factor = 100;
            int factorMinusOne = (int)factor - 1;
            int length = (locationDigits - 1) / 4;
            decimal x = left / (decimal)width * factor;
            decimal y = top / (decimal)height * factor;
            decimal w = (right - left) / (decimal)width * factor;
            decimal h = (bottom - top) / (decimal)height * factor;
            string xPadded = x < factor ? ILocation.GetLeftPadded(length, (int)x) : ILocation.GetLeftPadded(length, factorMinusOne);
            string yPadded = y < factor ? ILocation.GetLeftPadded(length, (int)y) : ILocation.GetLeftPadded(length, factorMinusOne);
            string widthPadded = w < factor ? ILocation.GetLeftPadded(length, (int)w) : ILocation.GetLeftPadded(length, factorMinusOne);
            string heightPadded = h < factor ? ILocation.GetLeftPadded(length, (int)h) : ILocation.GetLeftPadded(length, factorMinusOne);
            check = string.Concat(checksum, xPadded, yPadded, widthPadded, heightPadded);
        }
        long value = long.Parse(check);
        if (value > int.MaxValue)
            throw new Exception();
        result = (int)value;
        return result;
    }

    internal static int GetWholePercentages(int height, Models.Location location, int locationDigits, int width)
    {
        int result = GetWholePercentages(location.Bottom, height, location.Left, locationDigits, location.Right, location.Top, width, zCount: 1);
        return result;
    }

    internal static int GetConfidencePercent(int faceConfidencePercent, float[] rangeFaceConfidence, double confidence)
    {
        int result = (int)(confidence / rangeFaceConfidence[1] * faceConfidencePercent);
        return result;
    }

    internal static RectangleF? GetPercentagesRectangle(int locationDigits, string wholePercentages)
    {
        RectangleF? result;
        int length = (locationDigits - 1) / 4;
        string[] segments = new string[]
        {
            wholePercentages[..1],
            wholePercentages.Substring(1, length),
            wholePercentages.Substring(3, length),
            wholePercentages.Substring(5, length),
            wholePercentages.Substring(7, length)
        };
        if (string.Join(string.Empty, segments) != wholePercentages)
            result = null;
        else
        {
            if (!int.TryParse(segments[1], out int xWholePercent) || !int.TryParse(segments[2], out int yWholePercent) || !int.TryParse(segments[3], out int wWholePercent) || !int.TryParse(segments[4], out int hWholePercent))
                result = null;
            else
            {
                float factor = 100;
                result = new(xWholePercent / factor, yWholePercent / factor, wWholePercent / factor, hWholePercent / factor);
            }
        }
        return result;
    }

    internal static Rectangle? GetRectangle(int locationDigits, Models.OutputResolution outputResolution, string wholePercentages)
    {
        Rectangle? result;
        if (wholePercentages.Length != locationDigits || wholePercentages[0] is not '4' and not '8')
            throw new NotSupportedException("Old way has been removed!");
        (int width, int height) = OutputResolution.Get(outputResolution);
        RectangleF? rectangle = GetPercentagesRectangle(locationDigits, wholePercentages);
        if (rectangle is null)
            result = null;
        else
        {
            result = new((int)(rectangle.Value.X * width), (int)(rectangle.Value.Y * height), (int)(rectangle.Value.Width * width), (int)(rectangle.Value.Height * height));
        }
        if (result is null)
            throw new NullReferenceException(nameof(result));
        return result;
    }

    private static bool Matches(Models.OutputResolution outputResolution, DatabaseFile databaseFile)
    {
        bool result = outputResolution.Height == databaseFile.FileHeight && outputResolution.Width == databaseFile.FileWidth;
        return result;
    }

    internal static RectangleF? GetPercentagesRectangle(DatabaseFile databaseFile, Marker marker, Models.OutputResolution outputResolution)
    {
        RectangleF? result;
        bool matches = Matches(outputResolution, databaseFile);
        if (!matches)
            result = null;
        else
            result = new(marker.X, marker.Y, marker.W, marker.H);
        return result;
    }

    private static Models.Location? GetLocation(DatabaseFile databaseFile, Marker marker, RectangleF rectangle)
    {
        Models.Location? result;
        int top = (int)Math.Ceiling(rectangle.Top * databaseFile.FileHeight);
        int left = (int)Math.Ceiling(rectangle.Left * databaseFile.FileWidth);
        int right = (int)Math.Ceiling(rectangle.Right * databaseFile.FileWidth);
        int bottom = (int)Math.Ceiling(rectangle.Bottom * databaseFile.FileHeight);
        bool verified = Check(bottom, databaseFile.FileHeight, left, right, top, databaseFile.FileWidth, zCount: 1, throwException: false);
        if (!verified)
            result = null;
        else
            result = new(bottom, marker.Score / 100, left, right, top);
        return result;
    }

    internal static Models.Location? GetLocation(int height, Rectangle rectangle, int width)
    {
        Models.Location? result;
        double confidence = 0;
        bool verified = Check(rectangle.Bottom, height, rectangle.Left, rectangle.Right, rectangle.Top, width, zCount: 1, throwException: false);
        if (!verified)
            result = null;
        else
            result = new(rectangle.Bottom, confidence, rectangle.Left, rectangle.Right, rectangle.Top);
        return result;
    }

    internal static Models.Location? GetLocation(DatabaseFile databaseFile, Marker marker, Models.OutputResolution outputResolution)
    {
        Models.Location? result;
        RectangleF? rectangle = GetPercentagesRectangle(databaseFile, marker, outputResolution);
        if (rectangle is null)
            result = null;
        else
            result = GetLocation(databaseFile, marker, rectangle.Value);
        return result;
    }

    internal static float? GetIntersectPercent(RectangleF rectangleA, float? areaA, RectangleF rectangleB)
    {
        float? result;
        if (rectangleA.Equals(rectangleB))
            result = 1;
        else
        {
            float intersectArea;
            RectangleF intersectRectangle;
            areaA ??= rectangleA.Width * rectangleA.Height;
            float areaB = rectangleB.Width * rectangleB.Height;
            bool check = areaA > areaB;
            if (check)
                intersectRectangle = RectangleF.Intersect(rectangleB, rectangleA);
            else
                intersectRectangle = RectangleF.Intersect(rectangleA, rectangleB);
            intersectArea = intersectRectangle.Width * intersectRectangle.Height;
            if (check)
                result = intersectArea / areaA;
            else
                result = intersectArea / areaB;
        }
        return result;
    }

    internal static List<Models.Location> GetLocations<T>(List<LocationContainer<T>> locationContainers, List<Models.Face> faces, List<MappingFromPhotoPrism> mappingFromPhotoPrismCollection, float rectangleIntersectMinimum)
    {
        List<Models.Location> results = new();
        bool any;
        bool matches;
        float? percent;
        float prismArea;
        int width, height;
        Models.Location? location;
        RectangleF? prismRectangle;
        int dlibLocationWholePercentages;
        RectangleF? dlibPercentagesRectangle;
        Models.OutputResolution? outputResolution = null;
        foreach (Models.Face face in faces)
        {
            if (face.Location is null || face.OutputResolution is null)
                continue;
            results.Add(face.Location);
            outputResolution ??= face.OutputResolution;
        }
        int before = results.Count;
        foreach (LocationContainer<T> locationContainer in locationContainers)
        {
            if (locationContainer.Location is null)
                continue;
            results.Add(locationContainer.Location);
        }
        foreach (MappingFromPhotoPrism mappingFromPhotoPrism in mappingFromPhotoPrismCollection)
        {
            if (outputResolution is null)
                break;
            matches = Matches(outputResolution, mappingFromPhotoPrism.DatabaseFile);
            if (!matches)
                break;
            foreach (Marker marker in mappingFromPhotoPrism.Markers)
            {
                any = false;
                prismRectangle = GetPercentagesRectangle(mappingFromPhotoPrism.DatabaseFile, marker, outputResolution);
                if (prismRectangle is null)
                    break;
                prismArea = prismRectangle.Value.Width * prismRectangle.Value.Height;
                location = GetLocation(mappingFromPhotoPrism.DatabaseFile, marker, prismRectangle.Value);
                if (location is null)
                    break;
                foreach (LocationContainer<T> locationContainer in locationContainers)
                {
                    if (any)
                        continue;
                    if (locationContainer.Rectangle is null)
                        continue;
                    percent = GetIntersectPercent(prismRectangle.Value, prismArea, locationContainer.Rectangle.Value);
                    if (percent is null || percent < rectangleIntersectMinimum)
                        continue;
                    if (!any)
                        any = true;
                    break;
                }
                foreach (Models.Face face in faces)
                {
                    if (any)
                        continue;
                    if (face.Location is null || face.OutputResolution is null)
                        continue;
                    (width, height) = OutputResolution.Get(face.OutputResolution);
                    dlibLocationWholePercentages = GetWholePercentages(height, face.Location, Stateless.ILocation.Digits, width);
                    dlibPercentagesRectangle = GetPercentagesRectangle(Stateless.ILocation.Digits, dlibLocationWholePercentages.ToString());
                    if (dlibPercentagesRectangle is null)
                        continue;
                    percent = GetIntersectPercent(prismRectangle.Value, prismArea, dlibPercentagesRectangle.Value);
                    if (percent is null || percent < rectangleIntersectMinimum)
                        continue;
                    if (!any)
                        any = true;
                    break;
                }
                if (!any)
                    results.Add(location);
            }
        }
        if (before == results.Count)
            results.Clear();
        return results;
    }

    internal static List<Models.Face> FilterByIntersect(Models.Face[] faces, float rectangleIntersectMinimum, int wholePercentages)
    {
        List<Models.Face> results = new();
        float? percent;
        int width, height;
        int faceLocationWholePercentages;
        RectangleF? facePercentagesRectangle;
        RectangleF? sourceRectangle = GetPercentagesRectangle(Stateless.ILocation.Digits, wholePercentages.ToString());
        float? sourceArea = sourceRectangle is null ? null : sourceRectangle.Value.Width * sourceRectangle.Value.Height;
        foreach (Models.Face face in faces)
        {
            if (sourceRectangle is null || sourceArea is null)
                continue;
            if (face.Location is null || face.OutputResolution is null)
                continue;
            (width, height) = OutputResolution.Get(face.OutputResolution);
            faceLocationWholePercentages = GetWholePercentages(height, face.Location, Stateless.ILocation.Digits, width);
            facePercentagesRectangle = GetPercentagesRectangle(Stateless.ILocation.Digits, faceLocationWholePercentages.ToString());
            if (facePercentagesRectangle is null)
                continue;
            percent = GetIntersectPercent(sourceRectangle.Value, sourceArea.Value, facePercentagesRectangle.Value);
            if (percent is null || percent < rectangleIntersectMinimum)
                continue;
            results.Add(face);
        }
        return results;
    }

}