// 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 handles all things related to child processes (P2Pool/XMRig).
// The main GUI thread will interface with the [Arc<Mutex<...>>] data found
// here, e.g: User clicks [Start P2Pool] -> Init p2pool thread here.

//---------------------------------------------------------------------------------------------------- Import
use std::{
	sync::{Arc,Mutex},
	path::PathBuf,
	process::Command,
	thread,
};
use crate::constants::*;
use log::*;

//---------------------------------------------------------------------------------------------------- [Process] Struct
// This holds all the state of a (child) process.
// The actual process thread runs in a 1 second loop, reading/writing to this struct.
// The main GUI thread will use this to display console text, online state, etc.
pub struct Process {
	name: ProcessName,     // P2Pool or XMRig?
	online: bool,          // Is the process alive?
	args: String,          // A single [String] containing the arguments
	path: PathBuf,         // The absolute path to the process binary
	signal: ProcessSignal, // Did the user click [Stop/Restart]?
	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
	input: Vec<String>,
}

//---------------------------------------------------------------------------------------------------- [Process] Impl
impl Process {
	pub fn new(name: ProcessName, args: String, path: PathBuf) -> Self {
		Self {
			name,
			online: false,
			args,
			path,
			signal: ProcessSignal::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()
	}

	pub fn spawn(process: &Arc<Mutex<Self>>, name: ProcessName) {
		// Setup
		let process = Arc::clone(process);
		let args = Self::parse_args(&process.lock().unwrap().args);
		info!("{} | Spawning initial thread", name);
		info!("{} | Arguments: {:?}", name, args);

		// Spawn thread
		thread::spawn(move || {
			// Create & spawn child
			let mut child = Command::new(&process.lock().unwrap().path)
				.args(args)
				.stdout(std::process::Stdio::piped())
				.spawn().unwrap();

			// 1-second loop, reading and writing data to relevent struct
			loop {
				let process = process.lock().unwrap(); // Get lock
				// If user sent a signal, handle it
				match process.signal {
					ProcessSignal::None => {},
					_ => { child.kill(); break; },
				};
//				println!("{:?}", String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap());
				thread::sleep(SECOND);
			}

			// End of thread, must mean process is offline
			process.lock().unwrap().online = false;
		});
	}
}

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

//---------------------------------------------------------------------------------------------------- [ProcessName] Enum
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessName {
	P2Pool,
	XMRig,
}

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