use crate::core::ScoreType;
use crate::esruler_compat::EsRulerCompat;

#[allow(clippy::too_many_arguments)]
#[cfg(feature = "gpu")]
pub(crate) fn run_multilevel_gsea_impl(
    n_total: usize,
    scaled_scores: &[i64],
    k: usize,
    obs_es: f64,
    score_type: ScoreType,
    sample_size: usize,
    seed: u64,
    eps: f64,
) -> (f64, bool, Option<f64>) {
    run_multilevel_gsea_esruler(
        n_total,
        scaled_scores,
        k,
        obs_es,
        score_type,
        sample_size,
        seed,
        eps,
    )
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn run_multilevel_gsea_group_impl(
    n_total: usize,
    scaled_scores: &[i64],
    k: usize,
    obs_es_list: &[f64],
    score_type: ScoreType,
    sample_size: usize,
    seed: u64,
    eps: f64,
) -> Vec<(f64, bool, Option<f64>)> {
    run_multilevel_gsea_esruler_group(
        n_total,
        scaled_scores,
        k,
        obs_es_list,
        score_type,
        sample_size,
        seed,
        eps,
    )
}

#[allow(clippy::too_many_arguments)]
#[cfg(feature = "gpu")]
fn run_multilevel_gsea_esruler(
    n_total: usize,
    scaled_scores: &[i64],
    k: usize,
    obs_es: f64,
    score_type: ScoreType,
    sample_size: usize,
    seed: u64,
    eps: f64,
) -> (f64, bool, Option<f64>) {
    if k == 0 || n_total == 0 {
        return (1.0, true, Some(0.0));
    }
    let pos_ranks: Vec<i64> = scaled_scores.iter().map(|v| v.abs()).collect();
    let mut neg_ranks = pos_ranks.clone();
    neg_ranks.reverse();

    let mut es_pos = EsRulerCompat::new(pos_ranks, sample_size, k, 1.0, false);
    let mut es_neg = EsRulerCompat::new(neg_ranks, sample_size, k, 1.0, false);

    if obs_es >= 0.0 {
        es_pos.extend(obs_es.abs(), seed, eps);
    } else {
        es_neg.extend(obs_es.abs(), seed, eps);
    }

    let sign = matches!(score_type, ScoreType::Pos | ScoreType::Neg);
    let (p, is_cp_ge_half, err) = if obs_es >= 0.0 {
        es_pos.get_pvalue(obs_es.abs(), eps, sign)
    } else {
        es_neg.get_pvalue(obs_es.abs(), eps, sign)
    };

    if err.is_finite() {
        (p, is_cp_ge_half, Some(err))
    } else {
        (p, is_cp_ge_half, None)
    }
}

#[allow(clippy::too_many_arguments)]
fn run_multilevel_gsea_esruler_group(
    n_total: usize,
    scaled_scores: &[i64],
    k: usize,
    obs_es_list: &[f64],
    score_type: ScoreType,
    sample_size: usize,
    seed: u64,
    eps: f64,
) -> Vec<(f64, bool, Option<f64>)> {
    if k == 0 || n_total == 0 || obs_es_list.is_empty() {
        return vec![(1.0, true, Some(0.0)); obs_es_list.len()];
    }

    let pos_ranks: Vec<i64> = scaled_scores.iter().map(|v| v.abs()).collect();
    let mut neg_ranks = pos_ranks.clone();
    neg_ranks.reverse();

    let mut es_pos = EsRulerCompat::new(pos_ranks, sample_size, k, 1.0, false);
    let mut es_neg = EsRulerCompat::new(neg_ranks, sample_size, k, 1.0, false);

    let mut max_es = f64::NEG_INFINITY;
    let mut min_es = f64::INFINITY;
    for &es in obs_es_list {
        if es > max_es {
            max_es = es;
        }
        if es < min_es {
            min_es = es;
        }
    }

    if max_es >= 0.0 {
        es_pos.extend(max_es.abs(), seed, eps);
    }
    if min_es < 0.0 {
        es_neg.extend(min_es.abs(), seed, eps);
    }

    let sign = matches!(score_type, ScoreType::Pos | ScoreType::Neg);
    obs_es_list
        .iter()
        .copied()
        .map(|cur_es| {
            let (p, is_cp_ge_half, err) = if cur_es >= 0.0 {
                es_pos.get_pvalue(cur_es.abs(), eps, sign)
            } else {
                es_neg.get_pvalue(cur_es.abs(), eps, sign)
            };
            if err.is_finite() {
                (p, is_cp_ge_half, Some(err))
            } else {
                (p, is_cp_ge_half, None)
            }
        })
        .collect()
}
