use std::io::{self, Write};
use std::option;
use std::sync::atomic::{AtomicU64, AtomicBool, Ordering};
use std::time::{Instant,Duration};
use std::u128;

use clap::{Arg, ArgMatches};
use isatty::stdout_isatty;

use tokio::time;


// ==================================================
pub fn args<'a, 'b>() -> [Arg<'a, 'b>; 4] {[
	Arg::with_name("duration")  .short("d").long("duration")  .takes_value(true).default_value("5").help("Duration of the experiments in seconds"),
	Arg::with_name("iterations").short("i").long("iterations").takes_value(true).conflicts_with("duration").help("Number of iterations of the experiments"),
	Arg::with_name("nthreads")  .short("t").long("nthreads")  .takes_value(true).default_value("1").help("Number of threads to use"),
	Arg::with_name("nprocs")    .short("p").long("nprocs")    .takes_value(true).default_value("1").help("Number of processors to use")
]}

pub struct BenchData {
	pub clock_mode: bool,
	pub stop: AtomicBool,
	pub stop_count: u64,
	pub duration: f64,
	pub threads_left: AtomicU64,
	is_tty: bool,
}

impl BenchData {
	pub fn new(options: ArgMatches, nthreads: usize, default_it: option::Option<u64>) -> BenchData {
		let (clock_mode, stop_count, duration) = if options.is_present("iterations") {
			(false,
			options.value_of("iterations").unwrap().parse::<u64>().unwrap(),
			-1.0)
		} else if !default_it.is_none() {
			(false,
			default_it.unwrap(),
			-1.0)
		} else {
			(true,
			std::u64::MAX,
			options.value_of("duration").unwrap().parse::<f64>().unwrap())
		};

		BenchData{
			clock_mode: clock_mode,
			stop: AtomicBool::new(false),
			stop_count: stop_count,
			duration: duration,
			threads_left: AtomicU64::new(nthreads as u64),
			is_tty: stdout_isatty(),
		}
	}

	#[allow(dead_code)]
	pub async fn wait(&self, start: &Instant) -> Duration{
		loop {
			time::sleep(Duration::from_micros(100000)).await;
			let delta = start.elapsed();
			if self.is_tty {
				print!(" {:.1}\r", delta.as_secs_f32());
				io::stdout().flush().unwrap();
			}
			if self.clock_mode && delta >= Duration::from_secs_f64(self.duration)  {
				break;
			}
			else if !self.clock_mode && self.threads_left.load(Ordering::Relaxed) == 0 {
				break;
			}
		}

		self.stop.store(true, Ordering::SeqCst);
		return start.elapsed();
	}
}

// ==================================================
pub fn _lehmer64( state: &mut u128 ) -> u64 {
	*state = state.wrapping_mul(0xda942042e4dd58b5);
	return (*state >> 64) as u64;
}