From aa3733c0a767ea544938f093209e0beb4c646029 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Thu, 29 Dec 2022 17:03:29 -0500
Subject: [PATCH] Status Submenu: add basic [PayoutView] GUI <-> API UI

---
 src/constants.rs |  6 +++++
 src/disk.rs      | 68 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/main.rs      |  2 +-
 src/status.rs    | 49 +++++++++++++++++++++++++++++++++-
 src/xmr.rs       | 30 +++++++++++++++++++++
 5 files changed, 153 insertions(+), 2 deletions(-)

diff --git a/src/constants.rs b/src/constants.rs
index 4a5dcb7..79b82de 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -175,6 +175,12 @@ pub const STATUS_XMRIG_THREADS:     &str = "The amount of threads XMRig is curre
 pub const STATUS_SUBMENU_PROCESSES: &str = "View the status of process related data for [Gupax|P2Pool|XMRig]";
 pub const STATUS_SUBMENU_P2POOL:    &str = "View P2Pool specific data";
 pub const STATUS_SUBMENU_MONERO:    &str = "View general Monero blockchain data";
+pub const STATUS_SUBMENU_PAYOUT:    &str = "The total amount of payouts received via P2Pool across all time";
+pub const STATUS_SUBMENU_XMR:       &str = "The total of XMR mined via P2Pool across all time";
+pub const STATUS_SUBMENU_LATEST:    &str = "Sort the logs latest to oldest";
+pub const STATUS_SUBMENU_OLDEST:    &str = "Sort the logs oldest to latest";
+pub const STATUS_SUBMENU_BIGGEST:   &str = "Sort the logs by biggest payouts first";
+pub const STATUS_SUBMENU_SMALLEST:  &str = "Sort the logs by smallest payouts first";
 
 // Gupax
 pub const GUPAX_UPDATE:           &str = "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
diff --git a/src/disk.rs b/src/disk.rs
index 09f8d8c..ad001db 100644
--- a/src/disk.rs
+++ b/src/disk.rs
@@ -561,9 +561,12 @@ impl Pool {
 #[derive(Clone,Debug)]
 pub struct GupaxP2poolApi {
 	pub log: String,           // Log file only containing full payout lines
+	pub log_rev: String,       // Same as above but reversed based off lines
 	pub payout: HumanNumber,   // Human-friendly display of payout count
 	pub payout_u64: u64,       // [u64] version of above
 	pub payout_ord: PayoutOrd, // Ordered Vec of payouts, see [PayoutOrd]
+	pub payout_low: String,    // A pre-allocated/computed [String] of the above Vec from low payout to high
+	pub payout_high: String,   // Same as above but high -> low
 	pub xmr: AtomicUnit,       // XMR stored as atomic units
 	pub path_log: PathBuf,     // Path to [log]
 	pub path_payout: PathBuf,  // Path to [payout]
@@ -576,9 +579,12 @@ impl GupaxP2poolApi {
 	pub fn new() -> Self {
 		Self {
 			log: String::new(),
+			log_rev: String::new(),
 			payout: HumanNumber::unknown(),
 			payout_u64: 0,
 			payout_ord: PayoutOrd::new(),
+			payout_low: String::new(),
+			payout_high: String::new(),
 			xmr: AtomicUnit::new(),
 			path_xmr: PathBuf::new(),
 			path_payout: PathBuf::new(),
@@ -624,6 +630,34 @@ impl GupaxP2poolApi {
 		Ok(())
 	}
 
+	pub fn update_log_rev(&mut self) {
+		let mut log_rev = String::with_capacity(self.log.len());
+		for line in self.log.lines().rev() {
+			log_rev.push_str(line);
+			log_rev.push_str("\n");
+		}
+		self.log_rev = log_rev;
+	}
+
+	pub fn append_head_log_rev(&mut self, line: &str) {
+		self.log_rev = format!("{}\n{}", line, self.log_rev);
+	}
+
+	pub fn update_payout_low(&mut self) {
+		self.payout_ord.sort_payout_low_to_high();
+		self.payout_low = self.payout_ord.to_string();
+	}
+
+	pub fn update_payout_high(&mut self) {
+		self.payout_ord.sort_payout_high_to_low();
+		self.payout_high = self.payout_ord.to_string();
+	}
+
+	pub fn update_payout_strings(&mut self) {
+		self.update_payout_low();
+		self.update_payout_high();
+	}
+
 	pub fn read_all_files_and_update(&mut self) -> Result<(), TomlError> {
 		let payout_u64 = match read_to_string(File::Payout, &self.path_payout)?.trim().parse::<u64>() {
 			Ok(o)  => o,
@@ -636,6 +670,7 @@ impl GupaxP2poolApi {
 		let payout = HumanNumber::from_u64(payout_u64);
 		let log    = read_to_string(File::Log, &self.path_log)?;
 		self.payout_ord.update_from_payout_log(&log);
+		self.update_payout_strings();
 		*self = Self {
 			log,
 			payout,
@@ -643,16 +678,19 @@ impl GupaxP2poolApi {
 			xmr,
 			..std::mem::take(self)
 		};
+		self.update_log_rev();
 		Ok(())
 	}
 
 	// Takes the log line and (date, atomic_unit, block) and updates [self] and the [PayoutOrd]
 	pub fn add_payout(&mut self, log_payout_line: &str, date: String, atomic_unit: AtomicUnit, block: HumanNumber) {
 		self.log.push_str(log_payout_line);
+		self.append_head_log_rev(log_payout_line);
 		self.payout_u64 += 1;
 		self.payout = HumanNumber::from_u64(self.payout_u64);
 		self.xmr = self.xmr.add_self(atomic_unit);
 		self.payout_ord.push(date, atomic_unit, block);
+		self.update_payout_strings();
 	}
 
 	pub fn write_to_all_files(&self) -> Result<(), TomlError> {
@@ -764,6 +802,34 @@ impl Display for Submenu {
 	}
 }
 
+//---------------------------------------------------------------------------------------------------- [PayoutView] enum for [Status/P2Pool] tab
+// The enum buttons for selecting which "view" to sort the payout log in.
+#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
+pub enum PayoutView {
+	Latest,   // Shows the most recent logs first
+	Oldest,   // Shows the oldest logs first
+	Biggest,  // Shows highest to lowest payouts
+	Smallest, // Shows lowest to highest payouts
+}
+
+impl PayoutView {
+	fn new() -> Self {
+		Self::Latest
+	}
+}
+
+impl Default for PayoutView {
+	fn default() -> Self {
+		Self::new()
+	}
+}
+
+impl Display for PayoutView {
+	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+		write!(f, "{}", self)
+	}
+}
+
 //---------------------------------------------------------------------------------------------------- [Hash] enum for [Status/P2Pool]
 #[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
 pub enum Hash {
@@ -856,6 +922,7 @@ pub struct State {
 #[derive(Clone,PartialEq,Debug,Deserialize,Serialize)]
 pub struct Status {
 	pub submenu: Submenu,
+	pub payout_view: PayoutView,
 	pub monero_enabled: bool,
 	pub manual_hash: bool,
 	pub hashrate: f64,
@@ -941,6 +1008,7 @@ impl Default for Status {
 	fn default() -> Self {
 		Self {
 			submenu: Submenu::default(),
+			payout_view: PayoutView::default(),
 			monero_enabled: false,
 			manual_hash: false,
 			hashrate: 0.0,
diff --git a/src/main.rs b/src/main.rs
index d1797c3..13d3fd5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1742,7 +1742,7 @@ XMRig console byte length: {}\n
 				}
 				Tab::Status => {
 					debug!("App | Entering [Status] Tab");
-					crate::disk::Status::show(&mut self.state.status, &self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.p2pool_img, &self.xmrig_img, p2pool_is_alive, xmrig_is_alive, self.max_threads, self.width, self.height, ctx, ui);
+					crate::disk::Status::show(&mut self.state.status, &self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.p2pool_img, &self.xmrig_img, p2pool_is_alive, xmrig_is_alive, self.max_threads, &self.gupax_p2pool_api, self.width, self.height, ctx, ui);
 				}
 				Tab::Gupax => {
 					debug!("App | Entering [Gupax] Tab");
diff --git a/src/status.rs b/src/status.rs
index 1395189..7861b80 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -25,6 +25,8 @@ use crate::{
 	Hash,
 	Submenu,
 	macros::*,
+	GupaxP2poolApi,
+	PayoutView,
 };
 use std::sync::{Arc,Mutex};
 use log::*;
@@ -32,10 +34,12 @@ use egui::{
 	Label,RichText,TextStyle,
 	TextStyle::Monospace,
 	TextStyle::Name,
+	TextEdit,
+	SelectableLabel,
 };
 
 impl crate::disk::Status {
-pub fn show(&mut self, sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>, p2pool_img: &Arc<Mutex<ImgP2pool>>, xmrig_img: &Arc<Mutex<ImgXmrig>>, p2pool_alive: bool, xmrig_alive: bool, max_threads: usize, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
+pub fn show(&mut self, sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>, p2pool_img: &Arc<Mutex<ImgP2pool>>, xmrig_img: &Arc<Mutex<ImgXmrig>>, p2pool_alive: bool, xmrig_alive: bool, max_threads: usize, gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
 	//---------------------------------------------------------------------------------------------------- [Processes]
 	if self.submenu == Submenu::Processes {
 	let width = (width/3.0)-(SPACE*1.666);
@@ -123,7 +127,50 @@ pub fn show(&mut self, sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolAp
 		drop(api);
 	})});
 	});
+	//---------------------------------------------------------------------------------------------------- [P2Pool]
 	} else if self.submenu == Submenu::P2pool {
+	let mut api = lock!(gupax_p2pool_api);
+	let text = height / 25.0;
+	let log = height / 2.4;
+	ui.style_mut().override_text_style = Some(Monospace);
+	// Payout Text + PayoutView buttons
+	ui.group(|ui| {
+		ui.horizontal(|ui| {
+			let width = (width/3.0)-(SPACE*4.0);
+			ui.add_sized([width, text], Label::new(RichText::new(format!("Total Payouts: {}", api.payout)).underline().color(LIGHT_GRAY))).on_hover_text(STATUS_SUBMENU_PAYOUT);
+			ui.separator();
+			ui.add_sized([width, text], Label::new(RichText::new(format!("Total XMR: {}", api.xmr)).underline().color(LIGHT_GRAY))).on_hover_text(STATUS_SUBMENU_XMR);
+			let width = width / 4.0;
+			ui.separator();
+			if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Latest, "Latest")).on_hover_text(STATUS_SUBMENU_LATEST).clicked() { self.payout_view = PayoutView::Latest; }
+			ui.separator();
+			if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Oldest, "Oldest")).on_hover_text(STATUS_SUBMENU_OLDEST).clicked() { self.payout_view = PayoutView::Oldest; }
+			ui.separator();
+			if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Biggest, "Biggest")).on_hover_text(STATUS_SUBMENU_BIGGEST).clicked() {
+				api.update_payout_high();
+				self.payout_view = PayoutView::Biggest;
+			}
+			ui.separator();
+			if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Smallest, "Smallest")).on_hover_text(STATUS_SUBMENU_SMALLEST).clicked() {
+				api.update_payout_low();
+				self.payout_view = PayoutView::Smallest;
+			}
+		});
+		ui.separator();
+		// Actual logs
+		egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
+			egui::ScrollArea::vertical().stick_to_bottom(self.payout_view == PayoutView::Oldest).max_width(width).max_height(log).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
+				match self.payout_view {
+					PayoutView::Latest   => ui.add_sized([width, log], TextEdit::multiline(&mut api.log.as_str())),
+					PayoutView::Oldest   => ui.add_sized([width, log], TextEdit::multiline(&mut api.log_rev.as_str())),
+					PayoutView::Biggest  => ui.add_sized([width, log], TextEdit::multiline(&mut api.payout_high.as_str())),
+					PayoutView::Smallest => ui.add_sized([width, log], TextEdit::multiline(&mut api.payout_low.as_str())),
+				};
+			});
+		});
+	});
+	drop(api);
+	//---------------------------------------------------------------------------------------------------- [Monero]
 	} else if self.submenu == Submenu::Monero {
 	}
 }
diff --git a/src/xmr.rs b/src/xmr.rs
index 6163748..3082fd7 100644
--- a/src/xmr.rs
+++ b/src/xmr.rs
@@ -225,10 +225,18 @@ impl PayoutOrd {
 		self.0.sort_by(|a, b| b.1.0.cmp(&a.1.0));
 	}
 
+	// These sorting functions take around [0.0035~] seconds on a Ryzen 5950x
+	// given a Vec filled with 1_000_000 elements, not bad.
 	pub fn sort_payout_low_to_high(&mut self) {
 		self.0.sort_by(|a, b| a.1.0.cmp(&b.1.0));
 	}
 
+	// Returns a reversed [Iter] of the [PayoutOrd]
+	// This is obviously faster than actually reordering the Vec.
+	pub fn rev_iter(&self) -> std::iter::Rev<std::slice::Iter<'_, (String, AtomicUnit, HumanNumber)>> {
+		self.0.iter().rev()
+	}
+
 	// Recent <-> Oldest relies on the line order.
 	// The raw log lines will be shown instead of this struct.
 }
@@ -354,4 +362,26 @@ r#"2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321
 		println!("2: {:#?}", payout_ord);
 		assert!(PayoutOrd::is_same(&payout_ord, &payout_ord_2) == false);
 	}
+
+	#[test]
+	fn view_reverse_payout_ord() {
+		use crate::xmr::PayoutOrd;
+		use crate::xmr::AtomicUnit;
+		use crate::human::HumanNumber;
+		let mut payout_ord = PayoutOrd::from_vec(vec![
+			("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u64(1000000000), HumanNumber::from_u64(2654321)),
+			("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(2000000000), HumanNumber::from_u64(2654322)),
+			("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(3000000000), HumanNumber::from_u64(2654323)),
+		]);
+		println!("OG: {:#?}", payout_ord);
+
+		for (_, atomic_unit, _) in payout_ord.rev_iter() {
+			if atomic_unit.to_u64() == 3000000000 {
+				break
+			} else {
+				println!("expected: 3000000000, found: {}", atomic_unit);
+				panic!("not reversed");
+			}
+		}
+	}
 }