pub mod sim;
mod stats;
mod types;

use std::{env, process};
use crate::types::{Quorum, Region, NodeCount};
use crate::stats::{CurrentStats, CumulativeStats};
use crate::sim::Simulation;
use rand_pcg::Pcg64;
use rand::SeedableRng;
// use log::{info, error, set_logger};
// use simplelog::*;
// use std::fs::File;

fn usage(cmd: &String) {
    eprintln!("Usage: {} h m lg_r k g lg_c T S", cmd);
    eprintln!("h:           number of honest nodes");
    eprintln!("m:           number of malicious nodes");
    eprintln!("lg_r:        log_2 of the number of regions");
    eprintln!("k:           k - 1 no. of secondary joins in CCR before new primary joins are accepted, k*g/g' nodes cuckoo-ed out with each primary join");
    eprintln!("g:           desired number of nodes in a *region*");
    eprintln!("lg_c:        log_2 of the number of regions per quorum");
    eprintln!("T:           number of iterations after initialization");
    eprintln!("S:        number of seeds");
}
fn run_print_errors(sim: &mut Simulation, is_init: bool, h: usize, m: usize, ctr: usize, max: usize) -> bool
{
    let (prefix, iter, iters, inserted, inserted_below_bmax, rejoined_below_bmax, number_rejoined) = if is_init
    {
        let (init_iter, a, b, c, d) = sim.init(h, m);
        ("FAILED INIT ".to_string(), init_iter, m, a, b, c, d)
    }
    else
    {
        let (a, b, c, d) = sim.move_malicious();
        ("FAILED MOVE MALICIOUS ".to_string(), ctr, max, a, b, c, d)
    };
    if !inserted {
        let reason = "due to failed malicious node cuckoo insert";
        println!("{} iteration {} / {} {}", prefix, iter, iters, reason);
    }
    if !inserted_below_bmax {
        let reason = "as b_max was broken by moving malicious node";
        println!("{} iteration {} / {} {}", prefix, iter, iters, reason);
    }
    if !rejoined_below_bmax {
        let reason = "as b_max was broken by re-joining node".to_string();
        println!("{} iteration {} / {} {} {}", prefix, iter, iters, reason, number_rejoined + 1);
    }
    inserted && inserted_below_bmax && rejoined_below_bmax
}

fn run_sim_for_seed(h: NodeCount, m: NodeCount, lg_r: NodeCount, lg_c: NodeCount,
                    k: NodeCount, g: NodeCount, iters: usize, seed: usize) ->
                    (f64, NodeCount, NodeCount)
{
    let blankregion = Region {
        num_honest: 0,
        num_malicious: 0,
        last_join: 0,
        num_nodes_since_last_primary_join: 0,
    };

    let blankquorum = Quorum {
        tot_honest: 0,
        tot_malicious: 0,
        tot_last_join: 0,
    };

    let mut sim = Simulation {
        done_init: false,
        now: 0,
        rand: Pcg64::seed_from_u64(seed as u64),
        quorums: Vec::new(),
        regions: Vec::new(),
        lg_regions_per_quorum: lg_c,
        num_region_mask: (1<<lg_r)-1,
        cur_stats: CurrentStats::default(),
        cum_stats: CumulativeStats::default(),
        k,
        g,
    };

    sim.regions.resize(1<<lg_r, blankregion);
    sim.quorums.resize(1<<(lg_r-lg_c), blankquorum);

    eprintln!("Starting simulation n={}, h={}, m={}, 2^r={} regions, CCR: k={}, desired quorum size: g={}, regions/quorum: 2^c={},  T={}, seed={}",
              h+m, h, m, 1 << lg_r, k, g, 1 << lg_c, iters, seed);

    let successful_init = run_print_errors(&mut sim, true, h, m, 0, 0);
    let mut successful_move_malicious = false;
    if successful_init {
        for iter in 0..iters {
            successful_move_malicious = run_print_errors(&mut sim, false, 0, 0, iter, iters);
            if !successful_move_malicious
                {break;}
        }
    }

    println!("Results for seed={} h={} m={} 2^r={} k={} g={} 2^c={} T={}:",
             seed, h, m, 1 << lg_r, k, g, 1<< lg_c, iters);
    sim.cum_stats.print();

    if successful_init && successful_move_malicious
    {
        (sim.cum_stats.max_b_0, sim.cum_stats.min_tot_nodes, sim.cum_stats.max_tot_nodes)
    } else {
        (999.0, 1000000, 0)
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() != 9 {
        usage(&args[0]);
        process::exit(1);
    }

    let valid_args: Vec<usize> = args.iter().enumerate()
        .filter(|(i, _)| *i != 0)
        .map(|(_, v)| v.parse::<usize>())
        .filter(|x| x.is_ok())
        .map(|x| x.unwrap())
        .collect();

    if valid_args.len() != 8 {
        usage(&args[0]);
        process::exit(1);
    }

    let h = valid_args[0];
    let m = valid_args[1];
    let lg_r = valid_args[2];
    let k = valid_args[3];
    let g = valid_args[4];
    let lg_c = valid_args[5];
    let iters = valid_args[6];
    let no_of_seeds = valid_args[7];

    if (lg_c > lg_r) | (k > g) {
        usage(&args[0]);
        process::exit(1);
    }

    let mut b_0_array : Vec<f64> = vec![999.0; no_of_seeds];
    let mut mins_array : Vec<NodeCount> = vec![1000; no_of_seeds];
    let mut maxs_array : Vec<NodeCount> = vec![0; no_of_seeds];
    for seed_ctr in 1..no_of_seeds + 1 {
        let (b0, mins, maxs) = run_sim_for_seed(h, m, lg_r, lg_c, k, g, iters, seed_ctr);
        b_0_array[seed_ctr - 1] = b0;
        mins_array[seed_ctr - 1] = mins;
        maxs_array[seed_ctr - 1] = maxs;
    }
    let max_b_0 = b_0_array.iter().cloned().fold(-1./0. /* -inf */, f64::max);
    let min_mins = mins_array.iter().cloned().fold(1000, NodeCount::min);
    let max_maxs = maxs_array.iter().cloned().fold(0, NodeCount::max);
    println!("FINAL Max_b_0={} min_s={} max_s={}", max_b_0, min_mins, max_maxs);
}
