/// Author : Maxime Rohmer
/// Date : 09/06/2023
/// File : Settings.cs
/// Brief : Class that controls the settings view
/// Version : Beta 1.0

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using TrackTrends;

namespace TrackTrends
{
    public partial class Settings : Form
    {
        private string _grandPrixUrl = "";
        private string _selectedConfigFile;
        private List<string> _driverList = new List<string>();

        private F1TVEmulator Emulator = null;
        private ConfigurationTool Config = null;

        private bool CreatingZone = false;
        private Point ZoneP1;
        private Point ZoneP2;

        private bool CreatingWindow = false;
        private Point WindowP1;
        private Point WindowP2;

        List<Rectangle> WindowsToAdd = new List<Rectangle>();

        public string GrandPrixUrl { get => _grandPrixUrl; private set => _grandPrixUrl = value; }
        public List<string> DriverList { get => _driverList; private set => _driverList = value; }
        public string SelectedConfigFile { get => _selectedConfigFile; private set => _selectedConfigFile = value; }

        //For the responsive content
        Size oldSize = new Size();
        Size oldGpbxPreviewSize = new Size();
        Size oldGpbxWindowPreviewSize = new Size();

        Size oldPbxPreviewSize = new Size();
        Size oldPbxWindowPreviewSize = new Size();

        public Settings()
        {
            InitializeComponent();
            Load();
        }
        /// <summary>
        /// This methods regroups all the actions that the forms need to be doing at the first launch
        /// </summary>
        private void Load()
        {
            RefreshUI();
            oldSize = this.Size;
            oldGpbxPreviewSize = gpbxPreview.Size;
            oldGpbxWindowPreviewSize = gpbxWindowPreview.Size;
            oldPbxPreviewSize = pbxPreview.Size;
            oldPbxWindowPreviewSize = pbxWindowPreview.Size;

            btnLoadPreset.Enabled = false;
            btnDeletePreset.Enabled = false;
            btnSavePreset.Enabled = false;

            // I prefered regrouping all the tooltips here to make it easier to edit (there is 100% of thoses sentences containing typos so if you see one dont hesitate to edit those messages)
            tip1.SetToolTip(btnCreatZone, "After clicking you can select two points in the image to set the bounds of the important data");
            tip1.SetToolTip(btnCreateWindow, "After clicking this you will have to select all the windows that are important on the lower image. Refer to the documentation for more infos");
            tip1.SetToolTip(btnRefresh, "Starts the emulator or refreshes the images if its already running");
            tip1.SetToolTip(btnResetDriver, "Resets the driver if something went wrong or if you want to test an other URL");
            tip1.SetToolTip(lsbDrivers, "The drivers that are on the image. Non-Case sensitive");
            tip1.SetToolTip(tbxPresetName, "The name of the preset you want to save");
            tip1.SetToolTip(pbxPreview, "What the emulator returns");
            tip1.SetToolTip(pbxWindowPreview, "One of the driver zones that the program managed to slice from the main zone");
        }
        /// <summary>
        /// This is the main method that will be called anytime something changes on the view
        /// It can be called at any time and will adapt the UI taking into account the state of the app
        /// </summary>
        private void RefreshUI()
        {
            lsbDrivers.DataSource = null;
            lsbDrivers.DataSource = DriverList;

            if (Directory.Exists(ConfigurationTool.CONFIGS_FOLDER_NAME))
            {
                lsbPresets.DataSource = null;
                lsbPresets.DataSource = Directory.GetFiles(ConfigurationTool.CONFIGS_FOLDER_NAME);
            }
            if (CreatingZone)
            {
                if (ZoneP1 == new Point(-1, -1))
                {
                    lblZonePointsRemaning.Text = "2 points Remaining";
                }
                else
                {
                    lblZonePointsRemaning.Text = "1 point Remaining";
                }
            }
            else
            {
                lblZonePointsRemaning.Text = "";
            }

            if (CreatingWindow)
            {
                if (WindowP1 == new Point(-1, -1))
                {
                    lblWindowPointsRemaining.Text = "2 points Remaining";
                }
                else
                {
                    lblWindowPointsRemaining.Text = "1 point Remaining";
                }
                lblWindowPointsRemaining.Text = ConfigurationTool.NUMBER_OF_ZONES - WindowsToAdd.Count() + " Windows remaining";
            }
            else
            {
                lblWindowPointsRemaining.Text = "";
                lblWindowsRemaining.Text = "";
            }
            if (Config != null)
            {
                pbxPreview.Image = Config.MainZone.Draw();
                if (Config.MainZone.Zones.Count > 0)
                    pbxWindowPreview.Image = Config.MainZone.Zones[0].Draw();
            }
        }
        /// <summary>
        /// This will create a new zone but will require two points (one at each opposing sides and corners)
        /// </summary>
        /// <param name="p1">The first corner (usually top left)</param>
        /// <param name="p2">The second corner (usually bottom right)</param>
        private void CreateNewZone(Point p1, Point p2)
        {
            Rectangle dimensions = CreateAbsoluteRectangle(p1, p2);
            Config = new ConfigurationTool((Bitmap)pbxPreview.Image, dimensions);
            RefreshUI();
        }
        /// <summary>
        /// Creates all the windows with an array of rectangles
        /// </summary>
        /// <param name="dimensions">An array that contains all the windows bounds and position (expects 9)</param>
        private void CreateWindows(List<Rectangle> dimensions)
        {
            if (Config != null)
            {
                Config.AddWindows(dimensions);
            }
        }
        /// <summary>
        /// Will just change the main URL
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tbxGpUrl_TextChanged(object sender, EventArgs e)
        {
            GrandPrixUrl = tbxGpUrl.Text;
        }
        /// <summary>
        /// Adds a driver into the driver list
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAddDriver_Click(object sender, EventArgs e)
        {
            string newDriver = tbxDriverName.Text;
            DriverList.Add(newDriver);
            tbxDriverName.Text = "";
            RefreshUI();
        }
        /// <summary>
        /// Removes a driver from the drivers list
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRemoveDriver_Click(object sender, EventArgs e)
        {
            if (lsbDrivers.SelectedIndex >= 0)
            {
                DriverList.RemoveAt(lsbDrivers.SelectedIndex);
            }
            RefreshUI();
        }
        /// <summary>
        /// Will change everything that needs to be changed for when the users starts or stops creating a zone
        /// </summary>
        private void SwitchZoneCreation()
        {
            if (CreatingZone)
            {
                CreatingZone = false;
                lblZonePointsRemaning.Text = "";
            }
            else
            {
                CreatingZone = true;

                if (Config != null)
                    Config.ResetMainZone();

                if (CreatingWindow)
                    SwitchWindowCreation();

                if (Emulator != null && Emulator.Ready)
                {
                    Config = null;
                    pbxPreview.Image = Emulator.Screenshot();
                }

                ZoneP1 = new Point(-1, -1);
                ZoneP2 = new Point(-1, -1);

                lblZonePointsRemaning.Text = "2 Points left";
            }
            RefreshUI();
        }
        /// <summary>
        /// Will change everything that needs to be changed for when the users starts or stops creating a window
        /// </summary>
        private void SwitchWindowCreation()
        {
            if (CreatingWindow)
            {
                CreatingWindow = false;
            }
            else
            {
                CreatingWindow = true;

                if (Config != null)
                    Config.ResetWindows();

                if (CreatingZone)
                    SwitchZoneCreation();

                WindowP1 = new Point(-1, -1);
                WindowP2 = new Point(-1, -1);

                WindowsToAdd = new List<Rectangle>();
            }
            RefreshUI();
        }
        private void btnCreatZone_Click(object sender, EventArgs e)
        {
            SwitchZoneCreation();
        }
        private void btnCreateWindow_Click(object sender, EventArgs e)
        {
            SwitchWindowCreation();
        }
        /// <summary>
        /// If the user is supposed to create a zone, will record the position of the clicks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pbxMain_MouseClick(object sender, MouseEventArgs e)
        {
            if (CreatingZone && pbxPreview.Image != null)
            {
                //Point coordinates = pbxMain.PointToClient(new Point(MousePosition.X, MousePosition.Y));
                Point coordinates = e.Location;
                float xOffset = (float)pbxPreview.Image.Width / (float)pbxPreview.Width;
                float yOffset = (float)pbxPreview.Image.Height / (float)pbxPreview.Height;
                Point newPoint = new Point(Convert.ToInt32((float)coordinates.X * xOffset), Convert.ToInt32((float)coordinates.Y * yOffset));

                //MessageBox.Show("Coordinates" + Environment.NewLine + "Old : " + coordinates.ToString() + Environment.NewLine + "New : " + newPoint.ToString());

                if (ZoneP1 == new Point(-1, -1))
                {
                    ZoneP1 = newPoint;
                }
                else
                {
                    ZoneP2 = newPoint;
                    CreateNewZone(ZoneP1, ZoneP2);
                    SwitchZoneCreation();
                }
                RefreshUI();
            }
        }
        /// <summary>
        /// If the user is supposed to create a window, will record the position of the clicks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pbxDriverZone_MouseClick(object sender, MouseEventArgs e)
        {
            if (CreatingWindow && pbxWindowPreview.Image != null)
            {
                Point coordinates = e.Location;

                float xOffset = (float)pbxWindowPreview.Image.Width / (float)pbxWindowPreview.Width;
                float yOffset = (float)pbxWindowPreview.Image.Height / (float)pbxWindowPreview.Height;

                Point newPoint = new Point(Convert.ToInt32((float)coordinates.X * xOffset), Convert.ToInt32((float)coordinates.Y * yOffset));

                if (WindowP1 == new Point(-1, -1))
                {
                    WindowP1 = newPoint;
                }
                else
                {
                    WindowP2 = newPoint;
                    WindowsToAdd.Add(CreateAbsoluteRectangle(WindowP1, WindowP2));

                    if (WindowsToAdd.Count < ConfigurationTool.NUMBER_OF_ZONES)
                    {
                        WindowP1 = new Point(-1, -1);
                        WindowP2 = new Point(-1, -1);
                    }
                    else
                    {
                        WindowP1 = new Point(WindowP1.X, 0);
                        WindowP2 = new Point(WindowP2.X, pbxWindowPreview.Image.Height);
                        CreateWindows(WindowsToAdd);
                        SwitchWindowCreation();
                    }
                }
                RefreshUI();
            }
        }
        /// <summary>
        /// Creates a rectangle without caring about the order of the points.
        /// </summary>
        /// <param name="p1">First point. Can be top left or bottom right</param>
        /// <param name="p2">Second point. Can be top left or bottom right</param>
        /// <returns></returns>
        private Rectangle CreateAbsoluteRectangle(Point p1, Point p2)
        {
            Point newP1 = new Point();
            Point newP2 = new Point();
            
            //Kind of a pain to have to do this but this lets the user do stupid things without the app crashing
            if (p1.X < p2.X)
            {
                newP1.X = p1.X;
                newP2.X = p2.X;
            }
            else
            {
                newP1.X = p2.X;
                newP2.X = p1.X;
            }

            if (p1.Y < p2.Y)
            {
                newP1.Y = p1.Y;
                newP2.Y = p2.Y;
            }
            else
            {
                newP1.Y = p2.Y;
                newP2.Y = p1.Y;
            }
            return new Rectangle(newP1.X, newP1.Y, newP2.X - newP1.X, newP2.Y - newP1.Y);
        }
        /// <summary>
        /// Will refresh the emulator and will controll some of the controls
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnRefresh_Click(object sender, EventArgs e)
        {
            btnRefresh.Enabled = false;
            btnCreatZone.Enabled = false;
            btnCreateWindow.Enabled = false;
            btnResetDriver.Enabled = false;
            if (Emulator == null || Emulator.GrandPrixUrl != tbxGpUrl.Text)
            {
                Emulator = new F1TVEmulator(tbxGpUrl.Text);
            }

            if (!Emulator.Ready)
            {
                Task<int> start = Task.Run(() => Emulator.Start());
                int errorCode = await start;
                if (errorCode != 0)
                {
                    string message;
                    switch (errorCode)
                    {
                        case 100:
                            message = "Error " + errorCode + " Could not recover cookies. It could be because of an improper installation of python or bad cookies in the chrome database. Please try to log on to the F1TV using chrome again";
                            break;
                        case 101:
                            message = "Error " + errorCode + " Could not start the driver. It could be because an other instance is runnin make sure you closed them all before trying again";
                            break;
                        case 102:
                            message = "Error " + errorCode + " Could not navigate on the F1TV site. Make sure the correct URL has been given and that you logged from chrome. It can take a few minutes to update";
                            break;
                        case 103:
                            message = "Error " + errorCode + " The url is not a valid url";
                            break;
                        case 104:
                            message = "Error " + errorCode + " The url is not a valid url";
                            break;
                        case 105:
                            message = "Error " + errorCode + " There has been an error trying to emulate button presses. Please try again";
                            break;
                        case 106:
                            message = "Error " + errorCode + " There has been an error trying to emulate button presses. Please try again";
                            break;
                        default:
                            message = "Could not start the emulator Error " + errorCode;
                            break;
                    }
                    MessageBox.Show(message);
                    btnRefresh.Text = "Retry";
                    btnLoadPreset.Enabled = false;
                    btnDeletePreset.Enabled = false;
                    btnSavePreset.Enabled = false;
                    btnCreatZone.Enabled = false;
                    btnCreateWindow.Enabled = false;
                    btnResetDriver.Enabled = false;
                }
                else
                {
                    btnRefresh.Text = "Get a newer image";
                    pbxPreview.Image = Emulator.Screenshot();

                    btnLoadPreset.Enabled = true;
                    btnDeletePreset.Enabled = true;
                    btnSavePreset.Enabled = true;
                    btnCreatZone.Enabled = true;
                    btnCreateWindow.Enabled = true;
                    btnResetDriver.Enabled = true;
                }
            }
            else
            {
                pbxPreview.Image = Emulator.Screenshot();
                //I know im repeating myself. This part could use a bool variable that allows those buttons to be displayed but it was the fastest way to fix a bad behaviour in the app
                btnLoadPreset.Enabled = true;
                btnDeletePreset.Enabled = true;
                btnSavePreset.Enabled = true;
                btnCreatZone.Enabled = true;
                btnCreateWindow.Enabled = true;
                btnResetDriver.Enabled = true;
            }
            btnRefresh.Enabled = true;
        }
        /// <summary>
        /// Will try to close the headless browser so the main form can launch a new one safely
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Settings_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (Emulator != null)
            {
                Emulator.Stop();
            }
            Emulator = null;
            GC.Collect();
        }
        /// <summary>
        /// Will reset the drivers
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnResetDriver_Click(object sender, EventArgs e)
        {
            if (Emulator != null)
            {
                Emulator.ResetDriver();
            }
        }
        /// <summary>
        /// Saves the current presets as a new JSON file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSavePreset_Click(object sender, EventArgs e)
        {
            string presetName = tbxPresetName.Text;
            if (Config != null)
            {
                Config.SaveToJson(DriverList, presetName);
            }
            RefreshUI();
        }
        /// <summary>
        /// Will change the selected preset. Usefull if you close this page because then the main form will keep in memory your last choice
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lsbPresets_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lsbPresets.SelectedIndex >= 0)
                SelectedConfigFile = (string)lsbPresets.Items[lsbPresets.SelectedIndex];
        }
        /// <summary>
        /// Will load an existing presets
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnLoadPreset_Click(object sender, EventArgs e)
        {
            //MessageBox.Show(lsbPresets.SelectedIndex.ToString());
            if (lsbPresets.SelectedIndex >= 0 && pbxPreview.Image != null)
            {
                try
                {
                    string fileName = lsbPresets.Items[lsbPresets.SelectedIndex].ToString();
                    Reader reader = new Reader(fileName, (Bitmap)pbxPreview.Image, false);
                    //MainZones #0 is the big main zone containing driver zones
                    Config = new ConfigurationTool((Bitmap)pbxPreview.Image, reader.MainZones[0].Bounds);
                    Config.MainZone = reader.MainZones[0];
                    DriverList = reader.Drivers;
                    SelectedConfigFile = fileName;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Could not load the settings error :" + ex);
                }
                RefreshUI();
            }
        }
        /// <summary>
        /// This will be called everytime the form resizes. Here we are making the form responsive
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Settings_Resize(object sender, EventArgs e)
        {
            int xDiff = this.Width - oldSize.Width;
            int yDiff = this.Height - oldSize.Height;

            gpbxPreview.Size = new Size(oldGpbxPreviewSize.Width + xDiff, oldGpbxPreviewSize.Height + yDiff);
            gpbxWindowPreview.Size = new Size(oldGpbxWindowPreviewSize.Width + xDiff, oldGpbxWindowPreviewSize.Height);
            pbxPreview.Size = new Size(oldPbxPreviewSize.Width + xDiff, oldPbxPreviewSize.Height + yDiff);
            pbxWindowPreview.Size = new Size(oldPbxWindowPreviewSize.Width + xDiff, oldPbxWindowPreviewSize.Height);
        }
        /// <summary>
        /// Will delete an existing preset
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDeletePreset_Click(object sender, EventArgs e)
        {
            int selectedIndex = lsbPresets.SelectedIndex;
            if (selectedIndex >= 0)
            {
                string fileName = lsbPresets.Items[selectedIndex].ToString();
                if (File.Exists(fileName))
                {
                    File.Delete(fileName);
                    RefreshUI();
                }
                else
                {
                    MessageBox.Show("Could not delete the preset because it does not exists");
                }
            }
        }
        /// <summary>
        /// Sketchy method that is used to remove the borders from groupboxes... Yes its dumb but I dont think there is any other way
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void removeBorders(object sender, PaintEventArgs e)
        {
            GroupBox gpbx = (GroupBox)sender;

            using (Pen pen = new Pen(gpbx.BackColor, 50))
            {
                e.Graphics.DrawRectangle(pen, 0, 0, gpbx.Width - 1, gpbx.Height - 1);
                e.Graphics.DrawRectangle(pen, 0, 0, gpbx.Width - 1, gpbx.Height - 1);
            }

            using (var brush = new SolidBrush(gpbx.ForeColor))
            {
                var textPosition = new Point(5, 0); // Adjust the X and Y values as needed
                e.Graphics.DrawString(gpbx.Text, gpbx.Font, brush, textPosition);
            }
        }
    }
}
