/// Author : Maxime Rohmer
/// Date : 30/05/2023
/// File : ConfigurationTool.cs
/// Brief : Class that contains all the methods used to create config files for the main programm
/// Version : Alpha 1.0

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tesseract;
using System.IO;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace TrackTrends
{
    public class ConfigurationTool
    {
        public Zone MainZone;
        public const int NUMBER_OF_DRIVERS = 20;
        public const int NUMBER_OF_ZONES = 9;
        public const string CONFIGS_FOLDER_NAME = "./Presets/";
        /// <summary>
        /// Creates the configuration tool. It can only be created if you already have the dimensions of the main zone
        /// </summary>
        /// <param name="fullImage">The full image coming from the F1TV Data Channel</param>
        /// <param name="mainZoneDimensions">The dimensions of the zone where all the drivers data are situated</param>
        public ConfigurationTool(Bitmap fullImage, Rectangle mainZoneDimensions)
        {
            MainZone = new Zone(fullImage, mainZoneDimensions,"Main");
            AutoCalibrate();
        }
        /// <summary>
        /// Resets the main zone
        /// </summary>
        public void ResetMainZone()
        {
            MainZone.ResetZones();
        }
        /// <summary>
        /// Reset the windows
        /// </summary>
        public void ResetWindows()
        {
            MainZone.ResetWindows();
        }
        /// <summary>
        /// Save the current config in a JSON file stored in /Presets/
        /// </summary>
        /// <param name="drivers">A list of all the drivers in the GP. IMPORTANT, they need to ALL be mentionned or the program wont be able to detect the missing ones and will F up everything</param>
        /// <param name="configName">The name the config should have</param>
        public void SaveToJson(List<string> drivers, string configName)
        {
            string JSON = "";

            JsonObject jsonFileObject = new JsonObject();

            //Creates the mainZone object
            JsonObject mainZoneObject = new JsonObject();

            mainZoneObject.Add("x",MainZone.Bounds.X);
            mainZoneObject.Add("y",MainZone.Bounds.Y);
            mainZoneObject.Add("width",MainZone.Bounds.Width);
            mainZoneObject.Add("height",MainZone.Bounds.Height);

            JsonArray driverZonesArray = new JsonArray();

            //Creates all the subzones that contain driver infos
            int DriverID = 0;
            foreach (Zone driverZone in MainZone.Zones)
            {
                DriverID++;
                JsonObject driverZoneObject = new JsonObject();
                driverZoneObject.Add("name","Driver"+DriverID);
                driverZoneObject.Add("x", driverZone.Bounds.X);
                driverZoneObject.Add("y", driverZone.Bounds.Y);
                driverZoneObject.Add("width", driverZone.Bounds.Width);
                driverZoneObject.Add("height", driverZone.Bounds.Height);

                JsonArray windowsArray = new JsonArray();
                JsonObject windowObject = new JsonObject();

                //Creates all the windows of the current driver zone
                //Note : We store ALL the windows and zones in the JSON because they are not spaced exactly the same on the main zone
                foreach (Window window in driverZone.Windows)
                {
                    windowObject.Add(window.Name, new JsonObject {
                        { "x", window.Bounds.X },
                        { "y", window.Bounds.Y },
                        { "width", window.Bounds.Width },
                        { "height", window.Bounds.Height }
                    });
                }
                windowsArray.Add(windowObject);

                driverZoneObject.Add("Windows",windowsArray);

                driverZonesArray.Add(driverZoneObject);
            }

            mainZoneObject.Add("DriverZones",driverZonesArray);

            JsonArray driversArray = new JsonArray();

            foreach (string driver in drivers)
            {
                driversArray.Add(driver);
            }

            mainZoneObject.Add("Drivers",driversArray);

            jsonFileObject.Add("Main",mainZoneObject);

            JSON = jsonFileObject.ToString();
                
            //Saving the file
            string path = CONFIGS_FOLDER_NAME + configName;

            if (File.Exists(path + ".json"))
            {
                //We need to create a new name
                int count = 2;
                while (File.Exists(path + "_" + count + ".json"))
                {
                    count++;
                }
                path += "_" + count + ".json";
            }
            else
            {
                path += ".json";
            }

            File.WriteAllText(path, JSON);
        }
        /// <summary>
        /// Adds a window in the windows list
        /// Be carefull of the order. It cant be random or it will crash. The programm expect the first to be position, second Gap to leader etc...
        /// </summary>
        /// <param name="rectangles">The bounds of the window</param>
        public void AddWindows(List<Rectangle> rectangles)
        {
            foreach (Zone driverZone in MainZone.Zones)
            {
                Bitmap zoneImage = driverZone.ZoneImage;

                for (int i = 1; i <= rectangles.Count; i++)
                {
                    switch (i)
                    {
                        case 1:
                            //First zone should be the driver's Position
                            driverZone.AddWindow(new DriverPositionWindow(driverZone.ZoneImage, rectangles[i - 1], false));
                            break;
                        case 2:
                            //Second zone should be the Gap to leader
                            driverZone.AddWindow(new DriverGapToLeaderWindow(driverZone.ZoneImage, rectangles[i - 1], false));
                            break;
                        case 3:
                            //Third zone should be the driver's Lap Time
                            driverZone.AddWindow(new DriverLapTimeWindow(driverZone.ZoneImage, rectangles[i - 1], false));
                            break;
                        case 4:
                            //Fourth zone should be the driver's DRS status
                            driverZone.AddWindow(new DriverDrsWindow(driverZone.ZoneImage, rectangles[i - 1], false));
                            break;
                        case 5:
                            //Fifth zone should be the driver's Tyre's informations
                            driverZone.AddWindow(new DriverTyresWindow(driverZone.ZoneImage, rectangles[i - 1], false));
                            break;
                        case 6:
                            //Sixth zone should be the driver's Name
                            driverZone.AddWindow(new DriverNameWindow(driverZone.ZoneImage, rectangles[i - 1], false));
                            break;
                        case 7:
                            //Seventh zone should be the driver's First Sector
                            driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 1, false));
                            break;
                        case 8:
                            //Zone number eight should be the driver's Second Sector
                            driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 2, false));
                            break;
                        case 9:
                            //Zone number nine should be the driver's Position Sector
                            driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 3, false));
                            break;
                    }
                }
            }
        }
        /// <summary>
        /// This will automatically create all the driver zones at the correct places if the main zone has been weel positionned
        /// You cant just divide the image by the number of pilots or it will be messy and inconsistent at the end (Garbage in Garbage Out)
        /// </summary>
        public void AutoCalibrate()
        {
            List<Rectangle> detectedText = new List<Rectangle>();
            List<Zone> zones = new List<Zone>();

            TesseractEngine engine = new TesseractEngine(Window.TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default);
            Image image = MainZone.ZoneImage;
            var tessImage = Pix.LoadFromMemory(Window.ImageToByte(image));

            Page page = engine.Process(tessImage);
            //Runs a quick OCR detection. Not to detect any content but just to detect where is all the text positionned.
            //For each row it decides the best Zone location and adds it to the Driver zone list
            using (var iter = page.GetIterator())
            {
                iter.Begin();
                do
                {
                    Rect boundingBox;
                    if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out boundingBox))
                    {
                        //We remove all the rectangles that are definitely too big
                        if (boundingBox.Height < image.Height / NUMBER_OF_DRIVERS)
                        {
                            //Now we add a filter to only get the boxes in the right because they are much more reliable in size
                            if (boundingBox.X1 > image.Width / 2)
                            {
                                //Now we check if an other square box has been found roughly in the same y axis
                                bool match = false;
                                //The tolerance is roughly half the size that a window will be
                                int tolerance = (image.Height / NUMBER_OF_DRIVERS) / 2;

                                foreach (Rectangle rect in detectedText)
                                {
                                    if (rect.Y > boundingBox.Y1 - tolerance && rect.Y < boundingBox.Y1 + tolerance)
                                    {
                                        //There already is a rectangle in this line
                                        match = true;
                                    }
                                }
                                //if nothing matched we can add it
                                if (!match)
                                    detectedText.Add(new Rectangle(boundingBox.X1, boundingBox.Y1, boundingBox.Width, boundingBox.Height));
                            }
                        }
                    }
                } while (iter.Next(PageIteratorLevel.Word));
            }
            //DEBUG
            int i = 1;
            foreach (Rectangle Rectangle in detectedText)
            {
                Rectangle windowRectangle;
                Size windowSize = new Size(image.Width, image.Height / NUMBER_OF_DRIVERS);
                Point windowLocation = new Point(0, (Rectangle.Y + Rectangle.Height / 2) - windowSize.Height / 2);
                windowRectangle = new Rectangle(windowLocation, windowSize);
                //We add the driver zones
                Zone driverZone = new Zone(MainZone.ZoneImage, windowRectangle, "DriverZone");
                MainZone.AddZone(driverZone);
                //driverZone.ZoneImage.Save("Driver" + i+".png");
                i++;
            }
        }
    }
}
