using DlibDotNet;
using View_by_Distance.FaceRecognitionDotNet.Models;
using View_by_Distance.Shared.Models.Stateless;

namespace View_by_Distance.FaceRecognitionDotNet.Dlib.Python;

internal sealed class SimpleObjectDetector
{

    #region Methods

    public static IEnumerable<Rectangle> RunDetectorWithUpscale1(FrontalFaceDetector detector,
                                                                 Image img,
                                                                 uint upsamplingAmount,
                                                                 double adjustThreshold,
                                                                 List<double> detectionConfidences,
                                                                 List<ulong> weightIndices)
    {
        List<Rectangle>? rectangles = [];

        if (img.Mode == Mode.Greyscale)
        {
            Matrix<byte>? greyscaleMatrix = img.Matrix as Matrix<byte>;
            if (upsamplingAmount == 0)
            {
                detector.Operator(greyscaleMatrix, out IEnumerable<RectDetection>? rectDetections, adjustThreshold);

                RectDetection[]? dets = rectDetections.ToArray();
                SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices);

                foreach (RectDetection? rectDetection in dets)
                    rectDetection.Dispose();
            }
            else
            {
                using PyramidDown? pyr = new(2);
                Matrix<byte>? temp = null;

                try
                {
                    DlibDotNet.Dlib.PyramidUp(greyscaleMatrix, pyr, out temp);

                    uint levels = upsamplingAmount - 1;
                    while (levels > 0)
                    {
                        levels--;
                        DlibDotNet.Dlib.PyramidUp(temp);
                    }

                    detector.Operator(temp, out IEnumerable<RectDetection>? rectDetections, adjustThreshold);

                    RectDetection[]? dets = rectDetections.ToArray();
                    foreach (RectDetection? t in dets)
                        t.Rect = pyr.RectDown(t.Rect, upsamplingAmount);

                    SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices);

                    foreach (RectDetection? rectDetection in dets)
                        rectDetection.Dispose();
                }
                finally
                {
                    temp?.Dispose();
                }
            }

            return rectangles;
        }
        else
        {
            Matrix<RgbPixel>? rgbMatrix = img.Matrix as Matrix<RgbPixel>;
            if (upsamplingAmount == 0)
            {
                detector.Operator(rgbMatrix, out IEnumerable<RectDetection>? rectDetections, adjustThreshold);

                RectDetection[]? dets = rectDetections.ToArray();
                SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices);

                foreach (RectDetection? rectDetection in dets)
                    rectDetection.Dispose();
            }
            else
            {
                using PyramidDown? pyr = new(2);
                Matrix<RgbPixel>? temp = null;

                try
                {
                    DlibDotNet.Dlib.PyramidUp(rgbMatrix, pyr, out temp);

                    uint levels = upsamplingAmount - 1;
                    while (levels > 0)
                    {
                        levels--;
                        DlibDotNet.Dlib.PyramidUp(temp);
                    }

                    detector.Operator(temp, out IEnumerable<RectDetection>? rectDetections, adjustThreshold);

                    RectDetection[]? dets = rectDetections.ToArray();
                    foreach (RectDetection? t in dets)
                        t.Rect = pyr.RectDown(t.Rect, upsamplingAmount);

                    SplitRectDetections(dets, rectangles, detectionConfidences, weightIndices);

                    foreach (RectDetection? rectDetection in dets)
                        rectDetection.Dispose();
                }
                finally
                {
                    temp?.Dispose();
                }
            }

            return rectangles;
        }
    }

    public static IEnumerable<Tuple<Rectangle, double>> RunDetectorWithUpscale2(FrontalFaceDetector detector,
                                                                                Image image,
                                                                                uint upsamplingAmount)
    {
        if (detector == null)
            throw new NullReferenceException(nameof(detector));
        if (image == null)
            throw new NullReferenceException(nameof(image));

        detector.ThrowIfDisposed();
        image.ThrowIfDisposed();

        List<double>? detectionConfidences = [];
        List<ulong>? weightIndices = [];
        const double adjustThreshold = 0.0;

        Rectangle[]? rects = RunDetectorWithUpscale1(detector,
                                            image,
                                            upsamplingAmount,
                                            adjustThreshold,
                                            detectionConfidences,
                                            weightIndices).ToArray();

        int index = 0;
        foreach (Rectangle rect in rects)
            yield return new Tuple<Rectangle, double>(rect, detectionConfidences[index++]);
    }

    #region Helpers

    private static void SplitRectDetections(RectDetection[] rectDetections,
                                            List<Rectangle> rectangles,
                                            List<double> detectionConfidences,
                                            List<ulong> weightIndices)
    {
        rectangles.Clear();
        detectionConfidences.Clear();
        weightIndices.Clear();

        foreach (RectDetection? rectDetection in rectDetections)
        {
            rectangles.Add(rectDetection.Rect);
            detectionConfidences.Add(rectDetection.DetectionConfidence);
            weightIndices.Add(rectDetection.WeightIndex);
        }
    }

    #endregion

    #endregion

}