mod bitmatrix;
mod bitvec;
mod colgroup;
mod intmatrix;

use tempfile::tempdir;

use crate::{PersistentCompactIntVec, PersistentCompactIntVecBuilder};

fn roundtrip(values: &[(usize, u32)], n: usize) -> Vec<u32> {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(n, &path).unwrap();
    for &(slot, v) in values {
        b.set(slot, v);
    }
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    (0..n).map(|s| r.get(s)).collect()
}

fn make_pciv(values: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("v.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(values.len(), &path).unwrap();
    for (i, &v) in values.iter().enumerate() {
        b.set(i, v);
    }
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    (dir, r)
}

#[test]
fn all_zero_by_default() {
    let got = roundtrip(&[], 8);
    assert!(got.iter().all(|&v| v == 0));
}

#[test]
fn small_values_no_overflow() {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(4, &path).unwrap();
    b.set(0, 1);
    b.set(1, 254);
    b.set(2, 0);
    b.set(3, 100);
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert_eq!(r.get(0), 1);
    assert_eq!(r.get(1), 254);
    assert_eq!(r.get(2), 0);
    assert_eq!(r.get(3), 100);
}

#[test]
fn overflow_values_roundtrip() {
    let values = [(0, 255), (1, 1000), (2, 50), (3, 1_313_691)];
    let got = roundtrip(&values, 4);
    assert_eq!(got[0], 255);
    assert_eq!(got[1], 1000);
    assert_eq!(got[2], 50);
    assert_eq!(got[3], 1_313_691);
}

#[test]
fn mutation_downward_removes_from_overflow() {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(2, &path).unwrap();
    b.set(0, 1000);
    b.set(0, 42);
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert_eq!(r.get(0), 42);
    assert_eq!(r.step, 0, "no overflow entries expected");
}

#[test]
fn mutation_upward_updates_overflow() {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(1, &path).unwrap();
    b.set(0, 300);
    b.set(0, 500);
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert_eq!(r.get(0), 500);
}

#[test]
fn sparse_index_built_for_many_overflows() {
    let n    = 5000usize;
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(n, &path).unwrap();
    for i in 0..n {
        b.set(i, 1000 + i as u32);
    }
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert!(r.step > 0, "sparse index should have been built");
    for i in 0..n {
        assert_eq!(r.get(i), 1000 + i as u32, "mismatch at slot {i}");
    }
}

#[test]
fn iter_matches_get() {
    let values = [(0usize, 255u32), (1, 1000), (2, 50), (3, 1_313_691), (4, 7), (5, 42)];
    let n = 6;
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(n, &path).unwrap();
    for &(slot, v) in &values {
        b.set(slot, v);
    }
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();

    let via_iter: Vec<u32> = r.iter().collect();
    let via_get:  Vec<u32> = (0..n).map(|s| r.get(s)).collect();
    assert_eq!(via_iter, via_get);
}

#[test]
fn iter_size_hint_exact() {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(5, &path).unwrap();
    b.set(2, 1000);
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    let mut it = r.iter();
    assert_eq!(it.len(), 5);
    it.next();
    assert_eq!(it.len(), 4);
}

#[test]
fn into_iter_for_ref() {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(3, &path).unwrap();
    b.set(0, 10);
    b.set(1, 500);
    b.set(2, 30);
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    let collected: Vec<u32> = (&r).into_iter().collect();
    assert_eq!(collected, vec![10, 500, 30]);
}

#[test]
fn build_from_roundtrip() {
    let a = vec![0u32, 50, 255, 1000, 7, 1_313_691];
    let (_dir_a, ra) = make_pciv(&a);
    let dir_b = tempdir().unwrap();
    let path_b = dir_b.path().join("b.pciv");
    PersistentCompactIntVecBuilder::build_from(&ra, &path_b).unwrap().close().unwrap();
    let rb = PersistentCompactIntVec::open(&path_b).unwrap();
    assert_eq!(ra.iter().collect::<Vec<_>>(), rb.iter().collect::<Vec<_>>());
}

#[test]
fn combine_min() {
    let (_da, ra) = make_pciv(&[10, 300, 0,   1000]);
    let (_db, rb) = make_pciv(&[20, 100, 500, 800]);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pciv");
    let mut b = PersistentCompactIntVecBuilder::build_from(&ra, &path).unwrap();
    b.min(rb.view());
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![10, 100, 0, 800]);
}

#[test]
fn combine_max() {
    let (_da, ra) = make_pciv(&[10, 300, 0,   1000]);
    let (_db, rb) = make_pciv(&[20, 100, 500, 800]);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pciv");
    let mut b = PersistentCompactIntVecBuilder::build_from(&ra, &path).unwrap();
    b.max(rb.view());
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![20, 300, 500, 1000]);
}

#[test]
fn combine_add() {
    let (_da, ra) = make_pciv(&[10, 200, 0, 100]);
    let (_db, rb) = make_pciv(&[20, 100, 5, 1]);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pciv");
    let mut b = PersistentCompactIntVecBuilder::build_from(&ra, &path).unwrap();
    b.add(rb.view());
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![30, 300, 5, 101]);
}

#[test]
fn sum_aggregation() {
    let (_dir, r) = make_pciv(&[10, 200, 0, 1000, 7]);
    assert_eq!(r.sum(), 10 + 200 + 0 + 1000 + 7);
}

#[test]
fn sum_aggregation_with_overflow_values() {
    let (_dir, r) = make_pciv(&[100, 1_000_000, 200]);
    assert_eq!(r.sum(), 100u64 + 1_000_000 + 200);
}

#[test]
fn combine_diff() {
    let (_da, ra) = make_pciv(&[20, 1000, 5,  0]);
    let (_db, rb) = make_pciv(&[10, 300,  10, 1]);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pciv");
    let mut b = PersistentCompactIntVecBuilder::build_from(&ra, &path).unwrap();
    b.diff(rb.view());
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![10, 700, 0, 0]);
}

#[test]
fn bray_dist_basic() {
    // min=[1,0,0], sum_a=2, sum_b=2 → 1 - 2*1/4 = 0.5
    let (_da, ra) = make_pciv(&[1, 0, 1]);
    let (_db, rb) = make_pciv(&[1, 1, 0]);
    let d = ra.bray_dist(&rb);
    assert!((d - 0.5).abs() < 1e-12, "got {d}");
}

#[test]
fn bray_dist_identical() {
    let (_d, r) = make_pciv(&[3, 7, 2]);
    assert_eq!(r.bray_dist(&r), 0.0);
}

#[test]
fn bray_dist_disjoint() {
    // no overlap → min=0 → BC=1
    let (_da, ra) = make_pciv(&[1, 0]);
    let (_db, rb) = make_pciv(&[0, 1]);
    assert_eq!(ra.bray_dist(&rb), 1.0);
}

#[test]
fn relfreq_bray_dist_basic() {
    // [2,0] vs [0,2]: rel_freqs [1,0] vs [0,1], min=0 → dist=1
    let (_da, ra) = make_pciv(&[2, 0]);
    let (_db, rb) = make_pciv(&[0, 2]);
    assert_eq!(ra.relfreq_bray_dist(&rb), 1.0);
    // [2,0] vs [1,0]: same direction → dist=0
    let (_dc, rc) = make_pciv(&[1, 0]);
    assert_eq!(ra.relfreq_bray_dist(&rc), 0.0);
}

#[test]
fn euclidean_dist_basic() {
    // [3,4] vs [0,0] → 5
    let (_da, ra) = make_pciv(&[3, 4]);
    let (_db, rb) = make_pciv(&[0, 0]);
    assert!((ra.euclidean_dist(&rb) - 5.0).abs() < 1e-12);
}

#[test]
fn euclidean_dist_identical() {
    let (_d, r) = make_pciv(&[3, 7, 2]);
    assert_eq!(r.euclidean_dist(&r), 0.0);
}

#[test]
fn hellinger_euclidean_dist_disjoint() {
    // [4,0] vs [0,4]: sqrt(relfreq) = [1,0] vs [0,1] → euclidean = sqrt(2)
    let (_da, ra) = make_pciv(&[4, 0]);
    let (_db, rb) = make_pciv(&[0, 4]);
    assert!((ra.hellinger_euclidean_dist(&rb) - 2f64.sqrt()).abs() < 1e-12);
}

#[test]
fn hellinger_euclidean_dist_identical() {
    let (_d, r) = make_pciv(&[3, 7, 2]);
    assert_eq!(r.hellinger_euclidean_dist(&r), 0.0);
}

#[test]
fn jaccard_dist_basic() {
    // ref={0,2,3} other={0,1,2} → inter=2 union=4 → dist=0.5
    let (_da, ra) = make_pciv(&[1, 0, 1, 1]);
    let (_db, rb) = make_pciv(&[1, 1, 1, 0]);
    assert!((ra.jaccard_dist(&rb) - 0.5).abs() < 1e-12);
}

#[test]
fn jaccard_dist_identical() {
    let (_d, r) = make_pciv(&[3, 0, 7]);
    assert_eq!(r.jaccard_dist(&r), 0.0);
}

#[test]
fn hellinger_dist_disjoint() {
    // disjoint → hellinger_euclidean = sqrt(2) → hellinger = 1.0
    let (_da, ra) = make_pciv(&[4, 0]);
    let (_db, rb) = make_pciv(&[0, 4]);
    assert!((ra.hellinger_dist(&rb) - 1.0).abs() < 1e-12);
}

#[test]
fn hellinger_dist_identical() {
    let (_d, r) = make_pciv(&[3, 7, 2]);
    assert_eq!(r.hellinger_dist(&r), 0.0);
}

#[test]
fn threshold_jaccard_dist_with_threshold() {
    // threshold=3: ref={0,2} (values 5,4≥3) other={1,2} (values 3,4≥3)
    // inter={2}=1, union={0,1,2}=3 → dist=2/3
    let (_da, ra) = make_pciv(&[5, 1, 4]);
    let (_db, rb) = make_pciv(&[2, 3, 4]);
    let d = ra.threshold_jaccard_dist(&rb, 3);
    assert!((d - 2.0 / 3.0).abs() < 1e-12, "got {d}");
}

#[test]
fn mixed_large_dataset() {
    let n    = 1000usize;
    let dir  = tempdir().unwrap();
    let path = dir.path().join("test.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(n, &path).unwrap();
    for i in 0..n {
        let v = if i % 100 == 0 { 100_000 + i as u32 } else { i as u32 % 200 };
        b.set(i, v);
    }
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    for i in 0..n {
        let expected = if i % 100 == 0 { 100_000 + i as u32 } else { i as u32 % 200 };
        assert_eq!(r.get(i), expected, "slot {i}");
    }
}
