// License: see LICENSE file at root directory of `master` branch

//! # Namaste

#![warn(missing_docs)]

mod server;
mod tests;

use std::{
    io::{self, Error, ErrorKind},
    process,
    time::Duration,
};

use dia_args::Args;

use server::Server;

const CMD_HELP: &str = "help";
const CMD_VERSION: &str = "version";
const CMD_RUN: &str = "run";

const ARG_PORT: &[&str] = &["--port"];
const ARG_RW_TIMEOUT: &[&str] = &["--rw-timeout"];
const ARG_ACTIVE_JOB_LIMIT: &[&str] = &["--active-job-limit"];

const MAX_ACTIVE_JOB_LIMIT: usize = 4096;

/// # Main
fn main() {
    if let Err(err) = run() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

/// # Runs the program
fn run() -> io::Result<()> {
    let args = dia_args::parse()?;
    match args.cmd() {
        Some(self::CMD_HELP) => {
            ensure_args_are_empty(args.sub_cmd().1)?;
            print_version_and_help()
        },
        Some(self::CMD_VERSION) => {
            ensure_args_are_empty(args.sub_cmd().1)?;
            print_version()
        },
        Some(self::CMD_RUN) => run_main_job(args.sub_cmd().1),
        Some(other) => Err(Error::new(ErrorKind::InvalidInput, format!("Unknown command: {:?}", other))),
        None => Err(Error::new(ErrorKind::Other, "Missing command")),
    }
}

/// # Ensures arguments are empty
fn ensure_args_are_empty<A>(args: A) -> io::Result<()> where A: AsRef<Args> {
    let args = args.as_ref();
    match args.is_empty() {
        true => Ok(()),
        false => Err(Error::new(ErrorKind::InvalidInput, format!("Unknown arguments: {:?}", args))),
    }
}

/// # Prints version
fn print_version() -> io::Result<()> {
    println!("{} {} {:?}", namaste::NAME, namaste::VERSION, namaste::RELEASE_DATE);
    Ok(())
}

/// # Prints version and help
fn print_version_and_help() -> io::Result<()> {
    print_version()?;
    println!();
    println!("Available commands: {:?}, {:?}, {:?}", CMD_HELP, CMD_VERSION, CMD_RUN);
    Ok(())
}

/// # Runs main job
fn run_main_job(mut args: Args) -> io::Result<()> {
    let port = match args.take::<u16, _>(ARG_PORT)? {
        Some(port) => port,
        None => namaste::server::DEFAULT_PORT,
    };
    let rw_timeout = match args.take::<u64, _>(ARG_RW_TIMEOUT)? {
        Some(secs) => Duration::from_secs(secs),
        None => server::DEFAULT_RW_TIMEOUT,
    };
    let active_job_limit = match args.take::<usize, _>(ARG_ACTIVE_JOB_LIMIT)? {
        Some(active_job_limit) => match active_job_limit {
            0 => return Err(Error::new(ErrorKind::InvalidData, format!("Invalid value for {:?}: {}", ARG_ACTIVE_JOB_LIMIT, active_job_limit))),
            _ => match active_job_limit <= MAX_ACTIVE_JOB_LIMIT {
                true => active_job_limit,
                false => return Err(Error::new(
                    ErrorKind::InvalidData,
                    format!("Active job limit is too high: {} (max allowed: {})", active_job_limit, MAX_ACTIVE_JOB_LIMIT)
                )),
            },
        },
        None => unsafe { blackhole::get_online_processors!(None) as usize },
    };

    ensure_args_are_empty(args)?;

    Server::start(Some(port), rw_timeout, active_job_limit)?.join()
}
