# DataWrapper.cs ``` cs /// Author : Maxime Rohmer /// Date : 30/05/2023 /// File : DataWrapper.cs /// Brief : Class that is used to interface between the main Form (vue) and the Storage (wich is a class that wraps the sqlite database, so the model) its almost MVC :D /// Version : Alpha 1.0 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Drawing; namespace TrackTrends { internal class DataWrapper { private Reader Reader; private SqliteStorage Storage; List> LiveDriverDataLogs = new List>(); //Note : It could be usefull to get the mainForm at the start of the programm and not have to take it in half of the methods. /// /// Constructs a new DataWrapper. It needs the config file so it can create a Reader, It also needs a first screenshot for the same reason /// /// The JSON config file that is created by the configuration tool /// A screenshot of the public DataWrapper(string configFile, Bitmap screenshot) { Reader = new Reader(configFile, screenshot, true); //The Storage is here and on the Reader. It seems bad but it is ok as we dont use it at all to insert data and are only using it here to read some. The reader takes care of the inserts (Note: We could technically do both here but I did not find it usefull to transfer everything here) Storage = Reader.Storage; } /// /// Refreshes the controller so it has the latest driver datas (Be sure to call it everytime you need to use any other method and expects the data to be up to date) /// /// Error code, 0 is success, 1 is not (Note: Maybe it could be interesting in the future to add some more error handling here) public int Refresh() { LiveDriverDataLogs.Add(Reader.Decode(Reader.MainZones, Reader.Drivers)); if (LiveDriverDataLogs.Count > 0) return 0; return 1; } /// /// Changes the image to the newest screenshot in all of the zones and windows /// /// The new screenshot to put everywhere (Do not mix resolutions) public void ChangeImage(Bitmap image) { Reader.ChangeImage(image); } /// /// Gets all the data from one driver and also displays into the given panel the last five laps (or less if its the sart of the race) Note: Its responsive :D /// /// The name of the driver (should not be case sensitive but it MUST already exist in the first list that has been inserted into the DB) /// The pannel where you want the five last laps to be displayed /// The Main form. /// public DriverData GetFullDriverData(string driverName, Panel lastFiveLapsPanel, Main form1) { //Note : I know that its a bad idea to ask the Form in this method and some others because it means that it wont work with any main form. And to that Ill say that... your right ! DriverData result = new DriverData(); if (LiveDriverDataLogs.Count > 0) { //Searches the most recent live data from the given driverName foreach (DriverData data in LiveDriverDataLogs[LiveDriverDataLogs.Count - 1]) { if (data.Name == driverName) result = data; } if (result.Name != "") { //Recovers and displays the last five laps from the driver lastFiveLapsPanel.Controls.Clear(); Size labelDimensions = new Size(lastFiveLapsPanel.Width, lastFiveLapsPanel.Height / 5); List<(int LapTime, int Lap)> lapsInfos = Storage.GetDriverLaptimes(driverName, 5); int id = 0; foreach ((int LapTime, int Lap) lapData in lapsInfos) { //Hardcodes the new button. //Note : It could be smart to have like a default button for all the methods to use without needing to rewrite everything. Button newButton = new Button(); lastFiveLapsPanel.Controls.Add(newButton); newButton.Name = driverName + "_" + lapData.Lap; newButton.Text = Reader.ConvertMsToTime(lapData.LapTime); newButton.Size = labelDimensions; newButton.FlatStyle = FlatStyle.Popup; newButton.Click += form1.btnLapTime_Click; newButton.Location = new Point(0, id * newButton.Height); id++; } } } return result; } /// /// Runs trough every drivers live data to recover the drivers that are close to each others /// /// The control that will host the displayed battles /// The main form. It needs to have a method called 'btnDriver_Click' so it can reads the buttons clicks public void DisplayBattles(Panel pnlBattles,Main form1) { DriverData oldDriver = null; List<(DriverData d1, DriverData d2, int gap)> battles = new List<(DriverData d1, DriverData d2, int gap)>(); //Search trough all the drivers and finds the one battling foreach (DriverData driver in LiveDriverDataLogs[LiveDriverDataLogs.Count - 1]) { if (oldDriver != null && driver.Position != -1 && oldDriver.Position != -1) { if (driver.GapToLeader < oldDriver.GapToLeader) { //There is a problem with the drivers gaps } else { int gap = driver.GapToLeader - oldDriver.GapToLeader; //3000ms is 3s. If drivers are that close then they are definitely in battle. If they are farther then maybe not if (gap <= 3000) { battles.Add((oldDriver, driver, gap)); } } oldDriver = driver; } else { oldDriver = driver; } } //We will only display 4 battles max int maxBattles = 4; if (battles.Count > 0) { pnlBattles.Controls.Clear(); int maxUiHeight = Math.Max(pnlBattles.Height / maxBattles, pnlBattles.Height / battles.Count); int id = 0; foreach ((DriverData d1, DriverData d2, int gap) battle in battles) { if(id < maxBattles) { //*hardcoding* the different controls that needs to be added to the panel. //Note : this stuff could totally be handled by the Form with method returning a list of the drivers. It was just easier for me at the time to code it this way but its not the prettiest Button btnFirstDriver = new Button(); Button btnSecondDriver = new Button(); Label lblGap = new Label(); pnlBattles.Controls.Add(btnFirstDriver); pnlBattles.Controls.Add(lblGap); pnlBattles.Controls.Add(btnSecondDriver); btnFirstDriver.Anchor = AnchorStyles.Left | AnchorStyles.Top; btnSecondDriver.Anchor = AnchorStyles.Right | AnchorStyles.Top; lblGap.Anchor = AnchorStyles.Right | AnchorStyles.Left | AnchorStyles.Top; lblGap.TextAlign = ContentAlignment.MiddleCenter; lblGap.Font = new Font(lblGap.Font.FontFamily, 15); btnFirstDriver.Click += form1.btnDriver_Click; btnSecondDriver.Click += form1.btnDriver_Click; btnFirstDriver.FlatStyle = FlatStyle.Popup; btnSecondDriver.FlatStyle = FlatStyle.Popup; lblGap.FlatStyle = FlatStyle.Popup; btnFirstDriver.Size = new Size(pnlBattles.Width / 3, maxUiHeight); btnSecondDriver.Size = new Size(pnlBattles.Width / 3, maxUiHeight); lblGap.Size = new Size(pnlBattles.Width / 3, maxUiHeight); btnFirstDriver.Location = new Point(pnlBattles.Width / 3 * 0, id * maxUiHeight); lblGap.Location = new Point(pnlBattles.Width / 3 * 1, id * maxUiHeight); btnSecondDriver.Location = new Point(pnlBattles.Width / 3 * 2, id * maxUiHeight); btnFirstDriver.Text = battle.d1.Name; lblGap.Text = "+ " + Reader.ConvertMsToTime(battle.gap); if (battle.gap <= 2000) lblGap.ForeColor = Color.Yellow; if (battle.gap <= 1000) lblGap.ForeColor = Color.Green; btnSecondDriver.Text = battle.d2.Name; btnFirstDriver.Name = battle.d1.Name + "_" + id; lblGap.Name = "lbl_Gap_" + id; btnSecondDriver.Name = battle.d2.Name + "_" + id; } else { break; } id++; } } } /// /// Searches the fastest and slowests drivers and displays them in the given panels /// /// Panel that will contain the constructed controls /// Panel that will contain the constructed controls /// The main form that needs to implement the method btnDriver_Click to allow it to recover custom buttons click public void DisplayTimesDeltas(Panel pnlFastest,Panel pnlSlowest, Main form1) { List<(int avg, string driverName)> averages = new List<(int avg, string driverName)>(); foreach (DriverData driver in LiveDriverDataLogs[LiveDriverDataLogs.Count - 1]) { //We want to recover the last 5 lap times List<(int lapTime,int lap)> laps = Storage.GetDriverLaptimes(driver.Name,5); if(laps.Count > 0) { int avg = 0; foreach ((int lapTime, int lap) lap in laps) { avg += lap.lapTime; } avg = avg / laps.Count; averages.Add((avg, driver.Name)); } } int numberOfDriversToShow = 5; if (averages.Count > 0 && averages.Count > numberOfDriversToShow) { averages = averages.OrderBy(item => item.avg).ToList(); pnlFastest.Controls.Clear(); pnlSlowest.Controls.Clear(); int maxUiSize = pnlFastest.Height / numberOfDriversToShow; //Displays the fastest drivers for (int i = 0; i < numberOfDriversToShow; i++) { Button newButton = new Button(); (int avg, string driver) data = averages[i]; pnlFastest.Controls.Add(newButton); newButton.Size = new Size(pnlFastest.Width, maxUiSize); newButton.Location = new Point(0, i * maxUiSize); newButton.Text = data.driver; newButton.FlatStyle = FlatStyle.Popup; newButton.Name = data.driver + "_fastest_" + i; newButton.Click += form1.btnDriver_Click; //We take the average time lost per lap if (i != 0) newButton.Text += " + " + Reader.ConvertMsToTime(Convert.ToInt32(((float)data.avg - (float)averages[0].avg) / 5.0f)); } //Displays the slowests drivers int badId = 0; for (int i = averages.Count -1; i >= averages.Count - numberOfDriversToShow; i--) { Button newButton = new Button(); (int avg, string driver) data = averages[i]; pnlSlowest.Controls.Add(newButton); newButton.Size = new Size(pnlFastest.Width, maxUiSize); newButton.Location = new Point(0, badId * maxUiSize); newButton.Text = data.driver; newButton.FlatStyle = FlatStyle.Popup; newButton.Name = data.driver + "_slowest_" + i; newButton.Click += form1.btnDriver_Click; //We take the average time lost per lap newButton.Text += " + " + Reader.ConvertMsToTime(Convert.ToInt32(((float)data.avg - (float)averages[0].avg) / 5.0f)); badId++; } } } /// /// Will add to the list of overtakes the different changes of position /// /// The listbox containing all the infos public void DisplayOvertakes(ListBox lsbResult) { //Note : This method SHOULD REALLY not do this but just return a string or a list of string with the new overtakes so the form can handle it as it wishes if (LiveDriverDataLogs.Count > 1) { List oldList = LiveDriverDataLogs[LiveDriverDataLogs.Count - 2]; List newList = LiveDriverDataLogs[LiveDriverDataLogs.Count - 1]; for (int i = 0; i < LiveDriverDataLogs[LiveDriverDataLogs.Count - 1].Count;i++) { if (oldList[i].Name != newList[i].Name) { //There has been a change in the standings for(int y = 0; y < oldList.Count;y++) { if (newList[y].Name == oldList[i].Name) { //We found its new location if (y > i) { //The driver overtook someone lsbResult.Items.Add(newList[y].Name + " climbed to " + y); } else { //The driver got overtook by someone lsbResult.Items.Add(newList[y].Name + " fell to " + y); } } } } } } } /// /// Displays a messageBox containing the infos about a lap time /// /// The name of the driver that has done the lapTime /// The number of the lap on wich the lapTime has been set (CAUTION ITS NOT THE RACING LAP ITS FROM THE DB) /// The time (in ms) of the lap public void DisplayLapTimeInfos(string driverName, int Lap, string LapTime) { List sectors = Storage.GetSectorsFromLapTime(driverName, Lap); string message = "Lap time infos" + Environment.NewLine; message += LapTime + Environment.NewLine; if (sectors.Count > 0) message += "Sector 1 : " + Reader.ConvertMsToTime(sectors[0]) + Environment.NewLine; if (sectors.Count > 1) message += "Sector 2 : " + Reader.ConvertMsToTime(sectors[1]) + Environment.NewLine; if (sectors.Count > 2) message += "Sector 3 : " + Reader.ConvertMsToTime(sectors[2]) + Environment.NewLine; MessageBox.Show(message); } /// /// Displays the live ranking with the names of the drivers and their gap to the leader in the right order /// /// The control that will host all the new controls /// The main form public void DisplayLiveRanking(Panel pnl, Main form1) { if (LiveDriverDataLogs.Count > 0) { pnl.Controls.Clear(); //Gets the last item that should be the most recent data List liveData = LiveDriverDataLogs[LiveDriverDataLogs.Count - 1]; Button[] buttons = new Button[liveData.Count]; Size buttonDimensions = new Size(pnl.Width, pnl.Height / liveData.Count); for (int driverCount = 0; driverCount < liveData.Count; driverCount++) { Button newButton = new Button(); newButton.Size = buttonDimensions; newButton.Location = new Point(0, driverCount * buttonDimensions.Height); newButton.FlatStyle = FlatStyle.Popup; DriverData driver = liveData[driverCount]; if (driver.Position == -1) { //Its a DNF newButton.Enabled = false; } if (driver.Position > 1) { newButton.Text = driver.Name + " +" + Reader.ConvertMsToTime(driver.GapToLeader); } else { newButton.Text = driver.Name; } newButton.Name = liveData[driverCount].Name; newButton.TextAlign = ContentAlignment.MiddleLeft; newButton.FlatStyle = FlatStyle.Popup; newButton.Click += form1.btnDriver_Click; buttons[driverCount] = newButton; } //Note : It could be better to have this directly in the same loop foreach (Button button in buttons) { pnl.Controls.Add(button); } } } } } ```