using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ML;
using ObjectDetection.YoloParser;
using ObjectDetection.DataStructures;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace ObjectDetection
{
    class Program
    {
        public static void Main()
        {
            var assetsRelativePath = @"../../../assets";
            string assetsPath = GetAbsolutePath(assetsRelativePath);
            var modelFilePath = Path.Combine(assetsPath, "Model", "TinyYolo2_model.onnx");
            var imagesFolder = Path.Combine(assetsPath, "images");
            var outputFolder = Path.Combine(assetsPath, "images", "output");
            double limit_of_people_in_store = 0;

            // Get user input and determine the limit of people in the store
            limit_of_people_in_store = GetLimitOfPeopleInStore();

            // Initialize MLContext
            MLContext mlContext = new MLContext();

            try
            {
                // Load Data
                IEnumerable<ImageNetData> images = ImageNetData.ReadFromFile(imagesFolder);
                IDataView imageDataView = mlContext.Data.LoadFromEnumerable(images);

                // Create instance of model scorer
                var modelScorer = new OnnxModelScorer(imagesFolder, modelFilePath, mlContext);

                // Use model to score data
                IEnumerable<float[]> probabilities = modelScorer.Score(imageDataView);

                // Post-process model output
                YoloOutputParser parser = new YoloOutputParser();

                var boundingBoxes =
                    probabilities
                    .Select(probability => parser.ParseOutputs(probability))
                    .Select(boxes => parser.FilterBoundingBoxes(boxes, 100, .5F));

                int number_of_people_in_pictures = 0;
                int total_number_of_overlaps = 0;
                int overlaps_severity_level = 0;
                for (var i = 0; i < images.Count(); i++)
                {
                    int number_of_overlaps = 0;
                    string imageFileName = images.ElementAt(i).Label;
                    IList<YoloBoundingBox> detectedObjects = boundingBoxes.ElementAt(i);
                    foreach (var box in detectedObjects)
                    {
                        if (box.Label == "person")
                        {
                            number_of_people_in_pictures += 1;
                        }

                    }
                    number_of_overlaps = FindOverlappingBoxes(detectedObjects);
                    total_number_of_overlaps += number_of_overlaps;
                    overlaps_severity_level = DetermineSeverity(detectedObjects.Count(), (int)limit_of_people_in_store, images.Count(), number_of_overlaps);
                    IssueWarningForOverlap(overlaps_severity_level, imageFileName);
                    DrawBoundingBox(imagesFolder, outputFolder, imageFileName, detectedObjects);
                }

                Console.WriteLine("=========================================");
                Console.WriteLine("The number of people present: " + number_of_people_in_pictures);
                Console.WriteLine("The store limit is: " + limit_of_people_in_store);
                if ((double)number_of_people_in_pictures > limit_of_people_in_store)
                {
                    Console.WriteLine("The store exceeded its capacity.");
                }
                else if ((double)number_of_people_in_pictures == limit_of_people_in_store)
                {
                    Console.WriteLine("The store is at maximum capacity.");
                }
                else
                {
                    Console.WriteLine("The store is within the limit of people present at the store.");
                }
                Console.WriteLine("=========================================");

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            Console.ReadLine();
        }
        public DashStyle DashStyle { get; set; }

        public static void IssueWarningForOverlap(int overlap_severity_level, string ImageFileName)
        {
            if (overlap_severity_level ==  1)
            {
                Console.WriteLine("Level 1 severity social distancing warning: " + ImageFileName);
            }
            else if (overlap_severity_level == 2)
            {
                Console.WriteLine("Level 2 severity social distancing warning: " + ImageFileName);

            }
            else if (overlap_severity_level == 3)
            {
                Console.WriteLine("Level 3 severity social distancing warning: " + ImageFileName);

            }
        }

        public static int DetermineSeverity(int number_of_people, int limit_of_people, int number_of_pictures, int intersections)
        {
            int level = 0;
            int intersections_per_person = 0;
            int people_limit_per_picture = 0;
            if (intersections > 1)
            {
                level ++;

                intersections_per_person = intersections / number_of_people;
                if (intersections_per_person > 0.7)
                {
                    level++;
                }
                people_limit_per_picture = limit_of_people / number_of_pictures;
                if (people_limit_per_picture > number_of_people)
                {
                    level++;
                }
            }

            return level;
        }

        public static int FindOverlappingBoxes(IList<YoloBoundingBox> filteredBoundingBoxes)
        {
            int number_of_overlaps = 0;
            foreach (var box in filteredBoundingBoxes)
            {
                foreach (var box_to_compare in filteredBoundingBoxes)
                {
                    Rectangle box_rect = new Rectangle((int)box.Dimensions.X, (int)box.Dimensions.Y, (int)box.Dimensions.Width, (int)box.Dimensions.Height);
                    Rectangle box_to_compare_rect = new Rectangle((int)box_to_compare.Dimensions.X, (int)box_to_compare.Dimensions.Y, (int)box_to_compare.Dimensions.Width, (int)box_to_compare.Dimensions.Height);

                    if (box_to_compare == box)
                    {
                        continue;
                    }
                    if (box_rect.IntersectsWith(box_to_compare_rect))
                    {
                        box_rect.Intersect(box_to_compare_rect);
                        if (!box_rect.IsEmpty)
                        {
                            number_of_overlaps++;
                        }
                    }
                }
            }
            return number_of_overlaps / 2;
        }

        public void Intersect(System.Drawing.Rectangle rect)
        {
        }


        public static double GetLimitOfPeopleInStore()
        {
            bool peopleLimitSet = false;
            bool sizeOfStoreSet = false;
            double limit_of_people_per_m = 0;
            int size_of_store_m = 0;
            double limit_of_people_in_store = 0;
            while ((peopleLimitSet == false) && (sizeOfStoreSet == false))
            {
                try
                {
                    if (peopleLimitSet == false)
                    {
                        limit_of_people_per_m = SetLimitOfPeoplePerm2();
                        peopleLimitSet = true;
                    }
                    if (sizeOfStoreSet == false)
                    {
                        size_of_store_m = SetSizeOfStore();
                        sizeOfStoreSet = true;
                    }

                }
                catch (System.FormatException)
                {
                    Console.WriteLine("Please try again, only use whole numbers for your input.");
                }
            }

            limit_of_people_in_store = limit_of_people_per_m * size_of_store_m;
            return limit_of_people_in_store;
        }


        public static double SetLimitOfPeoplePerm2() 
        {
            Console.WriteLine("Set the limit of people per 10m2 according to the current legislation :");
            int limit_of_people_per_10m2 = Convert.ToInt32(Console.ReadLine());
            double limit_of_people_per_m2 = (double)limit_of_people_per_10m2 / (double)10;
            return limit_of_people_per_m2;
        }

        public static int SetSizeOfStore()
        {
            Console.WriteLine("Set the size of your shop in m2 :");
            int size_of_store_in_m = Convert.ToInt32(Console.ReadLine());
            return size_of_store_in_m;
        }


        public static string GetAbsolutePath(string relativePath)
        {
            FileInfo _dataRoot = new FileInfo(typeof(Program).Assembly.Location);
            string assemblyFolderPath = _dataRoot.Directory.FullName;

            string fullPath = Path.Combine(assemblyFolderPath, relativePath);

            return fullPath;
        }

        private static void DrawBoundingBox(string inputImageLocation, string outputImageLocation, string imageName, IList<YoloBoundingBox> filteredBoundingBoxes)
        {
            Image image = Image.FromFile(Path.Combine(inputImageLocation, imageName));

            var originalImageHeight = image.Height;
            var originalImageWidth = image.Width;  

            foreach (var box in filteredBoundingBoxes)
            {
                // Get Bounding Box Dimensions
                var x = (uint)Math.Max(box.Dimensions.X, 0);
                var y = (uint)Math.Max(box.Dimensions.Y, 0);
                var width = (uint)Math.Min(originalImageWidth - x, box.Dimensions.Width);
                var height = (uint)Math.Min(originalImageHeight - y, box.Dimensions.Height);

                // Resize To Image
                x = (uint)originalImageWidth * x / OnnxModelScorer.ImageNetSettings.imageWidth;
                y = (uint)originalImageHeight * y / OnnxModelScorer.ImageNetSettings.imageHeight;
                width = (uint)originalImageWidth * width / OnnxModelScorer.ImageNetSettings.imageWidth;
                height = (uint)originalImageHeight * height / OnnxModelScorer.ImageNetSettings.imageHeight;

                // Bounding Box Text
                string text = $"{box.Label} ({(box.Confidence * 100).ToString("0")}%)";

                using (Graphics thumbnailGraphic = Graphics.FromImage(image))
                {
                    thumbnailGraphic.CompositingQuality = CompositingQuality.HighQuality;
                    thumbnailGraphic.SmoothingMode = SmoothingMode.HighQuality;
                    thumbnailGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;

                    // Define Text Options
                    Font drawFont = new Font("Arial", 12, FontStyle.Bold);
                    SizeF size = thumbnailGraphic.MeasureString(text, drawFont);
                    SolidBrush fontBrush = new SolidBrush(Color.Black);
                    Point atPoint = new Point((int)x, (int)y - (int)size.Height - 1);

                    // Define BoundingBox options
                    Pen pen = new Pen(box.BoxColor, 5.2f);
                    if (box.Confidence < 0.5)
                    {
                        pen.DashPattern = new float[] { 3.0F, 3.0F };
                    }
                    SolidBrush colorBrush = new SolidBrush(box.BoxColor);

                    // Draw text on image 
                    thumbnailGraphic.FillRectangle(colorBrush, (int)x, (int)(y - size.Height - 1), (int)size.Width, (int)size.Height);
                    thumbnailGraphic.DrawString(text, drawFont, fontBrush, atPoint);

                    // Draw bounding box on image
                    thumbnailGraphic.DrawRectangle(pen, x, y, width, height);
                }
            }

            if (!Directory.Exists(outputImageLocation))
            {
                Directory.CreateDirectory(outputImageLocation);
            }

            image.Save(Path.Combine(outputImageLocation, imageName));
        }
    }
}



