#![allow(clippy::doc_overindented_list_items)]

use super::build::{trace_filter_parser, trace_level_parser};
use aiken_lang::{
    ast::{TraceLevel, Tracing},
    test_framework::Benchmark,
};
use aiken_project::watch::with_project;
use rand::prelude::*;
use std::{
    io::{self, IsTerminal},
    path::PathBuf,
    process,
};

#[derive(clap::Args)]
/// Benchmark an Aiken project
pub struct Args {
    /// Path to project
    directory: Option<PathBuf>,

    /// An initial seed to initialize the pseudo-random generator for benchmarks.
    #[clap(long)]
    seed: Option<u32>,

    /// The maximum size to benchmark with. Note that this does not necessarily equates the number
    /// of measurements actually performed but controls the maximum size given to a Sampler.
    #[clap(long, default_value_t = Benchmark::DEFAULT_MAX_SIZE)]
    max_size: usize,

    /// Only run benchmarks if they match any of these strings.
    ///
    /// You can match a module with `-m aiken/list` or `-m list`.
    /// You can match a benchmark with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"`
    #[clap(short, long)]
    match_benchmarks: Option<Vec<String>>,

    /// This is meant to be used with `--match-benchmarks`.
    /// It forces benchmark names to match exactly
    #[clap(short, long)]
    exact_match: bool,

    /// Environment to use for benchmarking
    env: Option<String>,

    /// Filter traces to be included in the generated program(s).
    ///
    ///   - user-defined:
    ///       only consider traces that you've explicitly defined
    ///       either through the 'trace' keyword of via the trace-if-false
    ///       ('?') operator.
    ///
    ///   - compiler-generated:
    ///       only included internal traces generated by the
    ///       Aiken compiler, for example in usage of 'expect'.
    ///
    ///   - all:
    ///       include both user-defined and compiler-generated traces.
    ///
    /// [default: all]
    #[clap(short = 'f', long, value_parser=trace_filter_parser(), default_missing_value="all", verbatim_doc_comment, alias="filter_traces")]
    trace_filter: Option<fn(TraceLevel) -> Tracing>,

    /// Choose the verbosity level of traces:
    ///
    ///   - silent: disable traces altogether
    ///   - compact: only culprit line numbers are shown on failures
    ///   - verbose: enable full verbose traces as provided by the user or the compiler
    ///
    /// [optional]
    #[clap(short, long, value_parser=trace_level_parser(), default_value_t=TraceLevel::Silent, verbatim_doc_comment)]
    trace_level: TraceLevel,
}

pub fn exec(
    Args {
        directory,
        match_benchmarks,
        exact_match,
        seed,
        max_size,
        env,
        trace_filter,
        trace_level,
    }: Args,
) -> miette::Result<()> {
    let mut rng = rand::thread_rng();

    let seed = seed.unwrap_or_else(|| rng.r#gen());

    let result = with_project(
        directory.as_deref(),
        false,
        false,
        !io::stdout().is_terminal(),
        |p| {
            p.benchmark(
                match_benchmarks.clone(),
                exact_match,
                seed,
                max_size,
                match trace_filter {
                    Some(trace_filter) => trace_filter(trace_level),
                    None => Tracing::All(trace_level),
                },
                env.clone(),
            )
        },
    );
    result.map_err(|_| process::exit(1))
}
