using System.Collections.Generic;
using System.Linq;
using CrossCutting;
using InterpreterLibrary.Models;
using OpenCvSharp;
using OpenCvSharp.Blob;
using Point = OpenCvSharp.Point;

namespace InterpreterLibrary.Processing
{
    class MatMasker
    {
        private readonly Mat _mat;
        private readonly Scalar _backgroundColor;

        public MatMasker(Mat mat, Scalar backgroundColor) : this(mat, new Rect(0, 0, mat.Width, mat.Height), backgroundColor)
        {

        }

        public MatMasker(Mat mat, Rect rectangle, Scalar backgroundColor)
        {
            _mat = mat.SubMat(rectangle);
            _backgroundColor = backgroundColor;
        }

        public MatMasker(Mat mat) : this(mat, mat.BoundingRect(), Scalar.Black)
        {

        }

        public Mat MaskBlobInPlace(CvBlob blob)
        {
            var mask = new Mat(_mat.Size(), _mat.Type(), Scalar.Black);
            Cv2.FillPoly(mask, new[] { blob.Contour.ConvertToPolygon() }, Scalar.White);
            using (var blobImage = new Mat())
            {
                using (var inverted = new Mat())
                {
                    Cv2.BitwiseNot(_mat, inverted);
                    inverted.CopyTo(blobImage, mask);
                    var ret = new Mat();
                    Cv2.BitwiseNot(blobImage, ret);
                    return ret;
                }
            }
        }
        public Mat MaskBlob(CvBlob blob)
        {
            return MaskBlobInPlace(blob).SubMat(blob.Rect);
        }
        public Mat ApplyPolygonMask(Point[] points)
        {
            return ApplyMultiPolygonMask(new List<Point[]> { points });
        }

        public Mat ApplyMultiPolygonMask(IEnumerable<Point[]> polygons)
        {
            int cols = _mat.Width, rows = _mat.Height;
            var polygonArray = polygons as Point[][] ?? polygons.ToArray();

            var ret = new Mat(new Size(cols, rows), _mat.Type(), _backgroundColor);

            var borderColor = Scalar.LightGray;
            var interiorColor = Scalar.White;
            var mask = new Mat(new Size(cols, rows), _mat.Type(), Scalar.Black);

            Cv2.DrawContours(mask, polygonArray, -1, borderColor);
            foreach (var polygon in polygonArray)
            {
                var interiorPoint = FindSomeInteriorPoint(polygon);
                if (interiorPoint == null)
                {
                    continue;
                }
                Cv2.FloodFill(mask, interiorPoint.Value, interiorColor, out _, null, null, FloodFillFlags.Link4);
            }

            var finalMask = mask.InRange(interiorColor, interiorColor);
            _mat.CopyTo(ret, finalMask);
            return ret;
        }
        private static Point? FindSomeInteriorPoint(IReadOnlyList<Point> polygonPoints)
        {
            if (polygonPoints.Count < 4)
            {
                return null;
            }

            var xMin = polygonPoints.Min(p => p.X);
            var yMin = polygonPoints.Min(p => p.Y);
            var xMax = polygonPoints.Max(p => p.X);
            var yMax = polygonPoints.Max(p => p.Y);

            var centerPoint = new Point((xMin + xMax) / 2f, (yMin + yMax) / 2f);
            if (Cv2.PointPolygonTest(polygonPoints, centerPoint, false) > 0)
            {
                return centerPoint;
            }

            var startPoint = new PointF(xMin - 1, (yMin + yMax) / 2f);
            var endPoint = new PointF(xMax + 1, startPoint.Y);
            var line = Line.FromPoints(startPoint, endPoint);

            var intersections = new List<PointF>();
            for (var i = 0; i < polygonPoints.Count - 1; ++i)
            {
                var p1 = polygonPoints[i];
                var p2 = polygonPoints[i + 1];
                if (p1 == p2)
                {
                    continue;
                }
                    var segment = new LineSegment(new PointF(p1.X, p1.Y), new PointF(p2.X, p2.Y));
                    var intersection = line.GetIntersectionWith(segment);
                    if (intersection != null)
                    {
                        intersections.Add(intersection.Value);
                        if (intersections.Count == 2)
                        {
                            var intersectionPoint = new Point(intersections.Average(p => p.X), startPoint.Y);
                            if (Cv2.PointPolygonTest(polygonPoints, intersectionPoint, false) > 0)
                            {
                                return intersectionPoint;
                            }

                            break;
                        }
                    }

            }
            return null;
        }
    }
}

