using System;
using InterpreterLibrary.Models;
using Tesseract;

namespace InterpreterLibrary.Processing
{
    public class BlackBlobsCounter : BlobCounterBase
    {

        public BlackBlobsCounter(string imageFileName) : this(Pix.LoadFromFile(imageFileName))
        {
        }

        public BlackBlobsCounter(string imageFileName, Rectangle areaOfInterest) : this(Pix.LoadFromFile(imageFileName),
            areaOfInterest)
        {
        }

        public BlackBlobsCounter(Pix image) : this(image, new Rectangle(0, 0, image.Width, image.Height))
        {
        }

        public BlackBlobsCounter(Pix image, Rectangle areaOfInterest) : base(image, areaOfInterest)
        {
            if (image.Depth != 32 && image.Depth != 1)
                throw new NotSupportedException();
        }

        private bool isBlackPixel(uint pixel)
        {
            const uint blackPixel = 0;
            const uint blackMask = 0xffffff;
            return (pixel & blackMask) == blackPixel;
        }

        protected override void BuildObjectsMap(Pix image, Rectangle areaOfInterest)
        {
            if (image.Depth != 32 && image.Depth != 1)
                throw new NotSupportedException("Only 32-bit images are supported.");

            if (areaOfInterest == null)
                areaOfInterest = new Rectangle(0, 0, ImageWidth, ImageHeight);

            
            if (ImageWidth == 1)
                throw new NotSupportedException(
                    $"{nameof(BlackBlobsCounter)} cannot process images that are one pixel wide.");

            var imageWidthM1 = ImageWidth - 1;


            objectLabels = new int[ImageWidth * ImageHeight];

            var labelsCount = 0;

            
            var maxObjects = (ImageWidth / 2 + 1) * (ImageHeight / 2 + 1) + 1;
            var map = new int[maxObjects];
            for (var i = 0; i < maxObjects; ++i)
                map[i] = i;

            var xMin = Math.Max(0, areaOfInterest.X);
            var xMax = Math.Min(ImageWidth, areaOfInterest.X + areaOfInterest.Width);
            var yMin = Math.Max(0, areaOfInterest.Y);
            var yMax = Math.Min(ImageHeight, areaOfInterest.Y + areaOfInterest.Height);
            var xMaxM1 = xMax - 1;

            
            var helper = PixHelper.Create(image);

                if (isBlackPixel(helper.GetPixel(xMin, yMin)))
                {
                    var p = xMin + yMin * ImageWidth;
                    objectLabels[p] = ++labelsCount;
                }

                
                for (var x = xMin + 1; x < xMax; x++)
                {
                    var p = x + yMin * ImageWidth;
                    
                    if (isBlackPixel(helper.GetPixel(x, yMin)))
                        if (isBlackPixel(helper.GetPixel(x - 1, yMin)))
                            objectLabels[p] = objectLabels[p - 1];
                        else
                            objectLabels[p] = ++labelsCount;
                }

                
                
                for (var y = yMin + 1; y < yMax; y++)
                {
                    
                    
                    if (isBlackPixel(helper.GetPixel(xMin, y)))
                    {
                        var p = xMin + y * ImageWidth;
                        
                        if (isBlackPixel(helper.GetPixel(xMin, y - 1)))
                            objectLabels[p] = objectLabels[p - ImageWidth];
                        else if (isBlackPixel(helper.GetPixel(xMin + 1, y - 1)))
                            objectLabels[p] = objectLabels[p + 1 - ImageWidth];
                        else
                            objectLabels[p] = ++labelsCount;
                    }

                    
                    for (var x = xMin + 1; x < xMaxM1; x++)
                        if (isBlackPixel(helper.GetPixel(x, y)))
                        {
                            var p = x + y * ImageWidth;
                            
                            if (isBlackPixel(helper.GetPixel(x - 1, y)))
                                objectLabels[p] = objectLabels[p - 1];
                            else if (isBlackPixel(helper.GetPixel(x - 1, y - 1)))
                                objectLabels[p] = objectLabels[p - 1 - ImageWidth];
                            else if (isBlackPixel(helper.GetPixel(x, y - 1)))
                                objectLabels[p] = objectLabels[p - ImageWidth];

                            if (isBlackPixel(helper.GetPixel(x + 1, y - 1)))
                                if (objectLabels[p] == 0)
                                {
                                    objectLabels[p] = objectLabels[p + 1 - ImageWidth];
                                }
                                else
                                {
                                    var l1 = objectLabels[p];
                                    var l2 = objectLabels[p + 1 - ImageWidth];

                                    if (l1 != l2 && map[l1] != map[l2])
                                    {
                                        
                                        if (map[l1] == l1)
                                        {
                                            
                                            map[l1] = map[l2];
                                        }
                                        else if (map[l2] == l2)
                                        {
                                            
                                            map[l2] = map[l1];
                                        }
                                        else
                                        {
                                            
                                            map[map[l1]] = map[l2];
                                            map[l1] = map[l2];
                                        }

                                        
                                        for (var i = 1; i <= labelsCount; i++)
                                            if (map[i] != i)
                                            {
                                                
                                                var j = map[i];
                                                while (j != map[j])
                                                    j = map[j];
                                                map[i] = j;
                                            }
                                    }
                                }

                            
                            if (objectLabels[p] == 0)
                                objectLabels[p] = ++labelsCount;
                        }

                    
                    
                    if (isBlackPixel(helper.GetPixel(imageWidthM1, y)))
                    {
                        var p = imageWidthM1 + y * ImageWidth;
                        
                        if (isBlackPixel(helper.GetPixel(imageWidthM1 - 1, y)))
                            objectLabels[p] = objectLabels[p - 1];
                        else if (isBlackPixel(helper.GetPixel(imageWidthM1 - 1, y - 1)))
                            objectLabels[p] = objectLabels[p - 1 - ImageWidth];
                        else if (isBlackPixel(helper.GetPixel(imageWidthM1, y - 1)))
                            objectLabels[p] = objectLabels[p - ImageWidth];
                        else
                            objectLabels[p] = ++labelsCount;
                    }
                }

                
                var reMap = new int[map.Length];

                
                objectsCount = 0;
                for (var i = 1; i <= labelsCount; i++)
                    if (map[i] == i)
                        reMap[i] = ++objectsCount;
                
                for (var i = 1; i <= labelsCount; i++)
                    if (map[i] != i)
                        reMap[i] = reMap[map[i]];

                
                for (int i = 0, n = objectLabels.Length; i < n; i++)
                    objectLabels[i] = reMap[objectLabels[i]];
            }
        }
    }
