/// Author : Maxime Rohmer
/// Date : 09/08/2023
/// File : SqliteStorage.cs
/// Brief : Class that controls the sqlite database
/// Version : Beta 1.0

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SQLite;
using System.IO;
using System.Windows.Forms;

namespace TrackTrends
{
    public class SqliteStorage
    {
        private const string DATABASE_FOLDER = "./Data";
        private const string DATABASE_FILE = "/database.sqlite";
        private const string CONNECTION_STRING = "Data Source=" + DATABASE_FOLDER + DATABASE_FILE + ";Version=3;";

        private SQLiteConnection Connection;
        /// <summary>
        /// Creates a new Sqlite Storage and initialize the database
        /// </summary>
        public SqliteStorage()
        {
            Load();
        }
        /// <summary>
        /// Loads a fresh new Database or create a new one if it does not exist.
        /// </summary>
        private void Load()
        {
            if (!Directory.Exists(DATABASE_FOLDER))
                Directory.CreateDirectory(DATABASE_FOLDER);

            if (!File.Exists(DATABASE_FOLDER + DATABASE_FILE))
            {
                SQLiteConnection.CreateFile(DATABASE_FOLDER + DATABASE_FILE);
            }
            else
            {
                //We are not using the existing DataBase
                File.Delete(DATABASE_FOLDER + DATABASE_FILE);
            }

            Connection = new SQLiteConnection(CONNECTION_STRING);
            Connection.Open();

            //Create the drivers table
            string createDriversTableQuery = @"CREATE TABLE IF NOT EXISTS Drivers 
                (ID INTEGER PRIMARY KEY AUTOINCREMENT,
                Name VARCHAR NOT NULL);";
            using (var command = new SQLiteCommand(createDriversTableQuery, Connection))
            {
                command.ExecuteNonQuery();
            }

            //Create the drivers table
            string createPitstopTableQuery = @"CREATE TABLE Pitstops
                (Lap INTEGER NOT NULL,
                DriverID INTEGER NOT NULL,
                Tyre VARCHAR,
                PRIMARY KEY (Lap,DriverID));";
            using (var command = new SQLiteCommand(createPitstopTableQuery, Connection))
            {
                command.ExecuteNonQuery();
            }

            //Create the stats
            string createStatsTableQuery = @"CREATE TABLE IF NOT EXISTS Stats 
                (Lap INTEGER NOT NULL,
                DriverID INTEGER NOT NULL,
                Tyre VARCHAR NOT NULL,
                LapTime INTEGER NOT NULL,
                Sector1 INTEGER NOT NULL,
                Sector2 INTEGER NOT NULL,
                Sector3 INTEGER NOT NULL,
                GapToLeader INTEGER NOT NULL,
                Position INTEGER NOT NULL,
                PRIMARY KEY (Lap, DriverID));";
            using (var command = new SQLiteCommand(createStatsTableQuery, Connection))
            {
                command.ExecuteNonQuery();
            }
        }
        /// <summary>
        /// Adds a driver into the drivers table. Meant to be used at the start of the programm
        /// </summary>
        /// <param name="name">The name of the driver. (non case sensitive)</param>
        public void AddDriver(string name)
        {
            string insertQuery = "INSERT INTO Drivers (Name) VALUES (@name);";

            using (var command = new SQLiteCommand(insertQuery,Connection))
            {
                command.Parameters.AddWithValue("@Name",name);

                try
                {
                    command.ExecuteNonQuery();
                }
                catch
                {
                    //MessageBox.Show("An error has occured while trying to insert a new driver into de Database");
                }
            }
        }
        /// <summary>
        /// Searches for a driver and returns its id if it has been found
        /// </summary>
        /// <param name="name">Name of the driver (non case sensitive)</param>
        /// <returns></returns>
        private int GetDriverID(string name)
        {
            string selectQuery = "SELECT ID FROM Drivers where Name LIKE @driverName";
            int result = 0;
            using (var command = new SQLiteCommand(selectQuery,Connection))
            {
                command.Parameters.AddWithValue("@driverName",name);
                try
                {
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            result = reader.GetInt32(0);
                        }
                    }
                }
                catch
                {
                    //MessageBox.Show("There has been an error while trying to retrieve the ID of a Driver from the database");
                }
            }
            return result;
        }
        /// <summary>
        /// Gets the sectors from a lapTime. Sectors are subdivisions of a laptime (could be usefull to validate one or the other)
        /// </summary>
        /// <param name="driverName">The name of the driver who has done the lap</param>
        /// <param name="lap">The lap at wich the driver has done the time</param>
        /// <returns>A list of the different sectors time in int (ms)</returns>
        public List<int> GetSectorsFromLapTime(string driverName,int lap)
        {
            int driverId = GetDriverID(driverName);
            string selectQuery = "SELECT Sector1,Sector2,Sector3 FROM Stats WHERE DriverID = @driverID AND Lap = @lap";
            List<int> result = new List<int>();
            using (var command = new SQLiteCommand(selectQuery, Connection))
            {
                command.Parameters.AddWithValue("@driverID", driverId);
                command.Parameters.AddWithValue("@lap", lap);
                try
                {
                    SQLiteDataReader reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        result.Add(reader.GetInt32(0));
                        result.Add(reader.GetInt32(1));
                        result.Add(reader.GetInt32(2));
                    }
                }
                catch
                {
                    //MessageBox.Show("There has been an error while trying to retrieve the ID of a Driver from the database");
                }
            }
            return result;
        }
        /// <summary>
        /// Get the laptime history of a driver
        /// </summary>
        /// <param name="driverName">The name of the driver</param>
        /// <param name="numberOfLaptimes">The number of lapTimes you want</param>
        /// <returns>A list of tuples with the lap and the laptime. It will only return the amount it found so even if you ask 5 expect getting less or even 0</returns>
        public List<(int LapTime, int Lap)> GetDriverLaptimes(string driverName,int numberOfLaptimes)
        {
            int driverId = GetDriverID(driverName);
            List<(int LapTime, int Lap)> lapData = new List<(int LapTime, int Lap)>();
            string selectQuery = "Select LapTime,Lap from Stats WHERE DriverID = @driverID ORDER BY Lap DESC LIMIT @limit";
            using (var command = new SQLiteCommand(selectQuery, Connection))
            {
                command.Parameters.AddWithValue("@driverID", driverId);
                command.Parameters.AddWithValue("@limit", numberOfLaptimes);
                try
                {
                    SQLiteDataReader reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        int lapTime = reader.GetInt32(0);
                        int lap = reader.GetInt32(1); 
                        lapData.Add((lapTime, lap));
                    }
                }
                catch
                {
                    //MessageBox.Show("There has been an error while trying to retrieve the ID of a Driver from the database");
                }
            }
            return lapData;
        }
        /// <summary>
        /// Add a pitstop into the db
        /// </summary>
        /// <param name="driverName">The name of the driver who made his pitstop</param>
        /// <param name="lap">The lap where he stopped</param>
        /// <param name="tyre">The tyre he took out</param>
        public void AddPitstop(string driverName,int lap,string tyre)
        {
            string insertQuery = "INSERT INTO Pitstops (Lap,DriverID,Tyre) VALUES (@Lap,@DriverID,@Tyre)";

            using (var command = new SQLiteCommand(insertQuery,Connection))
            {
                command.Parameters.AddWithValue("@Lap",lap);
                command.Parameters.AddWithValue("@DriverID",GetDriverID(driverName));
                command.Parameters.AddWithValue("@Tyre",tyre);
                try
                {
                    command.ExecuteNonQuery();
                }
                catch
                {
                    //MessageBox.Show("An error has occured while trying to insert a new pitstop into the DB" + Environment.NewLine + "Request :"+ command.ToString());
                }
            }
        }
        /// <summary>
        /// Adds drivers info into the DB (it should only be once per lap)
        /// </summary>
        /// <param name="data">The Driver data</param>
        /// <param name="lap">The lap from wich the datas are from</param>
        public void AddDriverStat(DriverData data,int lap)
        {
            string insertQuery = "INSERT INTO Stats (Lap,DriverID,Tyre,LapTime,Sector1,Sector2,Sector3,GapToLeader,Position) VALUES (@Lap,@DriverID,@Tyre,@LapTime,@Sector1,@Sector2,@Sector3,@GapToLeader,@Position);";

            using (var command = new SQLiteCommand(insertQuery,Connection))
            {
                command.Parameters.AddWithValue("@Lap",lap);
                command.Parameters.AddWithValue("@DriverID",GetDriverID(data.Name));
                command.Parameters.AddWithValue("@Tyre",data.CurrentTyre.Coumpound.ToString());
                command.Parameters.AddWithValue("@LapTime",data.LapTime);
                command.Parameters.AddWithValue("@Sector1",data.Sector1);
                command.Parameters.AddWithValue("@Sector2", data.Sector2);
                command.Parameters.AddWithValue("@Sector3", data.Sector3);
                command.Parameters.AddWithValue("@GapToLeader", data.GapToLeader);
                command.Parameters.AddWithValue("@Position", data.Position);

                try
                {
                    command.ExecuteNonQuery();
                }
                catch
                {
                    //MessageBox.Show("An error has occured while trying to insert infos about a driver");
                }
            }
        }
    }
}
