//! Hpu Simulation mockup
//! Emulate Hpu behavior for simulation
//! Enable to test tfhe-rs application that required tfhe-hpu-backend without the real hardware.
//! It rely on the `ffi-sim` interface of `tfhe-hpu-backend` and on ipc-channel for communication
//!
//! WARN: User must start the HpuSim mockup before tfhe-rs application

use std::fs::OpenOptions;
use std::path::Path;

use tfhe::tfhe_hpu_backend::prelude::*;
use tfhe_hpu_mockup::{HpuSim, MockupOptions, MockupParameters};

/// Define CLI arguments
use clap::Parser;
#[derive(clap::Parser, Debug, Clone)]
#[command(long_about = "Hpu Simulation mockup.")]
pub struct Args {
    // Configuration ----------------------------------------------------
    /// Fpga fake configuration
    /// Toml file similar to the one used with the real hpu-backend
    /// Enable to retrieved ipc_name, register_file and board definition
    #[arg(
        long,
        default_value = "${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_config.toml"
    )]
    pub config: ShellString,

    /// Hpu rtl parameters
    /// Enable to retrieved the associated tfhe-rs parameters and other Rtl parameters
    #[arg(
        long,
        default_value = "${HPU_MOCKUP_DIR}/params/gaussian_64b_fast.toml"
    )]
    pub params: ShellString,

    // Override params --------------------------------------------------
    // Quick way to override parameters through ClI instead of editing the
    // configuration file
    // Used to override some parameters at runtime
    /// Override Number of Register
    #[arg(long)]
    register: Option<usize>,

    /// Override HPU lookahead buffer depth
    /// Number of instruction that are considered in advance
    #[arg(long)]
    isc_depth: Option<usize>,

    // Simulation configuration -----------------------------------------
    /// Frequency in MHz
    /// Only use for report display
    #[arg(long, default_value_t = 300)]
    freq_mhz: usize,

    /// Simulation quantum in micro_seconds.
    /// Maximum simulation time drift between mockup and backend
    #[arg(long, default_value_t = 1_000_000)]
    quantum_us: usize,

    /// Consider all received ciphertext as trivial ciphertext
    /// Execute Pbs in a trivial manner and display value in tracing::debug
    /// Useful for IOp algorithm debug
    /// WARN: Only work if user application send trivial ciphertext
    #[arg(long)]
    trivial: bool,

    // Dump configuration ----------------------------------------------------
    // Use to activate some dump features for the generation of simulation stimulus
    /// Specify simulus dump folder.
    /// NB: The Rtl stimulus (i.e. Input/Output, Keys, Lut) should be generated by the client.
    ///     Only used to dump IOp/DOp and the inner register values (c.f. dump-reg)
    #[arg(long)]
    dump_out: Option<String>,

    /// Activate the dump of intermediate register value. Only work if dump-out is also specified
    #[arg(long)]
    dump_reg: bool,

    // Reports configuration -------------------------------------------------
    // Use to activate some performances reports
    /// Specify reports dump folder. When not specified, no reports were generated
    #[arg(long)]
    report_out: Option<String>,

    /// Activate the execution trace export for later analysis
    #[arg(long)]
    report_trace: bool,

    // Log configuration -------------------------------------------------
    /// Write trace message in the file (instead of on stdio)
    /// NB: Use RUST_LOG env variable to set the verbosity
    #[arg(long)]
    log_out: Option<String>,
}

impl From<&Args> for MockupOptions {
    fn from(args: &Args) -> Self {
        Self {
            dump_out: args.dump_out.clone(),
            dump_reg: args.dump_reg,
            report_out: args.report_out.clone(),
            report_trace: args.report_trace,
            trivial: args.trivial,
        }
    }
}

fn main() {
    let args = Args::parse();
    println!("User Options: {args:?}");

    let options = MockupOptions::from(&args);

    // Configure traging -------------------------------------------------
    // Tracing subscriber rely on env-filter
    // Select verbosity with env_var: e.g. `RUST_LOG=Alu=trace`
    let dflt_tracer = tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        // Display source code file paths
        .with_file(false)
        // Display source code line numbers
        .with_line_number(false)
        .without_time();

    if let Some(file) = args.log_out {
        // Open file
        // Create path
        let path = Path::new(&file);
        if let Some(dir_p) = path.parent() {
            std::fs::create_dir_all(dir_p).unwrap();
        }

        // Open file
        let wr_f = OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .open(path)
            .unwrap();

        // Build & register the subscriber to print in a file
        dflt_tracer.json().with_writer(wr_f).init();
    } else {
        // Build & register the subscriber to display on stdio
        dflt_tracer.compact().init();
    }

    // Load parameters from configuration file ------------------------------------
    let config = HpuConfig::from_toml(&args.config.expand());
    let params = {
        let mut rtl_params = HpuParameters::from_toml(&args.params.expand());

        // Override some parameters if required
        if let Some(register) = args.register.as_ref() {
            rtl_params.regf_params.reg_nb = *register;
        }
        if let Some(isc_depth) = args.isc_depth.as_ref() {
            rtl_params.isc_params.depth = *isc_depth;
        }
        MockupParameters {
            freq_mhz: args.freq_mhz,
            quantum_us: args.quantum_us,
            rtl_params,
        }
    };
    println!("Mockup parameters after override with CLI: {params:?}");

    // Start mockup ---------------------------------------------------------------
    let mut hpu_sim = HpuSim::new(config, params, options);
    hpu_sim.ipc_poll();
}
