#![feature(step_by)]
extern crate argparse;
extern crate sleep;
extern crate time;

use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::time::Duration;

use argparse::{ArgumentParser, Print, Store};
use sleep::sleep;
use time::precise_time_ns;

#[cfg(target_os = "linux")]
use argparse::StoreTrue;

#[cfg(target_os = "linux")]
use sleep::set_priority;

fn duration_from_ns(duration_ns: u64) -> Duration {
    Duration::new(duration_ns / 1_000_000_000,
                  (duration_ns % 1_000_000_000) as u32)
}
fn duration_to_ns(duration: Duration) -> u64 {
    duration.as_secs() * 1_000_000_000u64 + duration.subsec_nanos() as u64
}

fn measure_duration(sleep_duration: Duration) -> Duration {
    let start = precise_time_ns();
    let _remain = sleep(sleep_duration);
    let end = precise_time_ns();

    #[cfg(debug_assertions)]
    println!("remain = {:?}", _remain);

    let duration = end - start;
    let duration = duration_from_ns(duration);

    #[cfg(debug_assertions)]
    println!("duration = {:?}", duration);

    duration
}

fn measure_delay(sleep_duration: Duration) -> (Duration, Duration) {
    let duration = measure_duration(sleep_duration);
    let delay = duration - sleep_duration;

    #[cfg(debug_assertions)]
    println!("delay = {:?}", delay);

    (duration, delay)
}

fn measure_delay_loop(sleep_duration: Duration, count: u64) -> Vec<(Duration, Duration)> {
    println!("Starting measurment with period {}s {}ns for {} loops",
             sleep_duration.as_secs(),
             sleep_duration.subsec_nanos(),
             count);
    let mut data: Vec<(Duration, Duration)> = Vec::with_capacity(count as usize);

    for _ in 0..count {
        let (duration, delay) = measure_delay(sleep_duration);
        data.push((duration, delay));

        #[cfg(debug_assertions)]
        print_delay(duration, delay);
    }

    data
}

fn print_delay(duration: Duration, delay: Duration) {
    println!("value: {} s {} ns\tdelay: {} s {} ns\trelativ delay = {:.2}%\traw_data: {:?}",
             duration.as_secs(),
             duration.subsec_nanos(),
             delay.as_secs(),
             delay.subsec_nanos(),
             (duration_to_ns(delay) * 100) as f64 / duration_to_ns(duration) as f64,
             delay);
}

fn main() {
    let mut min: u64 = 1_000_000;
    let mut max: u64 = 100_000_000;
    let mut step: u64 = 1_000_000;
    let mut count: u64 = 100;
    let mut output: String = "".to_string();
    let mut file: Option<File> = None;

    #[cfg(target_os = "linux")]
    let mut realtime: bool = false;

    #[cfg(target_os = "macos")]
    let realtime: bool = false;

    {
        let mut ap = ArgumentParser::new();
        ap.set_description(env!("CARGO_PKG_DESCRIPTION"));
        ap.refer(&mut min)
            .add_option(&["--min"], Store, "Sleep period start");
        ap.refer(&mut max)
            .add_option(&["--max"], Store, "Sleep period end");
        ap.refer(&mut step)
            .add_option(&["--step"], Store, "Sleep period step size");
        ap.refer(&mut count)
            .add_option(&["--loop"], Store, "Count of measurements per period");
        #[cfg(target_os = "linux")]
        ap.refer(&mut realtime)
            .add_option(&["--rt"], StoreTrue, "Set realtime priority");
        ap.refer(&mut output)
            .add_option(&["-o", "--out"], Store, "Output file");
        ap.add_option(&["-V", "--version"],
                      Print(env!("CARGO_PKG_VERSION").to_string()),
                      "Show version");
        ap.parse_args_or_exit();
    }

    if realtime {
        #[cfg(target_os = "linux")]
        let ret = set_priority(99);

        #[cfg(target_os = "macos")]
        let ret = 0;

        if ret != 0 {
            panic!("Set realtime priority failed.");
        }
    }

    if output != "" {
        file = Some(File::create(Path::new(output.as_str())).expect(format!("Open file '{}' failed",
                                                                            output)
                                                                            .as_str()));
    }

    for duration in (min..max + 1).step_by(step) {
        let data = measure_delay_loop(duration_from_ns(duration), count);
        let max_delay = data.iter().max().expect("Max delay not found");

        #[cfg(debug_assertions)]
        println!("max_delay = {:?}", max_delay);

        print_delay(max_delay.0, max_delay.1);

        match file.as_ref() {
            Some(mut f) => {
                let value = format!("{: >9} {}\n", duration, duration_to_ns(max_delay.1));
                f.write_all(value.as_bytes())
                    .expect(format!("Write value '{}' to '{}' file failed", value, output)
                                .as_str());
            }
            None => {}
        }
    }
}
