// Gupax - GUI Uniting P2Pool And XMRig
//
// Copyright (c) 2022 hinto-janaiyo
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

// This file represents the "helper" thread, which is the full separate thread
// that runs alongside the main [App] GUI thread. It exists for the entire duration
// of Gupax so that things can be handled without locking up the GUI thread.
//
// This thread is a continual 1 second loop, collecting available jobs on the
// way down and (if possible) asynchronously executing them at the very end.
//
// The main GUI thread will interface with this thread by mutating the Arc<Mutex>'s
// found here, e.g: User clicks [Start P2Pool] -> Arc<Mutex<ProcessSignal> is set
// indicating to this thread during its loop: "I should start P2Pool!", e.g:
//
//     match p2pool.lock().unwrap().signal {
//         ProcessSignal::Start => start_p2pool(),
//         ...
//     }
//
// This also includes all things related to handling the child processes (P2Pool/XMRig):
// piping their stdout/stderr/stdin, accessing their APIs (HTTP + disk files), etc.

//---------------------------------------------------------------------------------------------------- Import
use std::{
	sync::{Arc,Mutex},
	path::PathBuf,
	process::{Command,Stdio},
	fmt::Write,
	time::*,
	thread,
};
use serde::{Serialize,Deserialize};
use crate::constants::*;
use num_format::{Buffer,Locale};
use log::*;

//---------------------------------------------------------------------------------------------------- Constants
const LOCALE: num_format::Locale = num_format::Locale::en;

//---------------------------------------------------------------------------------------------------- [Helper] Struct
// A meta struct holding all the data that gets processed in this thread
pub struct Helper {
	pub instant: Instant,                         // Gupax start as an [Instant]
	pub human_time: HumanTime,                    // Gupax uptime formatting for humans
	pub p2pool: Arc<Mutex<Process>>,              // P2Pool process state
	pub xmrig: Arc<Mutex<Process>>,               // XMRig process state
	pub pub_api_p2pool: Arc<Mutex<PubP2poolApi>>, // P2Pool API state (for GUI/Helper thread)
	pub pub_api_xmrig: Arc<Mutex<PubXmrigApi>>,   // XMRig API state (for GUI/Helper thread)
	priv_api_p2pool: Arc<Mutex<PrivP2poolApi>>,   // For "watchdog" thread
	priv_api_xmrig: Arc<Mutex<PrivXmrigApi>>,     // For "watchdog" thread
}

// Impl found at the very bottom of this file.

//---------------------------------------------------------------------------------------------------- [Process] Struct
// This holds all the state of a (child) process.
// The main GUI thread will use this to display console text, online state, etc.
pub struct Process {
	pub name: ProcessName,     // P2Pool or XMRig?
	pub state: ProcessState,   // The state of the process (alive, dead, etc)
	pub signal: ProcessSignal, // Did the user click [Start/Stop/Restart]?
	start: Instant,            // Start time of process
	pub uptime: HumanTime,     // Human readable process uptime
	pub output: String,        // This is the process's stdout + stderr
	// STDIN Problem:
	//     - User can input many many commands in 1 second
	//     - The process loop only processes every 1 second
	//     - If there is only 1 [String] holding the user input,
	//       the user could overwrite their last input before
	//       the loop even has a chance to process their last command
	// STDIN Solution:
	//     - When the user inputs something, push it to a [Vec]
	//     - In the process loop, loop over every [Vec] element and
	//       send each one individually to the process stdin
	stdin: Option<std::process::ChildStdin>, // A handle to the process's STDIN
	pub input: Vec<String>,
}

//---------------------------------------------------------------------------------------------------- [Process] Impl
impl Process {
	pub fn new(name: ProcessName, args: String, path: PathBuf) -> Self {
		let now = Instant::now();
		Self {
			name,
			state: ProcessState::Dead,
			signal: ProcessSignal::None,
			start: now,
			uptime: HumanTime::into_human(now.elapsed()),
			stdin: Option::None,
			// P2Pool log level 1 produces a bit less than 100,000 lines a day.
			// Assuming each line averages 80 UTF-8 scalars (80 bytes), then this
			// initial buffer should last around a week (56MB) before resetting.
			output: String::with_capacity(56_000_000),
			input: vec![String::new()],
		}
	}

	// Borrow a [&str], return an owned split collection
	pub fn parse_args(args: &str) -> Vec<String> {
		args.split_whitespace().map(|s| s.to_owned()).collect()
	}
}

//---------------------------------------------------------------------------------------------------- [Process*] Enum
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessState {
	Alive,    // Process is online, GREEN!
	Dead,     // Process is dead, BLACK!
	Failed,   // Process is dead AND exited with a bad code, RED!
	// Process is starting up, YELLOW!
	// Really, processes start instantly, this just accounts for the delay
	// between the main thread and this threads 1 second event loop.
	Starting,
}

#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessSignal {
	None,
	Start,
	Stop,
	Restart,
}

#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessName {
	P2pool,
	Xmrig,
}

impl std::fmt::Display for ProcessState  { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
impl std::fmt::Display for ProcessSignal { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
impl std::fmt::Display for ProcessName   { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }

//---------------------------------------------------------------------------------------------------- [HumanTime]
// This converts a [std::time::Duration] into something more readable.
// Used for uptime display purposes: [7 years, 8 months, 15 days, 23 hours, 35 minutes, 1 second]
// Code taken from [https://docs.rs/humantime/] and edited to remove sub-second time, change spacing and some words.
use std::time::Duration;

#[derive(Debug, Clone)]
pub struct HumanTime(Duration);

impl HumanTime {
	pub fn into_human(d: Duration) -> HumanTime {
		HumanTime(d)
	}

	fn plural(f: &mut std::fmt::Formatter, started: &mut bool, name: &str, value: u64) -> std::fmt::Result {
		if value > 0 {
			if *started { f.write_str(" ")?; }
		}
		write!(f, "{}{}", value, name)?;
		if value > 1 {
			f.write_str("s")?;
		}
		*started = true;
		Ok(())
	}
}

impl std::fmt::Display for HumanTime {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		let secs = self.0.as_secs();
		if secs == 0 {
			f.write_str("0s")?;
			return Ok(());
		}

		let years = secs / 31_557_600;  // 365.25d
		let ydays = secs % 31_557_600;
		let months = ydays / 2_630_016;  // 30.44d
		let mdays = ydays % 2_630_016;
		let days = mdays / 86400;
		let day_secs = mdays % 86400;
		let hours = day_secs / 3600;
		let minutes = day_secs % 3600 / 60;
		let seconds = day_secs % 60;

		let ref mut started = false;
		Self::plural(f, started, " year", years)?;
		Self::plural(f, started, " month", months)?;
		Self::plural(f, started, " day", days)?;
		Self::plural(f, started, " hour", hours)?;
		Self::plural(f, started, " minute", minutes)?;
		Self::plural(f, started, " second", seconds)?;
		Ok(())
	}
}

//---------------------------------------------------------------------------------------------------- [HumanNumber]
// Human readable numbers.
// Float    | [1234.57] -> [1,234]                    | Casts as u64/u128, adds comma
// Unsigned | [1234567] -> [1,234,567]                | Adds comma
// Percent  | [99.123] -> [99.12%]                    | Truncates to 2 after dot, adds percent
// Percent  | [0.001]  -> [0%]                        | Rounds down, removes redundant zeros
// Hashrate | [123.0, 311.2, null] -> [123, 311, ???] | Casts, replaces null with [???]
// CPU Load | [12.0, 11.4, null] -> [12.0, 11.4, ???] | No change, just into [String] form
#[derive(Debug, Clone)]
pub struct HumanNumber(String);

impl std::fmt::Display for HumanNumber {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "{}", &self.0)
	}
}

impl HumanNumber {
	fn unknown() -> Self {
		Self("???".to_string())
	}
	fn to_percent(f: f32) -> Self {
		if f < 0.01 {
			Self("0%".to_string())
		} else {
			Self(format!("{:.2}%", f))
		}
	}
	fn from_f32(f: f32) -> Self {
		let mut buf = num_format::Buffer::new();
		buf.write_formatted(&(f as u64), &LOCALE);
		Self(buf.as_str().to_string())
	}
	fn from_f64(f: f64) -> Self {
		let mut buf = num_format::Buffer::new();
		buf.write_formatted(&(f as u128), &LOCALE);
		Self(buf.as_str().to_string())
	}
	fn from_u8(u: u8) -> Self {
		let mut buf = num_format::Buffer::new();
		buf.write_formatted(&u, &LOCALE);
		Self(buf.as_str().to_string())
	}
	fn from_u16(u: u16) -> Self {
		let mut buf = num_format::Buffer::new();
		buf.write_formatted(&u, &LOCALE);
		Self(buf.as_str().to_string())
	}
	fn from_u32(u: u32) -> Self {
		let mut buf = num_format::Buffer::new();
		buf.write_formatted(&u, &LOCALE);
		Self(buf.as_str().to_string())
	}
	fn from_u64(u: u64) -> Self {
		let mut buf = num_format::Buffer::new();
		buf.write_formatted(&u, &LOCALE);
		Self(buf.as_str().to_string())
	}
	fn from_u128(u: u128) -> Self {
		let mut buf = num_format::Buffer::new();
		buf.write_formatted(&u, &LOCALE);
		Self(buf.as_str().to_string())
	}
	fn from_hashrate(array: [Option<f32>; 3]) -> Self {
		let mut string = "[".to_string();
		let mut buf = num_format::Buffer::new();

		let mut n = 0;
		for i in array {
			match i {
				Some(f) => {
					let f = f as u128;
					buf.write_formatted(&f, &LOCALE);
					string.push_str(buf.as_str());
					string.push_str(" H/s");
				},
				None => string.push_str("??? H/s"),
			}
			if n != 2 {
				string.push_str(", ");
				n += 1;
			} else {
				string.push(']');
				break
			}
		}

		Self(string)
	}
	fn from_load(array: [Option<f32>; 3]) -> Self {
		let mut string = "[".to_string();
		let mut n = 0;
		for i in array {
			match i {
				Some(f) => string.push_str(format!("{}", f).as_str()),
				None => string.push_str("???"),
			}
			if n != 2 {
				string.push_str(", ");
				n += 1;
			} else {
				string.push(']');
				break
			}
		}
		Self(string)
	}
}

//---------------------------------------------------------------------------------------------------- Public P2Pool API
// GUI thread interfaces with this.
pub struct PubP2poolApi {
	// One off
	pub mini: bool,
	// These are manually parsed from the STDOUT.
	pub payouts: f64,
	pub payouts_hour: f64,
	pub payouts_day: f64,
	pub payouts_month: f64,
	pub xmr: f64,
	pub xmr_hour: f64,
	pub xmr_day: f64,
	pub xmr_month: f64,
	// The rest are serialized from the API, then turned into [HumanNumber]s
	pub hashrate_15m: HumanNumber,
	pub hashrate_1h: HumanNumber,
	pub hashrate_24h: HumanNumber,
	pub shares_found: HumanNumber,
	pub average_effort: HumanNumber,
	pub current_effort: HumanNumber,
	pub connections: HumanNumber,
}

impl PubP2poolApi {
	pub fn new() -> Self {
		Self {
			mini: true,
			payouts: 0.0,
			payouts_hour: 0.0,
			payouts_day: 0.0,
			payouts_month: 0.0,
			xmr: 0.0,
			xmr_hour: 0.0,
			xmr_day: 0.0,
			xmr_month: 0.0,
			hashrate_15m: HumanNumber::unknown(),
			hashrate_1h: HumanNumber::unknown(),
			hashrate_24h: HumanNumber::unknown(),
			shares_found: HumanNumber::unknown(),
			average_effort: HumanNumber::unknown(),
			current_effort: HumanNumber::unknown(),
			connections: HumanNumber::unknown(),
		}
	}

	// Updates the struct with hour/day/month calculations given an uptime in f64 seconds.
	pub fn update_hour_day_month(self, elapsed: f64) -> Self {
		// Payouts
		let per_sec = self.payouts / elapsed;
		let payouts_hour = (per_sec * 60.0) * 60.0;
		let payouts_day = payouts_hour * 24.0;
		let payouts_month = payouts_day * 30.0;
		// Total XMR
		let per_sec = self.xmr / elapsed;
		let xmr_hour = (per_sec * 60.0) * 60.0;
		let xmr_day = payouts_hour * 24.0;
		let xmr_month = payouts_day * 30.0;
		Self {
			payouts_hour,
			payouts_day,
			payouts_month,
			xmr_hour,
			xmr_day,
			xmr_month,
			..self // <- wow this is so useful
		}
	}
}

//---------------------------------------------------------------------------------------------------- Private P2Pool API
// This is the data the "watchdog" threads mutate.
// It matches directly to P2Pool's [local/stats] JSON API file (excluding a few stats).
// P2Pool seems to initialize all stats at 0 (or 0.0), so no [Option] wrapper seems needed.
#[derive(Debug, Serialize, Deserialize)]
struct PrivP2poolApi {
	hashrate_15m: u128,
	hashrate_1h: u128,
	hashrate_24h: u128,
	shares_found: u128,
	average_effort: f32,
	current_effort: f32,
	connections: u16, // No one will have more than 65535 connections... right?
}

impl PrivP2poolApi {
	fn new() -> Self {
		Self {
			hashrate_15m: 0,
			hashrate_1h: 0,
			hashrate_24h: 0,
			shares_found: 0,
			average_effort: 0.0,
			current_effort: 0.0,
			connections: 0,
		}
	}

	// Formats raw private data into ready-to-print human readable version.
//	pub fn from_priv(private: PrivP2poolApi) -> Self {
//		Self {
//		}
//	}
}

//---------------------------------------------------------------------------------------------------- Public XMRig API
pub struct PubXmrigApi {
	worker_id: String,
	resources: HumanNumber,
	hashrate: HumanNumber,
	pool: String,
	diff: HumanNumber,
	accepted: HumanNumber,
	rejected: HumanNumber,
}

impl PubXmrigApi {
	pub fn new() -> Self {
		Self {
			worker_id: "???".to_string(),
			resources: HumanNumber::unknown(),
			hashrate: HumanNumber::unknown(),
			pool: "???".to_string(),
			diff: HumanNumber::unknown(),
			accepted: HumanNumber::unknown(),
			rejected: HumanNumber::unknown(),
		}
	}

	// Formats raw private data into ready-to-print human readable version.
	fn from_priv(private: PrivXmrigApi) -> Self {
		Self {
			worker_id: private.worker_id,
			resources: HumanNumber::from_load(private.resources.load_average),
			hashrate: HumanNumber::from_hashrate(private.hashrate.total),
			pool: private.connection.pool,
			diff: HumanNumber::from_u128(private.connection.diff),
			accepted: HumanNumber::from_u128(private.connection.accepted),
			rejected: HumanNumber::from_u128(private.connection.rejected),
		}
	}
}

//---------------------------------------------------------------------------------------------------- Private XMRig API
// This matches to some JSON stats in the HTTP call [summary],
// e.g: [wget -qO- localhost:18085/1/summary].
// XMRig doesn't initialize stats at 0 (or 0.0) and instead opts for [null]
// which means some elements need to be wrapped in an [Option] or else serde will [panic!].
#[derive(Debug, Serialize, Deserialize)]
struct PrivXmrigApi {
	worker_id: String,
	resources: Resources,
	connection: Connection,
	hashrate: Hashrate,
}

impl PrivXmrigApi {
	fn new() -> Self {
		Self {
			worker_id: String::new(),
			resources: Resources::new(),
			connection: Connection::new(),
			hashrate: Hashrate::new(),
		}
	}
}

#[derive(Debug, Serialize, Deserialize)]
struct Resources {
	load_average: [Option<f32>; 3],
}
impl Resources {
	fn new() -> Self {
		Self {
			load_average: [Some(0.0), Some(0.0), Some(0.0)],
		}
	}
}

#[derive(Debug, Serialize, Deserialize)]
struct Connection {
	pool: String,
	diff: u128,
	accepted: u128,
	rejected: u128,
}
impl Connection {
	fn new() -> Self {
		Self {
			pool: String::new(),
			diff: 0,
			accepted: 0,
			rejected: 0,
		}
	}
}

#[derive(Debug, Serialize, Deserialize)]
struct Hashrate {
	total: [Option<f32>; 3],
}
impl Hashrate {
	fn new() -> Self {
		Self {
			total: [Some(0.0), Some(0.0), Some(0.0)],
		}
	}
}

//---------------------------------------------------------------------------------------------------- [Helper]
use tokio::io::{BufReader,AsyncBufReadExt};

impl Helper {
	//---------------------------------------------------------------------------------------------------- General Functions
	pub fn new(instant: std::time::Instant) -> Self {
		Self {
			instant,
			human_time: HumanTime::into_human(instant.elapsed()),
			p2pool: Arc::new(Mutex::new(Process::new(ProcessName::P2pool, String::new(), PathBuf::new()))),
			xmrig: Arc::new(Mutex::new(Process::new(ProcessName::Xmrig, String::new(), PathBuf::new()))),
			pub_api_p2pool: Arc::new(Mutex::new(PubP2poolApi::new())),
			pub_api_xmrig: Arc::new(Mutex::new(PubXmrigApi::new())),
			priv_api_p2pool: Arc::new(Mutex::new(PrivP2poolApi::new())),
			priv_api_xmrig: Arc::new(Mutex::new(PrivXmrigApi::new())),
		}
	}

	// The tokio runtime that blocks while async reading both STDOUT/STDERR
	// Cheaper than spawning 2 OS threads just to read 2 pipes (...right? :D)
	#[tokio::main]
	async fn read_stdout_stderr(process: &Arc<Mutex<Process>>, stdout: tokio::process::ChildStdout, stderr: tokio::process::ChildStderr) {
		let process_stdout = Arc::clone(process);
		let process_stderr = Arc::clone(process);
		// Create STDOUT pipe job
		let stdout_job = tokio::spawn(async move {
			let mut stdout_reader = BufReader::new(stdout).lines();
			while let Ok(Some(line)) = stdout_reader.next_line().await {
				writeln!(process_stdout.lock().unwrap().output, "{}", line);
			}
		});
		// Create STDERR pipe job
		let stderr_job = tokio::spawn(async move {
			let mut stderr_reader = BufReader::new(stderr).lines();
			while let Ok(Some(line)) = stderr_reader.next_line().await {
				writeln!(process_stderr.lock().unwrap().output, "{}", line);
			}
		});
		// Block and read both until they are closed (automatic when process dies)
		// The ordering of STDOUT/STDERR should be automatic thanks to the locks.
		tokio::join![stdout_job, stderr_job];
	}

	//---------------------------------------------------------------------------------------------------- P2Pool specific
	// Intermediate function that parses the arguments, and spawns the P2Pool watchdog thread.
	pub fn spawn_p2pool(helper: &Arc<Mutex<Self>>, state: &crate::disk::P2pool, path: std::path::PathBuf) {
		let mut args = Vec::with_capacity(500);
		let path = path.clone();
		let mut api_path = path.clone();
		api_path.pop();

		// [Simple]
		if state.simple {
			// Build the p2pool argument
			let (ip, rpc, zmq) = crate::node::enum_to_ip_rpc_zmq_tuple(state.node); // Get: (IP, RPC, ZMQ)
			args.push(format!("--wallet {}", state.address));        // Wallet Address
			args.push(format!("--host {}", ip));                     // IP Address
			args.push(format!("--rpc-port {}", rpc));                // RPC Port
			args.push(format!("--zmq-port {}", zmq));                // ZMQ Port
			args.push(format!("--data-api {}", api_path.display())); // API Path
			args.push("--local-api".to_string());                    // Enable API
			args.push("--no-color".to_string());                     // Remove color escape sequences, Gupax terminal can't parse it :(
			args.push("--mini".to_string());                         // P2Pool Mini

		// [Advanced]
		} else {
			// Overriding command arguments
			if !state.arguments.is_empty() {
				for arg in state.arguments.split_whitespace() {
					args.push(arg.to_string());
				}
			// Else, build the argument
			} else {
				args.push(state.address.clone());      // Wallet
				args.push(state.selected_ip.clone());  // IP
				args.push(state.selected_rpc.clone()); // RPC
				args.push(state.selected_zmq.clone()); // ZMQ
				args.push("--local-api".to_string());  // Enable API
				args.push("--no-color".to_string());   // Remove color escape sequences
				if state.mini { args.push("--mini".to_string()); };      // Mini
				args.push(format!("--loglevel {}", state.log_level));    // Log Level
				args.push(format!("--out-peers {}", state.out_peers));   // Out Peers
				args.push(format!("--in-peers {}", state.in_peers));     // In Peers
				args.push(format!("--data-api {}", api_path.display())); // API Path
			}
		}

		// Print arguments to console
		crate::disk::print_dash(&format!("P2Pool | Launch arguments ... {:#?}", args));

		// Spawn watchdog thread
		let process = Arc::clone(&helper.lock().unwrap().p2pool);
		let pub_api = Arc::clone(&helper.lock().unwrap().pub_api_p2pool);
		let priv_api = Arc::clone(&helper.lock().unwrap().priv_api_p2pool);
		thread::spawn(move || {
			Self::spawn_p2pool_watchdog(process, pub_api, priv_api, args, path);
		});
	}

	// The actual P2Pool watchdog tokio runtime.
	#[tokio::main]
	async fn spawn_p2pool_watchdog(process: Arc<Mutex<Process>>, pub_api: Arc<Mutex<PubP2poolApi>>, priv_api: Arc<Mutex<PrivP2poolApi>>, args: Vec<String>, path: std::path::PathBuf) {
		// 1. Create command
		let mut child = tokio::process::Command::new(path)
			.args(args)
			.stdout(Stdio::piped())
			.stderr(Stdio::piped())
			.stdin(Stdio::piped())
			.spawn().unwrap();

		// 2. Set start time
		let now = Instant::now();
		process.lock().unwrap().start = now;
		process.lock().unwrap().uptime = HumanTime::into_human(now.elapsed());

		// 3. Spawn STDOUT/STDERR thread
		thread::spawn(move || {
			Self::read_stdout_stderr(&process, child.stdout.take().unwrap(), child.stderr.take().unwrap());
		});

		// 4. Loop forever as watchdog until process dies
		loop {
			// a. Watch user SIGNAL
			// b. Create STDIN task
			// c. Create API task
			// d. Execute async tasks
			// e. Sleep (900ms)
		}
	}

	//---------------------------------------------------------------------------------------------------- XMRig specific
	// Intermediate function that parses the arguments, and spawns the XMRig watchdog thread.
	pub fn spawn_xmrig(state: &crate::disk::Xmrig, api_path: &std::path::Path) {
		let mut args = Vec::with_capacity(500);
		if state.simple {
			let rig_name = if state.simple_rig.is_empty() { GUPAX_VERSION.to_string() } else { state.simple_rig.clone() }; // Rig name
			args.push(format!("--threads {}", state.current_threads)); // Threads
			args.push(format!("--user {}", state.simple_rig));         // Rig name
			args.push(format!("--url 127.0.0.1:3333"));                // Local P2Pool (the default)
			args.push("--no-color".to_string());                       // No color escape codes
			if state.pause != 0 { args.push(format!("--pause-on-active {}", state.pause)); } // Pause on active
		} else {
			if !state.arguments.is_empty() {
				for arg in state.arguments.split_whitespace() {
					args.push(arg.to_string());
				}
			} else {
				args.push(format!("--user {}", state.address.clone()));    // Wallet
				args.push(format!("--threads {}", state.current_threads)); // Threads
				args.push(format!("--rig-id {}", state.selected_rig));     // Rig ID
				args.push(format!("--url {}:{}", state.selected_ip.clone(), state.selected_port.clone())); // IP/Port
				args.push(format!("--http-host {}", state.api_ip).to_string());   // HTTP API IP
				args.push(format!("--http-port {}", state.api_port).to_string()); // HTTP API Port
				args.push("--no-color".to_string());                         // No color escape codes
				if state.tls { args.push("--tls".to_string()); }             // TLS
				if state.keepalive { args.push("--keepalive".to_string()); } // Keepalive
				if state.pause != 0 { args.push(format!("--pause-on-active {}", state.pause)); } // Pause on active
			}
		}
		// Print arguments to console
		crate::disk::print_dash(&format!("XMRig | Launch arguments ... {:#?}", args));

		// Spawn watchdog thread
		thread::spawn(move || {
			Self::spawn_xmrig_watchdog(args);
		});
	}

	// The actual XMRig watchdog tokio runtime.
	#[tokio::main]
	pub async fn spawn_xmrig_watchdog(args: Vec<String>) {
	}

	//---------------------------------------------------------------------------------------------------- The "helper"
	// Intermediate function that spawns the helper thread.
	pub fn spawn_helper(helper: &Arc<Mutex<Self>>) {
		let helper = Arc::clone(helper);
		thread::spawn(move || { Self::helper(helper); });
	}

	// [helper] = Actual Arc
	// [h]      = Temporary lock that gets dropped
	// [jobs]   = Vector of async jobs ready to go
//	#[tokio::main]
	pub fn helper(helper: Arc<Mutex<Self>>) {
		// Begin loop
		loop {

		// 1. Create "jobs" vector holding async tasks
//		let jobs: Vec<tokio::task::JoinHandle<Result<(), anyhow::Error>>> = vec![];

		// 2. Loop init timestamp
		let start = Instant::now();

		// 7. Set Gupax/P2Pool/XMRig uptime
		let mut h = helper.lock().unwrap();
		h.human_time = HumanTime::into_human(h.instant.elapsed());
		drop(h);

		// 8. Calculate if we should sleep or not.
		// If we should sleep, how long?
		let elapsed = start.elapsed().as_millis();
		if elapsed < 1000 {
			// Casting from u128 to u64 should be safe here, because [elapsed]
			// is less than 1000, meaning it can fit into a u64 easy.
			std::thread::sleep(std::time::Duration::from_millis((1000-elapsed) as u64));
		}

		// 9. End loop
		}
	}
}