#include<iostream>
#include<fstream>
#include<vector>
#include <random>
#include <algorithm>
#include <numeric>
#include <cmath>
#include <limits>
#include <string>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <stdexcept>
#include <nlohmann/json.hpp>
using namespace std;
using nlohmann::json;



const float INF = std::numeric_limits<float>::infinity();


typedef vector<int> VI;
typedef vector<VI> V2I;
typedef vector<V2I> V3I;
typedef vector<float> VD;
typedef vector<VD> V2D;
typedef vector<V2D> V3D;

float p;
// float kappa;
int n_simulations, n_steps;
int energy_type;

/*
Parameters: 
- L, U: lower and upper parameters of target interval 
- p, coin bias
- tau: first time things count
- T: final time horizon
- zeta: energy function

ValProb(t,c) = prob to ever leave the target interval when you have seen t coins, with c ones_ours

*/


random_device rd;     // nondeterministic seed source
mt19937 rng(rd());    // seed Mersenne Twister

int bernoulli(double bias) {
    // return std::generate_canonical<double, 53>(rng) < p;
    return generate_canonical<float, numeric_limits<float>::digits>(rng) < bias ? 1 : 0;
}


float energy_function_monotonic(float x, float r, float safetyL, float safetyU, float limitL, float limitU, float p) {
    int m = 2;
    float alpha_r = (1-r)/r;
    if (p < limitL) {
        float kappa = (safetyU+limitU)/2;
        float a_r = (1.0-r)*limitL + r*limitU;
        float C_r = (a_r - p)/(1.0 - p);
        if (x < a_r) { 
            return C_r + (1-C_r)*(1-exp((x-a_r)/alpha_r));
        }
        if (x > kappa) {
            return 1-exp(-pow((x-kappa)/alpha_r, m));
        }
        return C_r*pow(1-(x-a_r)/(kappa-a_r), alpha_r);
    }
    if (p > limitU) {
        float kappa = (safetyL+limitL)/2;
        float a_r = (r)*limitL + (1.0-r)*limitU;
        float C_r = (p-a_r)/(p);
        if (x < kappa) {
            return 1-exp(-pow((x-kappa)/alpha_r, m));
        }
        if (x > a_r) {
            return C_r + (1-C_r)*(1-exp((a_r-x)/alpha_r));
        }
        return C_r*pow(1-(x-a_r)/(kappa-a_r), alpha_r);
    }
    float beta_r = 2;
    float kappa = p;
    float res = (1/alpha_r)*pow(abs(x-kappa),beta_r);
    if (res > 1.0) return 1.0;
    return res;
}


float zeta(float x) {
    if (energy_type == 1) {
        return 0.8*(x-0.5)*(x-0.5); 
    }
    throw std::invalid_argument("energy_type="  + to_string(energy_type) + " not implemented");
}

class A3IShield {
public:
    A3IShield(int tau, float p, float L, float U, int accOnes, int accZeros) {
        tau_ = tau; p_ = p; L_ = L; U_ = U;
        accOnes_ = accOnes; accZeros_ = accZeros;
        memo_ = V2D(tau_ + 1, VD(tau_ + 1, -1));
        (void)val(0, 0); // precompute starting state lazily
    }

    void reset(int accOnes, int accZeros) {
        accOnes_ = accOnes;
        accZeros_ = accZeros;
        // Clear memo and recompute from the new starting state
        memo_ = V2D(tau_ + 1, VD(tau_ + 1, -1));
        // (void)val(0, 0);
    }

    // val(t,c) is the expected value of enforcing the target fairness having seen t candidates, among them c ones_ours.
    float val(int t, int c) {
        float &res = memo_[t][c];
        if (res != -1) return res;
        int cTot = c + accOnes_;
        int tTot = t + accOnes_ + accZeros_;
        if (t == tau_) {
            if ((cTot < tTot * L_) || (cTot > tTot * U_)) return res = INF;
            return res = 0.0f;
        }
        float costAccOne = val(t+1,c+1);
        float costRejOne = val(t+1,c) + 1;
        float costAccZero = val(t+1,c);
        float costRejZero = val(t+1,c+1)+1;
        return res = p_*min(costAccOne,costRejOne) + (1-p_)*min(costAccZero,costRejZero);
    }

private:
    // Parameters
    int tau_ = 0, accOnes_ = 0, accZeros_ = 0;
    float p_ = 0.5, L_ = 0.0f, U_ = 1.0f;
    float kappa_ = 0.3f;
    // Memo table
    V2D memo_;
    float zeta_local(float x)  { 
        return (x - kappa_) * (x - kappa_); 
    }
};





// Helper functions to compute statistics over integer samples
static float median_sorted(const vector<int>& a) {
    int n = (int)a.size();
    if (n == 0) return 0.0f;
    if (n % 2) return (float)a[n/2];
    return (a[n/2 - 1] + a[n/2]) / 2.0f;
}

static void compute_stats(const vector<int>& vals, float& mean, float& stddev, float& q1, float& q2, float& q3) {
    if (vals.empty()) { mean = stddev = q1 = q2 = q3 = 0.0f; return; }
    // mean
    float sum = accumulate(vals.begin(), vals.end(), 0.0f);
    int n = (int)vals.size();
    mean = sum / n;
    // stddev (sample)
    if (n > 1) {
        float acc = 0.0f;
        for (int x : vals) acc += (x - mean) * (x - mean);
        stddev = sqrt(acc / (n - 1));
    } else {
        stddev = 0.0f;
    }
    // quartiles using median of halves (exclude median when n is odd)
    vector<int> s(vals.begin(), vals.end());
    sort(s.begin(), s.end());
    q2 = median_sorted(s);
    vector<int> lower, upper;
    if (n % 2) {
        lower.assign(s.begin(), s.begin() + n/2);
        upper.assign(s.begin() + n/2 + 1, s.end());
    } else {
        lower.assign(s.begin(), s.begin() + n/2);
        upper.assign(s.begin() + n/2, s.end());
    }
    q1 = lower.empty() ? (float)s.front() : median_sorted(lower);
    q3 = upper.empty() ? (float)s.back()  : median_sorted(upper);
}

// Floating-point variant for outcome statistics
static void compute_stats_float(const vector<float>& vals, float& mean, float& stddev, float& q1, float& q2, float& q3) {
    if (vals.empty()) { mean = stddev = q1 = q2 = q3 = 0.0f; return; }
    float sum = 0.0f;
    for (float v : vals) sum += v;
    int n = (int)vals.size();
    mean = sum / n;
    if (n > 1) {
        float acc = 0.0f;
        for (float v : vals) acc += (v - mean) * (v - mean);
        stddev = sqrt(acc / (n - 1));
    } else {
        stddev = 0.0f;
    }
    vector<float> s(vals.begin(), vals.end());
    sort(s.begin(), s.end());
    auto median_sorted_f = [](const vector<float>& a) -> float {
        int n = (int)a.size();
        if (n == 0) return 0.0f;
        if (n % 2) return a[n/2];
        return (a[n/2 - 1] + a[n/2]) / 2.0f;
    };
    q2 = median_sorted_f(s);
    vector<float> lower, upper;
    if (n % 2) {
        lower.assign(s.begin(), s.begin() + n/2);
        upper.assign(s.begin() + n/2 + 1, s.end());
    } else {
        lower.assign(s.begin(), s.begin() + n/2);
        upper.assign(s.begin() + n/2, s.end());
    }
    q1 = lower.empty() ? s.front() : median_sorted_f(lower);
    q3 = upper.empty() ? s.back()  : median_sorted_f(upper);
}

void simulate(int n_steps, int n_simulations,
              bool naive_shield, bool aaai_shield,
              float safetyL, float safetyU,
              float limitL, float limitU,
              float naiveL, float naiveU,
              float aaaiL, float aaaiU,
              float aaaip1, int aaaitau,
              const string& out_path) {
    // Raw trajectories (ours): [simulation][step][metric: 0=zeros,1=ones,2=cost]
    V3I raw_ours(n_simulations, V2I(n_steps, VI(3,0)));
    V3I raw_naive; if (naive_shield) raw_naive.assign(n_simulations, V2I(n_steps, VI(3,0)));
    V3I raw_aaai;  if (aaai_shield)  raw_aaai.assign(n_simulations, V2I(n_steps, VI(3,0)));
    A3IShield a3ishield(aaaitau, aaaip1, aaaiL, aaaiU, 0,0);
    float kappa = p;
    if (p < limitL) kappa = (limitU + safetyU)/2.0;
    if (p > limitU) kappa = (limitL + safetyL)/2.0;
    // kappa = 0.5;

    for (int i = 0; i < n_simulations; ++i) {
        // Ours counters
        int o_ones = 0, o_zeros = 0, b_cost = 0;
        // Naive counters
        int n_ones = 0, n_zeros = 0, n_cost = 0;
        // AAAI counters
        int a_ones = 0, a_zeros = 0, a_cost = 0;
        int a_shield_ones = 0, a_shield_zeros = 0;

        double current_outcome_ours = 0.5; // kept as in baseline code
        double current_outcome_naive = 0.5;
        double current_outcome_aaai = 0.5;
        for (int j = 0; j < n_steps; ++j) {
            // Ours first flip with bias p
            int first_toss = bernoulli(p);
            // Baseline keep/enforce logic (unchanged)
            bool keep1 = ((first_toss == 1) && (current_outcome_ours <= kappa)) || ((first_toss == 0) && (current_outcome_ours >= kappa));
            // float energy = zeta(current_outcome_ours);
            float energy = energy_function_monotonic(current_outcome_ours, 0.1, safetyL, safetyU, limitL, limitU, p);
            bool keep2 = bernoulli(energy) == 0;
            bool enforce = !(keep1 || keep2);
            if (((first_toss == 1) && !enforce) || ((first_toss == 0) && enforce)) ++o_ones; 
            else ++o_zeros;
            if (enforce) ++b_cost;
            raw_ours[i][j][0] = o_zeros;
            raw_ours[i][j][1] = o_ones;
            raw_ours[i][j][2] = b_cost;
            current_outcome_ours = (float)(o_ones)/((float)(o_ones+o_zeros));

            // Naive shield: try to keep fraction within [naiveL, naiveU] by enforcing flips
            if (naive_shield) {
                // Decide resulting bit to keep
                int bit = first_toss;
                int next_total = (n_ones + n_zeros) + 1;
                // If accepting first_toss would violate bounds, enforce opposite decision
                float next_ratio_if_obs = float(n_ones + (first_toss == 1)) / float(next_total);
                bool need_enforce = false;
                if (next_ratio_if_obs < naiveL) {
                    bit = 1; need_enforce = (first_toss != 1);
                } else if (next_ratio_if_obs > naiveU) {
                    bit = 0; need_enforce = (first_toss != 0);
                }
                if (bit == 1) ++n_ones; else ++n_zeros;
                if (need_enforce) ++n_cost;
                raw_naive[i][j][0] = n_zeros;
                raw_naive[i][j][1] = n_ones;
                raw_naive[i][j][2] = n_cost;
            }

            // AAAI shield: reuses first_toss as intended; same acceptance pattern
            if (aaai_shield) {

                float costAccept = a3ishield.val(a_shield_ones+a_shield_zeros+1, a_shield_ones+first_toss);
                float costReject = a3ishield.val(a_shield_ones+a_shield_zeros+1, a_shield_ones + 1 - first_toss) + 1;
                if (costAccept <= costReject) {
                    if (first_toss == 1) {++a_ones; ++a_shield_ones;}
                    else {++a_zeros; ++a_shield_zeros;}
                }
                else {
                    if (first_toss == 1) {++a_zeros; ++a_shield_zeros;}
                    else {++a_ones; ++a_shield_ones;}
                    ++a_cost;
                }
                if (a_shield_ones + a_shield_zeros == aaaitau) {
                    a_shield_ones = 0;
                    a_shield_zeros = 0;
                    a3ishield.reset(a_ones, a_zeros);
                }
                raw_aaai[i][j][0] = a_zeros;
                raw_aaai[i][j][1] = a_ones;
                raw_aaai[i][j][2] = a_cost;
                current_outcome_aaai = (float)(a_ones)/((float)(a_ones+a_zeros));
            }   
        }
    }

    // Stats containers per variant
    auto compute_and_write = [&](ofstream& ofs, const string& prefix, const V3I& raw){
        V2D stats(n_steps, VD(3*5, 0.0f));
        for (int j = 0; j < n_steps; ++j) {
            vector<int> zeros_vals; zeros_vals.reserve(n_simulations);
            vector<int> ones_vals;  ones_vals.reserve(n_simulations);
            vector<int> cost_vals;  cost_vals.reserve(n_simulations);
            for (int i = 0; i < n_simulations; ++i) {
                zeros_vals.push_back(raw[i][j][0]);
                ones_vals.push_back(raw[i][j][1]);
                cost_vals.push_back(raw[i][j][2]);
            }
            float mean, stddev, q1, q2, q3;
            compute_stats(zeros_vals, mean, stddev, q1, q2, q3);
            stats[j][0] = mean; stats[j][1] = stddev; stats[j][2] = q1; stats[j][3] = q2; stats[j][4] = q3;
            compute_stats(ones_vals, mean, stddev, q1, q2, q3);
            stats[j][5] = mean; stats[j][6] = stddev; stats[j][7] = q1; stats[j][8] = q2; stats[j][9] = q3;
            compute_stats(cost_vals, mean, stddev, q1, q2, q3);
            stats[j][10] = mean; stats[j][11] = stddev; stats[j][12] = q1; stats[j][13] = q2; stats[j][14] = q3;
        }
        // Emit columns
        for (int j = 0; j < n_steps; ++j) {
            for (int k = 0; k < 15; ++k) ofs << "," << stats[j][k];
        }
    };

    // Write CSV header
    ofstream ofs(out_path);
    ofs.setf(ios::fixed); ofs.precision(6);
    ofs << "step";
    // header for ours: outcome (mean,std,q1,q2,q3) and cost
    ofs << ",ours_outcome_mean,ours_outcome_std,ours_outcome_q1,ours_outcome_q2,ours_outcome_q3"
        << ",ours_cost_mean,ours_cost_std,ours_cost_q1,ours_cost_q2,ours_cost_q3";
    if (naive_shield) {
        ofs << ",naive_outcome_mean,naive_outcome_std,naive_outcome_q1,naive_outcome_q2,naive_outcome_q3"
            << ",naive_cost_mean,naive_cost_std,naive_cost_q1,naive_cost_q2,naive_cost_q3";
    }
    if (aaai_shield) {
        ofs << ",aaai_outcome_mean,aaai_outcome_std,aaai_outcome_q1,aaai_outcome_q2,aaai_outcome_q3"
            << ",aaai_cost_mean,aaai_cost_std,aaai_cost_q1,aaai_cost_q2,aaai_cost_q3";
    }
    ofs << "\n";

    // Write per-step rows
    for (int j = 0; j < n_steps; ++j) {
        ofs << j;
        // ours columns for step j: outcome then cost
        {
            vector<int> z, o, c; z.reserve(n_simulations); o.reserve(n_simulations); c.reserve(n_simulations);
            for (int i = 0; i < n_simulations; ++i) { z.push_back(raw_ours[i][j][0]); o.push_back(raw_ours[i][j][1]); c.push_back(raw_ours[i][j][2]); }
            // compute outcome per simulation
            vector<float> outcomes; outcomes.reserve(n_simulations);
            for (int idx = 0; idx < n_simulations; ++idx) {
                int ones = o[idx]; int zeros = z[idx];
                int tot = ones + zeros;
                float val = tot > 0 ? float(ones) / float(tot) : 0.0f;
                outcomes.push_back(val);
            }
            float mean, stddev, q1, q2, q3;
            compute_stats_float(outcomes, mean, stddev, q1, q2, q3); ofs << "," << mean << "," << stddev << "," << q1 << "," << q2 << "," << q3;
            compute_stats(c, mean, stddev, q1, q2, q3); ofs << "," << mean << "," << stddev << "," << q1 << "," << q2 << "," << q3;
        }
        if (naive_shield) {
            vector<int> z, o, c; z.reserve(n_simulations); o.reserve(n_simulations); c.reserve(n_simulations);
            for (int i = 0; i < n_simulations; ++i) { z.push_back(raw_naive[i][j][0]); o.push_back(raw_naive[i][j][1]); c.push_back(raw_naive[i][j][2]); }
            vector<float> outcomes; outcomes.reserve(n_simulations);
            for (int idx = 0; idx < n_simulations; ++idx) {
                int ones = o[idx]; int zeros = z[idx];
                int tot = ones + zeros;
                float val = tot > 0 ? float(ones) / float(tot) : 0.0f;
                outcomes.push_back(val);
            }
            float mean, stddev, q1, q2, q3;
            compute_stats_float(outcomes, mean, stddev, q1, q2, q3); ofs << "," << mean << "," << stddev << "," << q1 << "," << q2 << "," << q3;
            compute_stats(c, mean, stddev, q1, q2, q3); ofs << "," << mean << "," << stddev << "," << q1 << "," << q2 << "," << q3;
        }
        if (aaai_shield) {
            vector<int> z, o, c; z.reserve(n_simulations); o.reserve(n_simulations); c.reserve(n_simulations);
            for (int i = 0; i < n_simulations; ++i) { z.push_back(raw_aaai[i][j][0]); o.push_back(raw_aaai[i][j][1]); c.push_back(raw_aaai[i][j][2]); }
            vector<float> outcomes; outcomes.reserve(n_simulations);
            for (int idx = 0; idx < n_simulations; ++idx) {
                int ones = o[idx]; int zeros = z[idx];
                int tot = ones + zeros;
                float val = tot > 0 ? float(ones) / float(tot) : 0.0f;
                outcomes.push_back(val);
            }
            float mean, stddev, q1, q2, q3;
            compute_stats_float(outcomes, mean, stddev, q1, q2, q3); ofs << "," << mean << "," << stddev << "," << q1 << "," << q2 << "," << q3;
            compute_stats(c, mean, stddev, q1, q2, q3); ofs << "," << mean << "," << stddev << "," << q1 << "," << q2 << "," << q3;
        }
        ofs << "\n";
    }
    ofs.close();
}

static void print_help(const char* prog) {
    cout << "Usage:\n"
         << "  " << prog << " --config <config.json>\n"
         << "  " << prog << " --config=config.json\n\n"
         << "Options:\n"
         << "  --config, -c   Path to JSON configuration file (required)\n"
         << "  --help, -h     Show this help message\n\n"
         << "The configuration JSON must contain the following fields:\n"
         << "  n_simulations (int > 0), n_steps (int > 0), p (0..1), kappa (0..1), energy_type (int)\n"
         << "Optional fields:\n"
         << "  energy_params: array (e.g. [alpha]) or energy1_alpha\n"
         << "  naive_shield (bool), aaai_shield (bool), naiveL (0..1), naiveU (0..1), aaaip1 (0..1), aaaitau (int>2)\n"
         << "  aaaiL, safetyL, limitL (0..1), aaaiU, safetyU, limitU (0..1)\n\n"
         << "Output: Writes a CSV with per-step statistics (mean, std, Q1, Q2, Q3) for zeros, ones, and cost; optionally also naive/aaai if enabled.\n";
}

int main(int argc, char** argv) {
    if (argc < 2) {
        print_help(argv[0]);
        return 1;
    }

    string config_path;

    // Simple argument parsing: accept --config <file>, --config=<file>, -c <file>
    for (int i = 1; i < argc; ++i) {
        string a = argv[i];
        if (a == "--help" || a == "-h") {
            print_help(argv[0]);
            return 0;
        }
        if (a == "--config" || a == "-c") {
            if (i + 1 >= argc) {
                cerr << "Error: --config requires a path argument\n";
                return 1;
            }
            config_path = argv[i + 1];
            ++i; // skip next
            continue;
        }
        const string prefix = "--config=";
        if (a.rfind(prefix, 0) == 0) {
            config_path = a.substr(prefix.size());
            continue;
        }
        // Backwards compatibility: single positional arg treated as config path
        if (config_path.empty() && a.size() > 0 && a[0] != '-') {
            config_path = a;
            continue;
        }
        // ignore unknown flags
    }

    if (config_path.empty()) {
        cerr << "Error: no config file provided. Use --config <path>\n";
        print_help(argv[0]);
        return 1;
    }

    std::ifstream ifs(config_path);
    if (!ifs) {
        cerr << "Failed to open config file: " << config_path << "\n";
        return 2;
    }

    json cfg;
    try {
        ifs >> cfg;
    } catch (const std::exception& e) {
        cerr << "Failed to parse JSON config: " << e.what() << "\n";
        return 3;
    }

    // Required parameters
    n_simulations = cfg.value("n_simulations", 0);
    n_steps = cfg.value("n_steps", 0);
    p = cfg.value("p", 0.5f);
    energy_type = cfg.value("energy_type", 1);

    float energy1_alpha = 0.0f;
    if (energy_type == 1) {
        if (cfg.contains("energy_params") && cfg["energy_params"].is_array() && cfg["energy_params"].size() > 0) {
            energy1_alpha = cfg["energy_params"][0].get<float>();
        } else {
            energy1_alpha = cfg.value("energy1_alpha", 0.0f);
        }
    } else {
        cerr << "energy_type " << energy_type << " not implemented\n";
        return 4;
    }

    // Optional shield params with defaults
    bool naive_shield = cfg.value("naive_shield", false);
    bool aaai_shield = cfg.value("aaai_shield", false);
    float naiveL = cfg.value("naiveL", 0.0f);
    float naiveU = cfg.value("naiveU", 1.0f);
    float aaaip1 = cfg.value("aaaip1", p);
    int aaaitau = cfg.value("aaaitau", 3);

    // New optional bounds (defaults 0.0)
    float aaaiL = cfg.value("aaaiL", 0.0f);
    float safetyL = cfg.value("safetyL", 0.0f);
    float limitL = cfg.value("limitL", 0.0f);
    float aaaiU = cfg.value("aaaiU", 0.0f);
    float safetyU = cfg.value("safetyU", 0.0f);
    float limitU = cfg.value("limitU", 0.0f);

    // Validate parameters
    try {
        if (n_simulations <= 0) throw std::invalid_argument("n_simulations must be a positive integer");
        if (n_steps <= 0) throw std::invalid_argument("n_steps must be a positive integer");
        if (!(p >= 0.0f && p <= 1.0f)) throw std::invalid_argument("p must be in [0,1]");
        if (!(naiveL >= 0.0f && naiveL <= 1.0f)) throw std::invalid_argument("naiveL must be in [0,1]");
        if (!(naiveU >= 0.0f && naiveU <= 1.0f)) throw std::invalid_argument("naiveU must be in [0,1]");
        if (naiveL > naiveU) throw std::invalid_argument("naiveL must be <= naiveU");
        if (!(aaaip1 >= 0.0f && aaaip1 <= 1.0f)) throw std::invalid_argument("aaaip1 must be in [0,1]");
        if (aaaitau <= 2) throw std::invalid_argument("aaaitau must be > 2");
    } catch (const std::exception& e) {
        cerr << "Invalid configuration: " << e.what() << "\n";
        return 5;
    }

    // Build descriptive, unique output filename (same as before)
    std::time_t t = std::time(nullptr);
    std::tm tm = *std::localtime(&t);

    std::ostringstream oss;
    oss.setf(std::ios::fixed);
    oss << std::setprecision(3);
    oss << "sim_results"
        << "_Nsim=" << n_simulations
        << "_Nsteps=" << n_steps
        << "_p=" << p;
    oss << "_" << std::put_time(&tm, "%Y%m%d_%H%M%S") << ".csv";

    string out_path = oss.str();

    // Call simulate with parsed params

    cout << "READ INPUT:" << endl;

    cout << "Parameter values for simulate():" << endl;
    cout << "n_steps: " << n_steps << endl;
    cout << "n_simulations: " << n_simulations << endl;
    cout << "naive_shield: " << naive_shield << endl;
    cout << "aaai_shield: " << aaai_shield << endl;
    cout << "safetyL: " << safetyL << endl;
    cout << "safetyU: " << safetyU << endl;
    cout << "limitL: " << limitL << endl;
    cout << "limitU: " << limitU << endl;
    cout << "naiveL: " << naiveL << endl;
    cout << "naiveU: " << naiveU << endl;
    cout << "aaaiL: " << aaaiL << endl;
    cout << "aaaiU: " << aaaiU << endl;
    cout << "aaaip1: " << aaaip1 << endl;
    cout << "aaaitau: " << aaaitau << endl;
    cout << "out_path: " << out_path << endl;

    simulate(n_steps, n_simulations, naive_shield, aaai_shield, safetyL, safetyU, limitL, limitU, naiveL, naiveU, aaaiL, aaaiU, aaaip1, aaaitau, out_path);

    // void simulate(int n_steps, int n_simulations,
    //           bool naive_shield, bool aaai_shield,
    //           float safetyL, float safetyU,
    //           float limitL, float limitU,
    //           float naiveL, float naiveU,
    //           float aaaiL, float aaaiU,
    //           float aaaip1, int aaaitau,
    //           const string& out_path) 

    return 0;
}