extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rcflib::{
    common::multidimdatawithkey::MultiDimDataWithKey,
    rcf::{create_rcf, RCF},
};

/// try cargo test --release
/// these tests are designed to be longish

#[test]
fn anomalydetection_impute_score_and_update() {
    let shingle_size = 8;
    let base_dimension = 5;
    let data_size = 100000;
    let number_of_trees = 30;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let dimensions = shingle_size * base_dimension;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = true;
    let store_attributes: bool = false;
    let internal_shingling: bool = true;
    let internal_rotation = false;
    let noise = 5.0;

    let mut forest: Box<dyn RCF> = create_rcf(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed,
        store_attributes,
        parallel_enabled,
        internal_shingling,
        internal_rotation,
        time_decay,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );
    let mut rng = ChaCha20Rng::seed_from_u64(42);
    let mut amplitude = Vec::new();
    for _i in 0..base_dimension {
        amplitude.push((1.0 + 0.2 * rng.gen::<f32>()) * 100.0);
    }
    let data_with_key = MultiDimDataWithKey::multi_cosine(
        data_size,
        &vec![60; base_dimension],
        &amplitude,
        noise,
        0,
        base_dimension.into(),
    );

    let mut score: f64 = 0.0;
    let _next_index = 0;
    let mut error = 0.0;
    let mut count = 0;

    for i in 0..data_with_key.data.len() {
        if i > 200 {
            let next_values = forest.extrapolate(1).unwrap().values;
            assert!(next_values.len() == base_dimension);
            error += next_values
                .iter()
                .zip(&data_with_key.data[i])
                .map(|(x, y)| ((x - y) as f64 * (x - y) as f64))
                .sum::<f64>();
            count += base_dimension;
        }

        let new_score = forest.score(&data_with_key.data[i]).unwrap();
        //println!("{} {} score {}",y,i,new_score);
        /*
        if next_index < data_with_key.change_indices.len() && data_with_key.change_indices[next_index] == i {
            println!(" score at change {} position {} ", new_score, i);
            next_index += 1;
        }
        */

        score += new_score;
        forest.update(&data_with_key.data[i], 0).unwrap();
    }

    println!(
        "Average score {} ",
        (score / data_with_key.data.len() as f64)
    );
    println!("Success! {}", forest.entries_seen());
    println!("PointStore Size {} ", forest.point_store_size());
    println!("Total size {} bytes (approx)", forest.size());
    println!(
        " RMSE {},  noise {} ",
        f64::sqrt(error / count as f64),
        noise
    );
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rcflib::{
    common::multidimdatawithkey::MultiDimDataWithKey,
    rcf::{create_rcf, RCF},
};

/// try cargo test --release
/// these tests are designed to be longish

#[test]
fn anomalydetection_score_and_update() {
    let shingle_size = 8;
    let base_dimension = 5;
    let data_size = 100000;
    let number_of_trees = 30;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let dimensions = shingle_size * base_dimension;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = false;
    let store_attributes: bool = false;
    let internal_shingling: bool = true;
    let internal_rotation = false;
    let noise = 5.0;

    let mut forest: Box<dyn RCF> = create_rcf(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed,
        store_attributes,
        parallel_enabled,
        internal_shingling,
        internal_rotation,
        time_decay,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );
    let mut rng = ChaCha20Rng::seed_from_u64(42);
    let mut amplitude = Vec::new();
    for _i in 0..base_dimension {
        amplitude.push((1.0 + 0.2 * rng.gen::<f32>()) * 100.0);
    }
    let data_with_key = MultiDimDataWithKey::multi_cosine(
        data_size,
        &vec![60; base_dimension],
        &amplitude,
        noise,
        0,
        base_dimension.into(),
    );

    let mut score: f64 = 0.0;
    let _next_index = 0;

    for i in 0..data_with_key.data.len() {
        let new_score = forest.score(&data_with_key.data[i]).unwrap();
        //println!("{} {} score {}",y,i,new_score);
        /*
        if next_index < data_with_key.change_indices.len() && data_with_key.change_indices[next_index] == i {
            println!(" score at change {} position {} ", new_score, i);
            next_index += 1;
        }
        */

        score += new_score;
        forest.update(&data_with_key.data[i], 0).unwrap();
    }

    println!(
        "Average score {} ",
        (score / data_with_key.data.len() as f64)
    );
    assert!(
        score < data_with_key.data.len() as f64,
        " average score is above 1"
    );
    println!("Success! {}", forest.entries_seen());
    println!("PointStore Size {} ", forest.point_store_size());
    println!("Total size {} bytes (approx)", forest.size());
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use num::abs;
use rcflib::{
    common::multidimdatawithkey::MultiDimDataWithKey,
    rcf::{create_rcf, RCF},
    visitor::visitor::VisitorInfo,
};

/// try cargo test --release
/// these tests are designed to be longish

#[test]
fn two_distribution_test_static() {
    let data_size = 1000;
    let dimensions = 20;
    let yard_stick = 5.0;
    let mut vec1 = vec![0.0f32; dimensions];
    let mut vec2 = vec![0.0f32; dimensions];
    vec1[0] = yard_stick;
    vec2[0] = -yard_stick;
    let scale = vec![vec![0.1f32; dimensions], vec![0.1f32; dimensions]];
    let mean = vec![vec1.clone(), vec2.clone()].clone();
    let data_with_key =
        MultiDimDataWithKey::mixture(data_size, &mean, &scale, &vec![0.5f32, 0.5f32], 0);

    let shingle_size = 1;
    let number_of_trees = 50;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = true;
    let store_attributes: bool = false;
    let internal_shingling: bool = false;
    let internal_rotation = false;

    let mut forest: Box<dyn RCF> = create_rcf(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed,
        store_attributes,
        parallel_enabled,
        internal_shingling,
        internal_rotation,
        time_decay,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );

    let mut another_forest: Box<dyn RCF> = create_rcf(
        dimensions,
        shingle_size,
        capacity * 2,
        number_of_trees,
        random_seed,
        store_attributes,
        parallel_enabled,
        internal_shingling,
        internal_rotation,
        time_decay,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );

    for i in 0..data_with_key.data.len() {
        forest.update(&data_with_key.data[i], 0).unwrap();
        another_forest.update(&data_with_key.data[i], 0).unwrap();
    }

    let anomaly = vec![0.0f32; dimensions];

    assert!(forest.score(&anomaly).unwrap() > 1.5);
    assert!(forest.displacement_score(&anomaly).unwrap() * f64::log2(capacity as f64) > 1.5);
    let interpolant = forest
        .interpolation_visitor_traversal(&anomaly, &VisitorInfo::default())
        .unwrap();
    let attribution = forest.attribution(&anomaly).unwrap();
    assert!(attribution.high[0] > 0.75);
    assert!(attribution.low[0] > 0.75);
    for i in 1..dimensions {
        assert!(attribution.high[i] < 0.1);
        assert!(attribution.low[i] < 0.1);
        assert!(abs(attribution.low[i] - interpolant.measure.low[i]) < 1e-6);
        assert!(abs(attribution.high[i] - interpolant.measure.high[i]) < 1e-6);
    }
    assert!(abs(attribution.low[0] - interpolant.measure.low[0]) < 1e-6);
    assert!(abs(attribution.high[0] - interpolant.measure.high[0]) < 1e-6);

    // a three signma radius
    assert!(
        abs(interpolant.distance.high[0] - yard_stick as f64 * interpolant.probability_mass.high[0])
            < 0.3
    );
    assert!(
        abs(interpolant.distance.low[0] - yard_stick as f64 * interpolant.probability_mass.low[0])
            < 0.3
    );
    assert!(interpolant.distance.high[1] < 0.1);
    assert!(interpolant.distance.low[1] < 0.1);
    assert!(interpolant.probability_mass.high[1] < 0.1);
    assert!(interpolant.probability_mass.low[1] < 0.1);
    assert!(interpolant.probability_mass.high[0] > 0.4);
    assert!(interpolant.probability_mass.low[0] > 0.4);
    let score = forest.score(&anomaly).unwrap();

    assert!(abs(score - attribution.total()) < 1e-6);
    // score is calibrated for clear cut anomalies, even if sample size doubles ...
    assert!(abs(score - another_forest.score(&anomaly).unwrap()) < 0.1 * score);

    // scores of non-anomalies are not calibrated to be he same but
    // are below 1 and should be close

    assert!(abs(forest.score(&vec1).unwrap() - another_forest.score(&vec1).unwrap()) < 0.1);
    assert!(abs(forest.score(&vec2).unwrap() - another_forest.score(&vec2).unwrap()) < 0.1);
    assert!(forest.score(&vec1).unwrap() < 0.8);
    assert!(forest.score(&vec2).unwrap() < 0.8);

    let displacement_score = forest.displacement_score(&anomaly).unwrap();
    // displacement is calibrated for clear cut anomalies
    // samplesize did not matter for such
    assert!(
        abs(displacement_score - another_forest.displacement_score(&anomaly).unwrap())
            < 0.1 * displacement_score
    );

    // displacement is NOT the same for dense regions; larger samplesize
    // leads to lower score; in fact the gap is close to the ratio of samplesize
    // due to normalization
    assert!(
        forest.displacement_score(&vec1).unwrap()
            > 1.5 * another_forest.displacement_score(&vec1).unwrap()
    );
    assert!(
        forest.displacement_score(&vec2).unwrap()
            > 1.5 * another_forest.displacement_score(&vec2).unwrap()
    );

    // multiplied by log_2 ; the displacement score is in the same numeric
    // range [0..log_2(sample size) as the regular score
    assert!(displacement_score * f64::log2(capacity as f64) > 2.0);

    // in contrast to displacement, density is calibrated at the dense points
    assert!(
        abs(forest.density(&vec1).unwrap() - another_forest.density(&vec1).unwrap())
            < 0.1 * forest.density(&vec1).unwrap()
    );
    assert!(
        abs(forest.density(&vec2).unwrap() - another_forest.density(&vec2).unwrap())
            < 0.1 * forest.density(&vec2).unwrap()
    );
    // and much more than at anomalous points
    assert!(forest.density(&vec1).unwrap() > capacity as f64 * forest.density(&anomaly).unwrap());
    assert!(forest.density(&vec2).unwrap() > capacity as f64 * forest.density(&anomaly).unwrap());

    // but now, unlike displacement, the  calibration is awry at potential anomalies
    // and moreover is in the other direction; larger samplesize gives larger densities because
    // of spurious points coming closer. This is a core intuition of observations/observability; the
    // calibration of central tendency (often used in forecast, also densities of dense regions)
    // has different requirements compared to callibration at extremeties (anomalies, sparse regions)
    assert!(another_forest.density(&anomaly).unwrap() > 1.5 * forest.density(&anomaly).unwrap());
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rcflib::{
    common::multidimdatawithkey,
    trcf::basictrcf::BasicTRCF
};

#[test]
fn test_basic_trcf() {
    let shingle_size = 8;
    let base_dimension = 5;
    let data_size = 1000;
    let number_of_trees = 30;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let dimensions = shingle_size * base_dimension;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = false;
    let noise = 5.0;

    let mut trcf = BasicTRCF::new(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed, parallel_enabled,
        time_decay,
        0.01,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );

    let mut rng = ChaCha20Rng::seed_from_u64(42);
    let mut amplitude = Vec::new();
    for _i in 0..base_dimension {
        amplitude.push((1.0 + 0.2 * rng.gen::<f32>()) * 60.0);
    }

    let data_with_key = multidimdatawithkey::MultiDimDataWithKey::multi_cosine(
        data_size,
        &vec![60; base_dimension],
        &amplitude,
        noise,
        42,
        base_dimension.into(),
    );

    let mut next_index = 0;

    for i in 0..data_with_key.data.len() {
        if next_index < data_with_key.change_indices.len() && data_with_key.change_indices[next_index] == i {
            print!("timestamp {} INJECT [ {}", i, data_with_key.changes[next_index][0]);
            for j in 1..base_dimension {
                print!(", {}", data_with_key.changes[next_index][j]);
            }
            println!("]");
            next_index += 1;
        }
        let result = trcf.process(&data_with_key.data[i], 0).unwrap();
        if result.anomaly_grade > 0.0 {
            print!("timestamp {} ", i);
            if result.relative_index.unwrap() != 0 {
                let gap = -result.relative_index.unwrap();
                if gap == 1 {
                    print!("1 step ago, ");
                } else {
                    print!("{} steps ago, ", gap);
                }
            }
            if result.forecast_reasonable {
                if let Some(expected_list) = result.expected_values_list {
                    let expected = &expected_list[0];
                    let past = &result.past_values.unwrap();
                    print!("DETECT [ {}", (past[0] - expected[0]));
                    for j in 1..base_dimension {
                        print!(", {}", (past[j] - expected[j]));
                    }
                    print!("]");
                }
            }
            println!(" score {}, grade {}", result.score, result.anomaly_grade);
        }
    }
}

#[test]
fn test_basic_trcf_scale() {
    let shingle_size = 8;
    let base_dimension = 5;
    let data_size = 100000;
    let number_of_trees = 30;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let dimensions = shingle_size * base_dimension;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = false;
    let noise = 5.0;

    let mut trcf = BasicTRCF::new(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed, parallel_enabled,
        time_decay,
        0.01,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );

    let mut rng = ChaCha20Rng::from_entropy();
    let mut amplitude = Vec::new();
    for _i in 0..base_dimension {
        amplitude.push((1.0 + 0.2 * rng.gen::<f32>()) * 60.0);
    }
    let data_with_key = multidimdatawithkey::MultiDimDataWithKey::multi_cosine(
        data_size,
        &vec![60; base_dimension],
        &amplitude,
        noise,
        rng.gen::<u64>(),
        base_dimension.into(),
    );

    let mut potential_anomalies:Vec<usize> = Vec::new();

    for i in 0..data_with_key.data.len() {
        let result = trcf.process(&data_with_key.data[i], 0).unwrap();
        if result.anomaly_grade > 0.0 {
            // some anomalies will be detected late
            // we will keep the vector unsorted, so out of order detection will be penalized
            potential_anomalies.push((i as i32 + result.relative_index.unwrap()) as usize);
        }
    }

    println!("{} anomalies injected in {} points", data_with_key.changes.len(), data_size);
    let mut common =0;
    let mut i:usize =0;
    let mut j:usize =0;
    while i<potential_anomalies.len() && j<data_with_key.change_indices.len() {
        if potential_anomalies[i] == data_with_key.change_indices[j] {
            i += 1;
            j += 1;
            common += 1;
        } else if potential_anomalies[i] < data_with_key.change_indices[j] {
            i += 1;
        } else {
            j += 1;
        }
    }

    println!("{} detected, precision {}, recall {}",potential_anomalies.len(),
             common as f32/potential_anomalies.len() as f32,
             common as f32/data_with_key.change_indices.len() as f32);
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use num::abs;
/// try cargo test --release
/// these tests are designed to be longish
use rand::{prelude::ThreadRng, Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rand_core::RngCore;
use rcflib::{
    common::{multidimdatawithkey::MultiDimDataWithKey, samplesummary::summarize},
    l1distance, l2distance, linfinitydistance,
};
use rcflib::common::cluster::{Center, multi_cluster_as_object_with_weight_array, multi_cluster_as_ref, multi_cluster_as_weighted_ref, multi_cluster_obj, persist, single_centroid_cluster_slice_with_weight_arrays, single_centroid_cluster_weighted_vec, single_centroid_cluster_weighted_vec_with_distance_over_slices, single_centroid_unweighted_cluster_slice};
use rcflib::errors::RCFError;

fn gen_data(data_size:usize, test_dimension:usize, seed:u64,yard_stick : f32) -> MultiDimDataWithKey {
    let mut mean = Vec::new();
    let mut scale = Vec::new();
    for i in 0..test_dimension {
        let mut vec1 = vec![0.0f32; test_dimension];
        let mut vec2 = vec![0.0f32; test_dimension];
        vec1[i] = 2.0 * yard_stick;
        vec2[i] = -2.0 * yard_stick;
        mean.push(vec1);
        mean.push(vec2);
        scale.push(vec![0.1f32; test_dimension]);
        scale.push(vec![0.1f32; test_dimension]);
    }
    MultiDimDataWithKey::mixture(
        data_size,
        &mean,
        &scale,
        &vec![0.5 / test_dimension as f32; 2 * test_dimension],
        seed,
    )
}

fn test_center(result: &mut Vec<Center>,test_dimension:usize, yard_stick : f32) -> bool {
    let mut answer = true;
    for i in 0..test_dimension {
        result.sort_by(|a, b| a.representative()[i].partial_cmp(&b.representative()[i]).unwrap());
        answer = answer && abs(result[0].representative()[i] + 2.0 * yard_stick) < 0.2;
        answer = answer
            && abs(result[2 * test_dimension - 1].representative()[i] - 2.0 * yard_stick) < 0.2;
        for j in 1..(2 * test_dimension - 1) {
            answer = answer && abs(result[j].representative()[i]) < 0.2;
        }
    }
    answer
}

fn bad_distance<T :?Sized>(a : &T, b:&T) -> f64{
    -1.0
}

#[test]
fn test_config() {
    let test_dimension = 3;
    let yard_stick = l1distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(1000,test_dimension,0u64,yard_stick);
    let mut input = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push((data_with_key.data[i].clone(), 1.0f32));
    }
    let mut result = single_centroid_cluster_weighted_vec_with_distance_over_slices(&input, bad_distance, 2 * test_dimension + 3, false);

    match &result {
        Ok(x) => assert!(false),
        Err(y) => assert!(true),
    };

    let mut result = single_centroid_cluster_weighted_vec_with_distance_over_slices(&input, l2distance, 0, false);

    match &result {
        Ok(x) => assert!(false),
        Err(y) => assert!(true),
    };

    let mut result = single_centroid_cluster_weighted_vec_with_distance_over_slices(&input, l2distance, 200, false);

    match &result {
        Ok(x) => assert!(false),
        Err(y) => assert!(true),
    };

    let mut result = single_centroid_cluster_weighted_vec_with_distance_over_slices(&input, l2distance, 20, false);

    match &result {
        Ok(x) => assert!(true),
        Err(y) => assert!(false),
    };
}

fn core(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);
    let mut input = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push((data_with_key.data[i].clone(), 1.0f32));
    }
    let mut result = single_centroid_cluster_weighted_vec_with_distance_over_slices(&input, distance, 2 * test_dimension + 3, false).unwrap();
    let answer = (result.len() == 2 * test_dimension) && test_center(&mut result,test_dimension,yard_stick);
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_cluster() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (core(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}

fn core_as_slice_uniform(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);

    let mut input:Vec<&[f32]> = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push(&data_with_key.data[i]);
    }
    let mut result = single_centroid_unweighted_cluster_slice(&input, distance, 2 * test_dimension + 3, false).unwrap();
    let answer = (result.len() == 2 * test_dimension) && test_center(&mut result,test_dimension,yard_stick);
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_slice_uniform() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (core_as_slice_uniform(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}

fn core_as_slice_weighted(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);
    let mut input:Vec<&[f32]> = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push(&data_with_key.data[i]);
    }
    let weights = vec![1.0f32;data_with_key.data.len()];
    let mut result = single_centroid_cluster_slice_with_weight_arrays(&input, &weights, distance, 2 * test_dimension + 3, false).unwrap();
    let answer = (result.len() == 2 * test_dimension) && test_center(&mut result,test_dimension,yard_stick);
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_slice_weighted() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (core_as_slice_weighted(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}

fn vec_dist(a: &Vec<f32>, b: &Vec<f32>) -> f64 {
    l1distance(&a,&b)
}

fn core_vec(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&Vec<f32>, &Vec<f32>) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);
    let mut input = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push((data_with_key.data[i].clone(),1.0f32));
    }

    let mut result = single_centroid_cluster_weighted_vec(&input, distance, 2 * test_dimension + 3, false).unwrap();
    let answer = (result.len() == 2 * test_dimension) && test_center(&mut result,test_dimension,yard_stick);
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_vec() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (core_vec(200000, d, seed, vec_dist) == false) as i32;
    }
    assert!(error < 5);
}

fn multi_as_vec(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);
    let mut input:Vec<Vec<f32>> = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push(data_with_key.data[i].clone());
    }
    let mut result = multi_cluster_obj(&input,   vec_dist, 5,0.1,true,2 * test_dimension + 3, false).unwrap();
    let mut answer = (result.len() == 2 * test_dimension);
    for i in 0..test_dimension {
        result.sort_by(|a, b| a.representatives()[0].0[i].partial_cmp(&b.representatives()[0].0[i]).unwrap());
        answer = answer && abs(result[0].representatives()[0].0[i] + 2.0 * yard_stick) < 0.5;
        answer = answer
            && abs(result[2 * test_dimension - 1].representatives()[0].0[i] - 2.0 * yard_stick) < 0.5;
        for j in 1..(2 * test_dimension - 1) {
            answer = answer && abs(result[j].representatives()[0].0[i]) < 0.5;
        }
    }
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_multi_vec() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (multi_as_vec(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}

fn multi_as_ref(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);
    let mut input = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push(&data_with_key.data[i]);
    }
    let mut result = multi_cluster_as_ref(&input,   vec_dist, 5,0.1,true,2 * test_dimension + 3, false).unwrap();
    let mut answer = (result.len() == 2 * test_dimension);
    for i in 0..test_dimension {
        result.sort_by(|a, b| a.representatives()[0].0[i].partial_cmp(&b.representatives()[0].0[i]).unwrap());
        answer = answer && abs(result[0].representatives()[0].0[i] + 2.0 * yard_stick) < 0.5;
        answer = answer
            && abs(result[2 * test_dimension - 1].representatives()[0].0[i] - 2.0 * yard_stick) < 0.5;
        for j in 1..(2 * test_dimension - 1) {
            answer = answer && abs(result[j].representatives()[0].0[i]) < 0.5;
        }
    }
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_multi_ref() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (multi_as_ref(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}

fn multi_as_weighted_ref(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);
    let mut input = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push((&data_with_key.data[i],1.0f32));
    }
    let mut result = multi_cluster_as_weighted_ref(&input,   vec_dist, 5,0.1,true,2 * test_dimension + 3, false).unwrap();
    let mut answer = (result.len() == 2 * test_dimension);
    for i in 0..test_dimension {
        result.sort_by(|a, b| a.representatives()[0].0[i].partial_cmp(&b.representatives()[0].0[i]).unwrap());
        answer = answer && abs(result[0].representatives()[0].0[i] + 2.0 * yard_stick) < 0.5;
        answer = answer
            && abs(result[2 * test_dimension - 1].representatives()[0].0[i] - 2.0 * yard_stick) < 0.5;
        for j in 1..(2 * test_dimension - 1) {
            answer = answer && abs(result[j].representatives()[0].0[i]) < 0.5;
        }
    }
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_multi_weighted_ref() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (multi_as_weighted_ref(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}

fn multi_as_vec_weighted(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    println!(" starting {}",test_dimension);
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    let data_with_key = gen_data(data_size,test_dimension,seed,yard_stick);
    let mut input:Vec<Vec<f32>> = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push(data_with_key.data[i].clone());
    }
    let weights = vec![1.0f32;data_with_key.data.len()];
    let mut ref_result = multi_cluster_as_object_with_weight_array(&input,  &weights, vec_dist, 5,0.1,true,2 * test_dimension + 3, false).unwrap();
    let mut result = persist(&ref_result);
    let mut answer = (result.len() == 2 * test_dimension);
    for i in 0..test_dimension {
        result.sort_by(|a, b| a.representatives()[0].0[i].partial_cmp(&b.representatives()[0].0[i]).unwrap());
        answer = answer && abs(result[0].representatives()[0].0[i] + 2.0 * yard_stick) < 0.5;
        answer = answer
            && abs(result[2 * test_dimension - 1].representatives()[0].0[i] - 2.0 * yard_stick) < 0.5;
        for j in 1..(2 * test_dimension - 1) {
            answer = answer && abs(result[j].representatives()[0].0[i]) < 0.5;
        }
    }
    println!(" done {} {}",test_dimension,answer);
    answer
}

#[test]
fn benchmark_multi_vec_weighted() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (multi_as_vec_weighted(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use std::f32::consts::PI;

use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rcflib::{
    common::multidimdatawithkey::MultiDimDataWithKey,
    rcf::{create_rcf, RCF},
};

/// try cargo test --release
/// these tests are designed to be longish

#[test]
fn dynamic_density() {
    let dimensions = 2;
    let shingle_size = 1;
    let number_of_trees = 50;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = false;
    let store_attributes: bool = false;
    let internal_shingling: bool = false;
    let internal_rotation = false;

    let mut forest: Box<dyn RCF> = create_rcf(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed,
        store_attributes,
        parallel_enabled,
        internal_shingling,
        internal_rotation,
        time_decay,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );

    let data: Vec<Vec<f32>> = generate_fan(1000, 3);
    let query_point = vec![0.7, 0.0f32];

    for degree in 0..360 {
        for j in 0..data.len() {
            forest.update(
                &rotate_clockwise(&data[j], 2.0 * PI * degree as f32 / 360.0),
                0,
            ).unwrap();
        }

        let density = forest.directional_density(&query_point).unwrap();
        let value = density.total();

        if (degree <= 60)
            || ((degree >= 120) && (degree <= 180))
            || ((degree >= 240) && (degree <= 300))
        {
            assert!(density.total() < 0.8 * capacity as f64); // the fan is above at 90,210,330
        }

        if ((degree >= 75) && (degree <= 105))
            || ((degree >= 195) && (degree <= 225))
            || ((degree >= 315) && (degree <= 345))
        {
            assert!(density.total() > 0.5 * (capacity as f64));
        }

        // Testing for directionality
        // There can be unclear directionality when the
        // blades are right above

        let blade_above_in_y = density.low[1];
        let blade_below_in_y = density.high[1];
        let blade_to_the_left = density.high[0];
        let blade_to_the_right = density.low[0];

        // the tests below have a freedom of 10% of the total value
        if ((degree >= 75) && (degree <= 85))
            || ((degree >= 195) && (degree <= 205))
            || ((degree >= 315) && (degree <= 325))
        {
            assert!(blade_above_in_y + 0.1 * value > blade_below_in_y);
            assert!(blade_above_in_y + 0.1 * value > blade_to_the_right);
        }

        if ((degree >= 95) && (degree <= 105))
            || ((degree >= 215) && (degree <= 225))
            || ((degree >= 335) && (degree <= 345))
        {
            assert!(blade_below_in_y + 0.1 * value > blade_above_in_y);
            assert!(blade_below_in_y + 0.1 * value > blade_to_the_right);
        }

        if ((degree >= 60) && (degree <= 75))
            || ((degree >= 180) && (degree <= 195))
            || ((degree >= 300) && (degree <= 315))
        {
            assert!(blade_above_in_y + 0.1 * value > blade_to_the_left);
            assert!(blade_above_in_y + 0.1 * value > blade_to_the_right);
        }

        if ((degree >= 105) && (degree <= 120))
            || ((degree >= 225) && (degree <= 240))
            || (degree >= 345)
        {
            assert!(blade_below_in_y + 0.1 * value > blade_to_the_left);
            assert!(blade_below_in_y + 0.1 * value > blade_to_the_right);
        }

        // fans are farthest to the left at 30,150 and 270
        if ((degree >= 15) && (degree <= 45))
            || ((degree >= 135) && (degree <= 165))
            || ((degree >= 255) && (degree <= 285))
        {
            assert!(
                blade_to_the_left + 0.1 * value
                    > blade_above_in_y + blade_below_in_y + blade_to_the_right
            );
            assert!(blade_above_in_y + blade_below_in_y + 0.1 * value > blade_to_the_right);
        }
    }
}

fn generate_fan(num_per_blade: usize, blades: usize) -> Vec<Vec<f32>> {
    let mut data = Vec::new();

    let data_with_key = MultiDimDataWithKey::mixture(
        num_per_blade * blades,
        &vec![vec![0f32, 0f32]],
        &vec![vec![0.05, 0.2]],
        &vec![1.0f32],
        0,
    );
    let mut rng = ChaCha20Rng::seed_from_u64(72345);
    for point in data_with_key.data {
        let toss: f64 = rng.gen();
        let mut i = 0;
        while i < blades + 1 {
            if toss < i as f64 / blades as f64 {
                let theta = 2.0 * PI * i as f32 / blades as f32;
                let mut vec = rotate_clockwise(&point, theta);
                vec[0] += 0.6 * theta.sin();
                vec[1] += 0.6 * theta.cos();
                data.push(vec);
                break;
            } else {
                i += 1;
            }
        }
    }
    data
}

fn rotate_clockwise(point: &[f32], theta: f32) -> Vec<f32> {
    let mut result = vec![0.0f32; 2];
    result[0] = theta.cos() * point[0] + theta.sin() * point[1];
    result[1] = -theta.sin() * point[0] + theta.cos() * point[1];
    return result;
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use std::f32::consts::PI;
/// try cargo test --release
/// these tests are designed to be longish
use rand::{prelude::ThreadRng, Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rand_core::RngCore;
use rcflib::{
    common::{multidimdatawithkey::MultiDimDataWithKey, samplesummary::summarize},
    l1distance, l2distance, linfinitydistance,
};
use rcflib::common::cluster::{Center, multi_cluster_as_object_with_weight_array, multi_cluster_as_ref, multi_cluster_as_weighted_ref, multi_cluster_obj, MultiCenter, persist, single_centroid_cluster_slice_with_weight_arrays, single_centroid_cluster_weighted_vec, single_centroid_cluster_weighted_vec_with_distance_over_slices, single_centroid_unweighted_cluster_slice};
use rcflib::common::multidimdatawithkey::new_vec;
use rcflib::errors::RCFError;
use rcflib::glad::GlobalLocalAnomalyDetector;

fn rotate_clockwise(point: &[f32], theta: f32) -> Vec<f32> {
    let mut result = vec![0.0f32; 2];
    result[0] = theta.cos() * point[0] + theta.sin() * point[1];
    result[1] = -theta.sin() * point[0] + theta.cos() * point[1];
    return result;
}

fn gen_numeric_data(data_size:usize, seed:u64, shift : (f32,f32), number_of_fans: usize) -> MultiDimDataWithKey {
    let mut vec_mean = vec![shift.0,shift.1];
    let scale = vec![1.0,0.5/number_of_fans as f32];
    let mut data :Vec<Vec<f32>> = Vec::new();
    let mut labels:Vec<usize> = Vec::new();
    let mut rng = ChaCha20Rng::seed_from_u64(seed);
    for i in 0..data_size {
        let vec = new_vec(&vec_mean, &scale, &mut rng);
        if rng.gen::<f64>() < 0.005 {
            let j :usize = (rng.next_u32() as usize)%number_of_fans;
            data.push(rotate_clockwise(&vec,(2.0*PI * i as f32)/data_size as f32 + PI*(1.0 + 2.0*j as f32)/number_of_fans as f32));
            labels.push( number_of_fans + 2*j) ;
        } else {
            let j :usize = (rng.next_u32() as usize)%number_of_fans;
            data.push(rotate_clockwise(&vec,(2.0*PI * i as f32)/data_size as f32 + PI*(2.0*j as f32)/number_of_fans as f32));
            labels.push(j) ;
        }
    }
    MultiDimDataWithKey{
        data,
        change_indices: Vec::new(),
        labels,
        changes: Vec::new()
    }
}

fn vec_dist(a: &Vec<f32>, b : &Vec<f32>) -> f64 {
    l2distance(a,b)
}

fn bad_distance<T :?Sized>(a : &T, b:&T) -> f64{
    -0.0001
}

#[test]
fn numeric_glad() {
    let data_size = 1000000; // should be sufficiently large for covering a 360 degree rotation, for |capacity| points
    let number_of_fans = 3;
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);
    let capacity = 2000;
    let time_decay = 1.0 /capacity as f64;
    let data_with_key = gen_numeric_data(data_size,one_seed,(5.0 + 1.0/number_of_fans as f32,0.0),number_of_fans);
    let mut glad = GlobalLocalAnomalyDetector::<Vec<f32>>::new(2000,0,time_decay,5, 0.1,true);

    glad.set_z_factor(6.0 + number_of_fans as f32/4.0);

    let mut false_neg = 0;
    let mut false_pos = 0;
    let mut true_pos = 0;
    let mut print_clusters = false;
    // set the above to see the cluster centers printed with associated relative mass
    // each block is separated by two println!()
    // a simple visualization tool can plot an animation of the clusters
    // for example in gnuplot, try something like
    // set terminal gif transparent animate delay 5
    // do for [i = 0:360] { plot [-10:10][-10:10] "typescript" i i u 1:2:3 w p palette pt 7 t "" }
    let mut first = true;
    for j in 0..data_with_key.data.len() {
        let answer = glad.process(&data_with_key.data[j],1.0,vec_dist,vec_dist, false).unwrap();
        if answer.grade != 0.0 {
            if data_with_key.labels[j] < number_of_fans {
                false_pos += 1;
            } else {
                true_pos += 1;
            }
        } else {
            if data_with_key.labels[j] >= number_of_fans {
                false_neg += 1;
            }
        }

        if (j*360/data_size)%2 != 0 {
            if print_clusters && !first {
                println!();
                println!();
            }
            first = true;
        } else {
            if print_clusters && first {
                let a = glad.clusters();
                for i in 0..a.len() {
                    let item =&a[i];
                    for rep in item.representatives() {
                        println!("{} {} {} {}", rep.0[0], rep.0[1], i , rep.1);
                    }
                }
                first = false;
            }
        }

    }
    println!(" precision {} recall {} out of {} injected anomalies", (true_pos as f32)/(true_pos + false_pos) as f32, true_pos as f32/(true_pos + false_neg) as f32, (true_pos + false_neg));

    // negative weight is error
    assert!(glad.process(&data_with_key.data[0],-1.0,vec_dist,vec_dist,false).is_err());
    // negative distance is error
    assert!(glad.process(&data_with_key.data[0],1.0,vec_dist,bad_distance,false).is_err());
}

pub fn toy_d(a:&Vec<char>, b: &Vec<char>) -> f64 {
    if a.len() > b.len() {
        return toy_d(b, a);
    }
    let mut one = vec![0.0;(b.len()+1)];
    let mut two = vec![0.0;(b.len()+1)];

    for j in 0..b.len()+1 {
        one[j] = j as f64;
    }
    for i in 1..a.len()+1 {

        two[0] = i as f64;
        for ((x, y), z) in two[1..].iter_mut().zip(&one[..b.len()]).zip(b) {
            *x = if a[i-1] == *z {*y} else {*y + 1.0};
        }

        for (x, y) in two.iter_mut().zip(&one) {
            *x = if *x < *y + 1.0 {*x} else {*y+1.0};
        }

        for j in 1..b.len()+1 {
            if two[j] > two[j - 1] + 1.0 {
                two[j] = two[j - 1] + 1.0;
            }
        }

        // change one
        for(x,y) in one.iter_mut().zip(&two){
            *x = *y;
        }
    }
    one[b.len()]
}

pub fn get_ab_array(size: usize, probability_of_a: f64, rng: &mut ChaCha20Rng, change_in_middle : bool, fraction : f64) -> Vec<char> {
    let mut answer = Vec::new();
    let new_size = size + (rng.next_u32() as usize)%(size / 5);
    for i in 0..new_size {
        let toss = if change_in_middle && (i as f64 > (1.0 - fraction) * new_size as f64 || (i as f64) < (new_size as f64 ) * fraction) {
            1.0 - probability_of_a
        } else {
            probability_of_a
        };
        if rng.gen::<f64>() < toss {
            answer.push('\u{2014}');
        } else {
            answer.push('\u{005F}');
        }
    }
    answer
}

const ANSI_RESET :&str = "\u{001B}[0m";
const ANSI_RED : &str = "\u{001B}[31m";
const ANSI_BLUE : &str = "\u{001B}[34m";

pub fn print_array(a:&[char]) {
    for i in 0..a.len() {
        if a[i] == '\u{2014}' {
            print!("{}{}{}", ANSI_RED,a[i],ANSI_RESET);
        } else {
            print!("{}{}{}",ANSI_BLUE,a[i], ANSI_RESET);
        }
    }
}

fn print_clusters(clusters: &Vec<MultiCenter<Vec<char>>>) {
    for i in 0..clusters.len()  {
        println!(" Cluster {},  weight {:.3}, average radius {:.3} ",i,clusters[i].weight(),clusters[i].average_radius());
        for item in &clusters[i].representatives() {
            print!("(wt {:.2}, len {})", item.1,item.0.len());
            print_array(&item.0);
            println!();
        }
        println!();
        println!();
    }
}

#[test]
fn string_glad() {
    let data_size = 200000; // should be sufficiently large for covering a 360 degree rotation, for |capacity| points
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);
    let string_size = 70;
    let capacity= 2000;
    let change_in_middle = true;
    // the following should be away from 0.5 in [0.5,1]
    let gap_prob_of_a = 0.85;
    let time_decay = 1.0 /capacity as f64;
    let anomaly_rate = 0.05;
    let mut injected: bool;
    let mut number_of_injected = 0;

    let mut false_neg = 0;
    let mut false_pos = 0;
    let mut true_pos = 0;

    let print_clusters_strings = true;

    let mut glad = GlobalLocalAnomalyDetector::<Vec<char>>::new(2000,0,time_decay,5, 0.1,false);

    // we will not store the points but perform streaming

    for i in 0..data_size {
        if i>0 && i%10000 == 0 {
            println!(" at {} ",i);
            if print_clusters_strings {
                print_clusters(&glad.clusters());
            }
        }
        let mut point = Vec::new();
        if rng.gen::<f64>() < anomaly_rate {
            injected = true;
            number_of_injected += 1;
            point = get_ab_array(string_size + 10, 0.5, &mut rng, false, 0.0);
        } else {
            let flag = change_in_middle && rng.gen::<f64>() < 0.25;
            let prob = if rng.gen::<f64>() < 0.5 {
                gap_prob_of_a
            } else {
                (1.0 - gap_prob_of_a )
            };
            injected = false;
            point = get_ab_array(string_size, prob, &mut rng, flag, 0.25 * i as f64/ data_size as f64);
        }
        let answer = glad.process(&point,1.0,toy_d,toy_d, false).unwrap();
        if answer.grade != 0.0 {
            if !injected {
                false_pos += 1;
            } else {
                true_pos += 1;
            }
        } else {
            if injected && i > capacity/2 {
                false_neg += 1;
            }
        }
    }
    println!("injected {}", number_of_injected);
    println!(" precision {} recall {} out of {} injected anomalies", (true_pos as f32)/(true_pos + false_pos) as f32, true_pos as f32/(true_pos + false_neg) as f32, (true_pos + false_neg));
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rcflib::{
    common::multidimdatawithkey::MultiDimDataWithKey,
    rcf::{create_rcf, RCF},
};

/// try cargo test --release
/// these tests are designed to be longish

#[test]
fn impute_different_period() {
    let shingle_size = 30;
    let base_dimension = 3;
    let data_size = 100000;
    let number_of_trees = 100;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let dimensions = shingle_size * base_dimension;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = true;
    let store_attributes: bool = false;
    let internal_shingling: bool = true;
    let internal_rotation = false;
    let noise = 5.0;

    let mut forest: Box<dyn RCF> = create_rcf(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed,
        store_attributes,
        parallel_enabled,
        internal_shingling,
        internal_rotation,
        time_decay,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );
    let mut rng = ChaCha20Rng::seed_from_u64(42);
    let mut amplitude = Vec::new();
    for _i in 0..base_dimension {
        amplitude.push((1.0 + 0.2 * rng.gen::<f32>()) * 100.0);
    }
    let mut period_rng = ChaCha20Rng::seed_from_u64(7);
    let mut period = Vec::new();
    for _i in 0..base_dimension {
        period.push(((1.0 + 0.2 * period_rng.gen::<f32>()) * 60.0) as usize);
    }
    let data_with_key = MultiDimDataWithKey::multi_cosine(
        data_size,
        &period,
        &amplitude,
        noise,
        0,
        base_dimension.into(),
    );

    let _next_index = 0;
    let mut error = 0.0;
    let mut count = 0;

    for i in 0..data_with_key.data.len() {
        if i > 200 {
            let next_values = forest.extrapolate(1).unwrap().values;
            assert!(next_values.len() == base_dimension);
            error += next_values
                .iter()
                .zip(&data_with_key.data[i])
                .map(|(x, y)| ((x - y) as f64 * (x - y) as f64))
                .sum::<f64>();
            count += base_dimension;
        }
        forest.update(&data_with_key.data[i], 0).unwrap();
    }

    println!("Success! {}", forest.entries_seen());
    println!("PointStore Size {} ", forest.point_store_size());
    println!("Total size {} bytes (approx)", forest.size());
    println!(
        " RMSE {},  noise {} ",
        f64::sqrt(error / count as f64),
        noise
    );
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rcflib::{
    common::multidimdatawithkey::MultiDimDataWithKey,
    rcf::{create_rcf, RCF},
};

/// try cargo test --release
/// these tests are designed to be longish

#[test]
fn impute_same_period() {
    let shingle_size = 8;
    let base_dimension = 5;
    let data_size = 100000;
    let number_of_trees = 30;
    let capacity = 256;
    let initial_accept_fraction = 0.1;
    let dimensions = shingle_size * base_dimension;
    let _point_store_capacity = capacity * number_of_trees + 1;
    let time_decay = 0.1 / capacity as f64;
    let bounding_box_cache_fraction = 1.0;
    let random_seed = 17;
    let parallel_enabled: bool = true;
    let store_attributes: bool = false;
    let internal_shingling: bool = true;
    let internal_rotation = false;
    let noise = 5.0;

    let mut forest: Box<dyn RCF> = create_rcf(
        dimensions,
        shingle_size,
        capacity,
        number_of_trees,
        random_seed,
        store_attributes,
        parallel_enabled,
        internal_shingling,
        internal_rotation,
        time_decay,
        initial_accept_fraction,
        bounding_box_cache_fraction,
    );
    let mut rng = ChaCha20Rng::seed_from_u64(42);
    let mut amplitude = Vec::new();
    for _i in 0..base_dimension {
        amplitude.push((1.0 + 0.2 * rng.gen::<f32>()) * 100.0);
    }
    let data_with_key = MultiDimDataWithKey::multi_cosine(
        data_size,
        &vec![60; base_dimension],
        &amplitude,
        noise,
        0,
        base_dimension.into(),
    );

    let _next_index = 0;
    let mut error = 0.0;
    let mut count = 0;

    for i in 0..data_with_key.data.len() {
        if i > 200 {
            let next_values = forest.extrapolate(1).unwrap().values;
            assert!(next_values.len() == base_dimension);
            error += next_values
                .iter()
                .zip(&data_with_key.data[i])
                .map(|(x, y)| ((x - y) as f64 * (x - y) as f64))
                .sum::<f64>();
            count += base_dimension;
        }
        forest.update(&data_with_key.data[i], 0).unwrap();
    }

    println!("Success! {}", forest.entries_seen());
    println!("PointStore Size {} ", forest.point_store_size());
    println!("Total size {} bytes (approx)", forest.size());
    println!(
        " RMSE {},  noise {} ",
        f64::sqrt(error / count as f64),
        noise
    );
}


extern crate rand;
extern crate rand_chacha;
extern crate rcflib;

use num::abs;
/// try cargo test --release
/// these tests are designed to be longish
use rand::{prelude::ThreadRng, Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rand_core::RngCore;
use rcflib::{
    common::{multidimdatawithkey::MultiDimDataWithKey, samplesummary::summarize},
    l1distance, l2distance, linfinitydistance,
};
use rcflib::common::cluster::multi_cluster_as_ref;
use rcflib::common::samplesummary::multi_summarize_ref;

#[cfg(test)]
parameterized_test::create! { sample_summary_distance_test, (test_dimension,distance), {
assert!(core(1000000,test_dimension,0,distance));
}}

fn core(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    let mut mean = Vec::new();
    let mut scale = Vec::new();
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    for i in 0..test_dimension {
        let mut vec1 = vec![0.0f32; test_dimension];
        let mut vec2 = vec![0.0f32; test_dimension];
        vec1[i] = 2.0 * yard_stick;
        vec2[i] = -2.0 * yard_stick;
        mean.push(vec1);
        mean.push(vec2);
        scale.push(vec![0.1f32; test_dimension]);
        scale.push(vec![0.1f32; test_dimension]);
    }

    let data_with_key = MultiDimDataWithKey::mixture(
        data_size,
        &mean,
        &scale,
        &vec![0.5 / test_dimension as f32; 2 * test_dimension],
        seed,
    );

    let mut input = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push((data_with_key.data[i].clone(), 1.0f32));
    }
    let mut result = summarize(&input, distance, 2 * test_dimension + 3, false).unwrap();
    let mut answer = result.summary_points.len() == 2 * test_dimension;
    // should be two centers per dimension
    // the top two should correspond to +/- 5.0 in first dimension
    for i in 0..test_dimension {
        result
            .summary_points
            .sort_by(|a, b| a[i].partial_cmp(&b[i]).unwrap());
        answer = answer && abs(result.summary_points[0][i] + 2.0 * yard_stick) < 0.5;
        answer = answer
            && abs(result.summary_points[2 * test_dimension - 1][i] - 2.0 * yard_stick) < 0.5;
        for j in 1..(2 * test_dimension - 1) {
            answer = answer && abs(result.summary_points[j][i]) < 0.5;
        }
    }
    answer
}

sample_summary_distance_test! {
    a1 : (1,l1distance),
    b1 : (1,l2distance),
    c1 : (1,linfinitydistance),
    a2 : (2,l1distance),
    b2 : (3,l2distance),
    a3 : (5,l1distance),
    b3 : (5,l2distance),
}

#[test]
fn benchmark() {
    let mut generator = ThreadRng::default();
    let one_seed: u64 = generator.gen();
    println!(" single seed is {}", one_seed);
    let mut rng = ChaCha20Rng::seed_from_u64(one_seed);

    let mut error = 0;
    for _ in 0..10 {
        let seed = rng.next_u64();
        let d = rng.gen_range(3..23);
        error += (core(200000, d, seed, l1distance) == false) as i32;
    }
    assert!(error < 5);
}

#[cfg(test)]
parameterized_test::create! { sample_summary_distance_test_ref, (test_dimension,distance), {
assert!(core_ref(1000000,test_dimension,0,distance));
}}

sample_summary_distance_test_ref! {
    a1 : (1,l1distance),
    b1 : (1,l2distance),
    c1 : (1,linfinitydistance),
    a2 : (2,l1distance),
    b2 : (3,l2distance),
    c2 : (4,linfinitydistance),
    a3 : (15,l1distance),
    b3 : (15,l2distance),
    c3 : (15,linfinitydistance),

}

fn core_ref(
    data_size: usize,
    test_dimension: usize,
    seed: u64,
    distance: fn(&[f32], &[f32]) -> f64,
) -> bool {
    let mut mean = Vec::new();
    let mut scale = Vec::new();
    let yard_stick = distance(&vec![0.0; test_dimension], &vec![1.0; test_dimension]) as f32;
    for i in 0..test_dimension {
        let mut vec1 = vec![0.0f32; test_dimension];
        let mut vec2 = vec![0.0f32; test_dimension];
        vec1[i] = 2.0 * yard_stick;
        vec2[i] = -2.0 * yard_stick;
        mean.push(vec1);
        mean.push(vec2);
        scale.push(vec![0.1f32; test_dimension]);
        scale.push(vec![0.1f32; test_dimension]);
    }

    let data_with_key = MultiDimDataWithKey::mixture(
        data_size,
        &mean,
        &scale,
        &vec![0.5 / test_dimension as f32; 2 * test_dimension],
        seed,
    );

    let mut input:Vec<(&[f32],f32)> = Vec::new();
    for i in 0..data_with_key.data.len() {
        input.push((&data_with_key.data[i], 1.0f32));
    }
    let mut result = multi_summarize_ref(&input, distance, 5,0.1,2 * test_dimension + 3, false).unwrap();
    let mut answer = result.summary_points.len() == 2 * test_dimension;
    // should be two centers per dimension
    // the top two should correspond to +/- 5.0 in first dimension
    for i in 0..test_dimension {
        result
            .summary_points
            .sort_by(|a, b| a[i].partial_cmp(&b[i]).unwrap());
        answer = answer && abs(result.summary_points[0][i] + 2.0 * yard_stick) < 0.5;
        answer = answer
            && abs(result.summary_points[2 * test_dimension - 1][i] - 2.0 * yard_stick) < 0.5;
        for j in 1..(2 * test_dimension - 1) {
            answer = answer && abs(result.summary_points[j][i]) < 0.5;
        }
    }
    answer
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#![cfg_attr(not(any(test, feature = "std")), no_std)]

#[cfg(any(feature = "testing", test))]
#[macro_use]
pub mod testing;

#[macro_use]
pub mod zerocopy;

pub mod decoder;
pub mod encoder;
pub mod unaligned;

pub use decoder::{
    CheckedRange, DecoderBuffer, DecoderBufferMut, DecoderBufferMutResult, DecoderBufferResult,
    DecoderError, DecoderParameterizedValue, DecoderParameterizedValueMut, DecoderValue,
    DecoderValueMut,
};
pub use encoder::{Encoder, EncoderBuffer, EncoderLenEstimator, EncoderValue};
pub use unaligned::*;


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use core::{
    convert::{TryFrom, TryInto},
    ops::Deref,
};

// Unaligned integer types are integers which Rust does not provide natively.
// This macro attempts to create wrapper types around the rounded up type
// supported, e.g. 24 -> 32.
//
// For the scope of the QUIC implementation 24-bit integers are needed
// for u24 encoded packet numbers:
// https://www.rfc-editor.org/rfc/rfc9000.html#name-packet-number-encoding-and-
//
// 48-bit integers are also implemented for completeness.
macro_rules! unaligned_integer_type {
    ($name:ident, $bitsize:expr, $storage_type:ty, [$($additional_conversions:ty),*]) => {
        #[allow(non_camel_case_types)]
        #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
        pub struct $name(pub(crate) $storage_type);

        impl $name {
            /// Truncate the storage value into the allowed range
            pub fn new_truncated(value: $storage_type) -> Self {
                Self(value & ((1 << $bitsize) - 1))
            }
        }

        #[cfg(feature = "generator")]
        impl bolero_generator::TypeGenerator for $name {
            fn generate<D: bolero_generator::Driver>(driver: &mut D) -> Option<Self> {
                Some(Self::new_truncated(driver.gen()?))
            }
        }

        impl TryFrom<$storage_type> for $name {
            type Error = TryFromIntError;

            fn try_from(value: $storage_type) -> Result<Self, Self::Error> {
                if value < (1 << $bitsize) {
                    Ok(Self(value))
                } else {
                    Err(TryFromIntError(()))
                }
            }
        }

        impl From<$name> for $storage_type {
            fn from(value: $name) -> $storage_type {
                value.0
            }
        }

        $(
            impl From<$additional_conversions> for $name {
                fn from(value: $additional_conversions) -> Self {
                    $name(value.into())
                }
            }

            impl From<$name> for $additional_conversions {
                fn from(value: $name) -> Self {
                    value.0 as $additional_conversions
                }
            }
        )*

        impl Deref for $name {
            type Target = $storage_type;

            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
    };
}

unaligned_integer_type!(u24, 24, u32, [u8, u16]);
unaligned_integer_type!(i24, 24, i32, [u8, i8, u16, i16]);

impl TryFrom<u64> for u24 {
    type Error = TryFromIntError;

    fn try_from(value: u64) -> Result<Self, Self::Error> {
        let storage_value: u32 = value.try_into()?;
        storage_value.try_into()
    }
}

impl From<u24> for u64 {
    fn from(value: u24) -> u64 {
        value.0.into()
    }
}

unaligned_integer_type!(u48, 24, u64, [u8, u16, u32]);
unaligned_integer_type!(i48, 24, i64, [u8, i8, u16, i16, u32, i32]);

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct TryFromIntError(());

impl From<core::num::TryFromIntError> for TryFromIntError {
    fn from(_: core::num::TryFromIntError) -> Self {
        Self(())
    }
}

impl core::fmt::Display for TryFromIntError {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "TryFromIntError")
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use ::byteorder::NetworkEndian;
use core::{
    cmp::Ordering,
    fmt,
    hash::{Hash, Hasher},
};
pub use zerocopy::*;

#[cfg(feature = "generator")]
use bolero_generator::*;

/// Define a codec implementation for a zerocopy value that implements
/// `FromBytes`, `AsBytes`, and `Unaligned`.
#[macro_export]
macro_rules! zerocopy_value_codec {
    ($name:ident) => {
        impl<'a> $crate::DecoderValue<'a> for $name
        where
            $name: $crate::zerocopy::FromBytes,
        {
            #[inline]
            fn decode(buffer: $crate::DecoderBuffer<'a>) -> $crate::DecoderBufferResult<Self> {
                let (value, buffer) = <&'a $name as $crate::DecoderValue>::decode(buffer)?;
                Ok((*value, buffer))
            }
        }

        impl<'a> $crate::DecoderValue<'a> for &'a $name
        where
            $name: $crate::zerocopy::FromBytes,
        {
            #[inline]
            fn decode(buffer: $crate::DecoderBuffer<'a>) -> $crate::DecoderBufferResult<Self> {
                let (value, buffer) = buffer.decode_slice(core::mem::size_of::<$name>())?;
                let value = value.into_less_safe_slice();
                let value = unsafe {
                    // Safety: the type implements FromBytes
                    &*(value as *const _ as *const $name)
                };
                Ok((value, buffer.into()))
            }
        }

        impl<'a> $crate::DecoderValueMut<'a> for $name
        where
            $name: $crate::zerocopy::FromBytes,
        {
            #[inline]
            fn decode_mut(
                buffer: $crate::DecoderBufferMut<'a>,
            ) -> $crate::DecoderBufferMutResult<Self> {
                let (value, buffer) = <&'a $name as $crate::DecoderValueMut>::decode_mut(buffer)?;
                Ok((*value, buffer))
            }
        }

        impl<'a> $crate::DecoderValueMut<'a> for &'a $name
        where
            $name: $crate::zerocopy::FromBytes,
        {
            #[inline]
            fn decode_mut(
                buffer: $crate::DecoderBufferMut<'a>,
            ) -> $crate::DecoderBufferMutResult<'a, Self> {
                let (value, buffer) =
                    <&'a mut $name as $crate::DecoderValueMut>::decode_mut(buffer)?;
                Ok((value, buffer))
            }
        }

        impl<'a> $crate::DecoderValueMut<'a> for &'a mut $name
        where
            $name: $crate::zerocopy::FromBytes,
        {
            #[inline]
            fn decode_mut(
                buffer: $crate::DecoderBufferMut<'a>,
            ) -> $crate::DecoderBufferMutResult<'a, Self> {
                let (value, buffer) = buffer.decode_slice(core::mem::size_of::<$name>())?;
                let value = value.into_less_safe_slice();
                let value = unsafe {
                    // Safety: the type implements FromBytes
                    &mut *(value as *mut _ as *mut $name)
                };

                Ok((value, buffer.into()))
            }
        }

        impl $crate::EncoderValue for $name
        where
            $name: $crate::zerocopy::AsBytes,
        {
            #[inline]
            fn encoding_size(&self) -> usize {
                core::mem::size_of::<$name>()
            }

            #[inline]
            fn encoding_size_for_encoder<E: $crate::Encoder>(&self, _encoder: &E) -> usize {
                core::mem::size_of::<$name>()
            }

            #[inline]
            fn encode<E: $crate::Encoder>(&self, encoder: &mut E) {
                let bytes = unsafe {
                    // Safety: the type implements AsBytes
                    core::slice::from_raw_parts(
                        self as *const $name as *const u8,
                        core::mem::size_of::<$name>(),
                    )
                };
                encoder.write_slice(bytes);
            }
        }

        impl<'a> $crate::EncoderValue for &'a $name
        where
            $name: $crate::zerocopy::AsBytes,
        {
            #[inline]
            fn encoding_size(&self) -> usize {
                core::mem::size_of::<$name>()
            }

            #[inline]
            fn encoding_size_for_encoder<E: $crate::Encoder>(&self, _encoder: &E) -> usize {
                ::core::mem::size_of::<$name>()
            }

            #[inline]
            fn encode<E: $crate::Encoder>(&self, encoder: &mut E) {
                let bytes = unsafe {
                    // Safety: the type implements AsBytes
                    core::slice::from_raw_parts(
                        *self as *const $name as *const u8,
                        core::mem::size_of::<$name>(),
                    )
                };
                encoder.write_slice(bytes);
            }
        }

        impl<'a> $crate::EncoderValue for &'a mut $name
        where
            $name: $crate::zerocopy::AsBytes,
        {
            #[inline]
            fn encoding_size(&self) -> usize {
                core::mem::size_of::<$name>()
            }

            #[inline]
            fn encoding_size_for_encoder<E: $crate::Encoder>(&self, _encoder: &E) -> usize {
                ::core::mem::size_of::<$name>()
            }

            #[inline]
            fn encode<E: $crate::Encoder>(&self, encoder: &mut E) {
                let bytes = unsafe {
                    // Safety: the type implements AsBytes
                    core::slice::from_raw_parts(
                        *self as *const $name as *const u8,
                        core::mem::size_of::<$name>(),
                    )
                };
                encoder.write_slice(bytes);
            }
        }
    };
}

// The `zerocopy` crate provides integer types that are able to be referenced
// in an endian-independent method. This macro wraps those types and implements
// a few convenience traits.
macro_rules! zerocopy_network_integer {
    ($native:ident, $name:ident) => {
        #[derive(
            Clone,
            Copy,
            Default,
            Eq,
            $crate::zerocopy::FromBytes,
            $crate::zerocopy::AsBytes,
            $crate::zerocopy::Unaligned,
        )]
        #[repr(C)]
        pub struct $name(::zerocopy::byteorder::$name<NetworkEndian>);

        impl $name {
            pub const ZERO: Self = Self(::zerocopy::byteorder::$name::ZERO);

            #[inline(always)]
            pub fn new(value: $native) -> Self {
                value.into()
            }

            #[inline(always)]
            pub fn get(&self) -> $native {
                self.get_be().to_be()
            }

            #[inline(always)]
            pub fn get_be(&self) -> $native {
                unsafe {
                    $native::from_ne_bytes(
                        *(self.0.as_bytes().as_ptr()
                            as *const [u8; ::core::mem::size_of::<$native>()]),
                    )
                }
            }

            #[inline(always)]
            pub fn set(&mut self, value: $native) {
                self.0.as_bytes_mut().copy_from_slice(&value.to_be_bytes());
            }

            #[inline(always)]
            pub fn set_be(&mut self, value: $native) {
                self.0.as_bytes_mut().copy_from_slice(&value.to_ne_bytes());
            }
        }

        impl PartialEq for $name {
            #[inline]
            fn eq(&self, other: &Self) -> bool {
                self.cmp(other) == Ordering::Equal
            }
        }

        impl PartialEq<$native> for $name {
            #[inline]
            fn eq(&self, other: &$native) -> bool {
                self.partial_cmp(other) == Some(Ordering::Equal)
            }
        }

        impl PartialOrd for $name {
            #[inline]
            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
                Some(self.cmp(other))
            }
        }

        impl PartialOrd<$native> for $name {
            #[inline]
            fn partial_cmp(&self, other: &$native) -> Option<Ordering> {
                Some(self.get().cmp(other))
            }
        }

        impl Ord for $name {
            #[inline]
            fn cmp(&self, other: &Self) -> Ordering {
                self.get_be().cmp(&other.get_be())
            }
        }

        impl Hash for $name {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.get().hash(state);
            }
        }

        impl fmt::Debug for $name {
            fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "{}", self.get())
            }
        }

        impl fmt::Display for $name {
            fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "{}", self.get())
            }
        }

        impl From<$native> for $name {
            #[inline]
            fn from(value: $native) -> Self {
                Self(::zerocopy::byteorder::$name::new(value))
            }
        }

        impl From<$name> for $native {
            #[inline]
            fn from(v: $name) -> $native {
                v.get()
            }
        }

        #[cfg(feature = "generator")]
        impl TypeGenerator for $name {
            fn generate<D: Driver>(driver: &mut D) -> Option<Self> {
                Some(Self::new(driver.gen()?))
            }
        }

        zerocopy_value_codec!($name);
    };
}

zerocopy_network_integer!(i16, I16);
zerocopy_network_integer!(u16, U16);
zerocopy_network_integer!(i32, I32);
zerocopy_network_integer!(u32, U32);
zerocopy_network_integer!(i64, I64);
zerocopy_network_integer!(u64, U64);
zerocopy_network_integer!(i128, I128);
zerocopy_network_integer!(u128, U128);

#[test]
fn zerocopy_struct_test() {
    use crate::DecoderBuffer;

    #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, FromBytes, AsBytes, Unaligned)]
    #[repr(C)]
    struct UdpHeader {
        source_port: U16,
        destination_port: U16,
        payload_len: U16,
        checksum: U16,
    }

    zerocopy_value_codec!(UdpHeader);

    let buffer = vec![0, 1, 0, 2, 0, 3, 0, 4];
    let decoder = DecoderBuffer::new(&buffer);
    let (mut header, _) = decoder.decode().unwrap();

    ensure_codec_round_trip_value!(UdpHeader, header).unwrap();
    ensure_codec_round_trip_value!(&UdpHeader, &header).unwrap();
    ensure_codec_round_trip_value_mut!(&mut UdpHeader, &mut header).unwrap();

    assert_eq!(header.source_port, 1u16);
    assert_eq!(header.destination_port, 2u16);
    assert_eq!(header.payload_len, 3u16);
    assert_eq!(header.checksum, 4u16);
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::decoder::{
    value::{DecoderParameterizedValue, DecoderValue},
    DecoderError,
};

pub type DecoderBufferResult<'a, T> = Result<(T, DecoderBuffer<'a>), DecoderError>;

/// DecoderBuffer is a panic-free byte buffer for look-ahead decoding untrusted input
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct DecoderBuffer<'a> {
    bytes: &'a [u8],
}

impl<'a> DecoderBuffer<'a> {
    /// Create a new `DecoderBuffer` from a byte slice
    #[inline]
    pub const fn new(bytes: &'a [u8]) -> Self {
        Self { bytes }
    }

    /// Move out the buffer's slice. This should be used with caution, as it
    /// removes any panic protection this struct provides.
    #[inline]
    pub fn into_less_safe_slice(self) -> &'a [u8] {
        self.bytes
    }
}

impl_buffer!(
    DecoderBuffer,
    DecoderBufferResult,
    DecoderValue,
    decode,
    DecoderParameterizedValue,
    decode_parameterized,
    split_at
);

impl<'a> From<&'a [u8]> for DecoderBuffer<'a> {
    #[inline]
    fn from(bytes: &'a [u8]) -> Self {
        Self::new(bytes)
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::decoder::{
    buffer::DecoderBuffer,
    value::{DecoderParameterizedValueMut, DecoderValueMut},
    DecoderError,
};

pub type DecoderBufferMutResult<'a, T> = Result<(T, DecoderBufferMut<'a>), DecoderError>;

/// DecoderBufferMut is a panic-free, mutable byte buffer for decoding untrusted input
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct DecoderBufferMut<'a> {
    bytes: &'a mut [u8],
}

impl<'a> DecoderBufferMut<'a> {
    /// Create a new `DecoderBufferMut` from a byte slice
    #[inline]
    pub fn new(bytes: &'a mut [u8]) -> Self {
        Self { bytes }
    }

    /// Freeze the mutable buffer into a `DecoderBuffer`
    #[inline]
    pub fn freeze(self) -> DecoderBuffer<'a> {
        DecoderBuffer::new(self.bytes)
    }

    /// Move out the buffer's slice. This should be used with caution, as it
    /// removes any panic protection this struct provides.
    #[inline]
    pub fn into_less_safe_slice(self) -> &'a mut [u8] {
        self.bytes
    }

    /// Mutably borrow the buffer's slice. This should be used with caution, as it
    /// removes any panic protection this struct provides.
    #[inline]
    pub fn as_less_safe_slice_mut(&'a mut self) -> &'a mut [u8] {
        self.bytes
    }
}

impl_buffer!(
    DecoderBufferMut,
    DecoderBufferMutResult,
    DecoderValueMut,
    decode_mut,
    DecoderParameterizedValueMut,
    decode_parameterized_mut,
    split_at_mut
);

impl<'a> From<DecoderBufferMut<'a>> for DecoderBuffer<'a> {
    fn from(b: DecoderBufferMut<'a>) -> Self {
        b.freeze()
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use core::fmt;

#[derive(Copy, Clone)]
pub struct CheckedRange {
    start: usize,
    end: usize,

    #[cfg(all(debug_assertions, feature = "checked_range_unsafe"))]
    original_ptr: *const u8,
}

impl CheckedRange {
    pub(crate) fn new(start: usize, end: usize, original_ptr: *const u8) -> Self {
        #[cfg(not(all(debug_assertions, feature = "checked_range_unsafe")))]
        let _ = original_ptr;

        Self {
            start,
            end,
            #[cfg(all(debug_assertions, feature = "checked_range_unsafe"))]
            original_ptr,
        }
    }

    #[cfg(feature = "checked_range_unsafe")]
    #[inline]
    pub fn get<'a>(&self, slice: &'a [u8]) -> &'a [u8] {
        unsafe {
            #[cfg(debug_assertions)]
            debug_assert_eq!(slice.as_ptr().add(self.start), self.original_ptr);

            slice.get_unchecked(self.start..self.end)
        }
    }

    #[cfg(not(feature = "checked_range_unsafe"))]
    #[inline]
    pub fn get<'a>(&self, slice: &'a [u8]) -> &'a [u8] {
        &slice[self.start..self.end]
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.end - self.start
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.start == self.end
    }
}

impl fmt::Debug for CheckedRange {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}..{}", self.start, self.end)
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

macro_rules! doc_comment {
    ($($x:expr)*; $($tt:tt)*) => {
        doc_comment!(@doc concat!($($x, "\n",)*), $($tt)*);
    };
    (@doc $x:expr, $($tt:tt)*) => {
        #[doc = $x]
        $($tt)*
    };
}

macro_rules! impl_buffer {
    ($name:ident, $result:ident, $value:ident, $value_call:ident, $parameterized:ident, $parameterized_call:ident, $split:ident) => {
        impl<'a> $name<'a> {
            doc_comment! {
                "Decode a slice of bytes by `count`, removing the slice from the current buffer"

                "```"
                "# use s2n_codec::*;"
                "let mut data = [0, 1, 2, 3, 4];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "let (slice, buffer) = buffer.decode_slice(5).unwrap();"
                "assert_eq!(slice, [0u8, 1, 2, 3, 4][..]);"
                ""
                "assert!(buffer.is_empty());"
                "```";

                #[inline]
                pub fn decode_slice(self, count: usize) -> $result<'a, $name<'a>> {
                    self.ensure_len(count)?;

                    let (slice, remaining) = self.bytes.$split(count);

                    Ok((Self::new(slice), Self::new(remaining)))
                }
            }

            doc_comment! {
                "Decode a value of type `T`, splitting the data from the current buffer"

                "```"
                "# use s2n_codec::*;"
                "let mut data = [0, 1, 2, 3, 4, 5, 6];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "let (value, buffer) = buffer.decode::<u8>().unwrap();"
                "assert_eq!(value, 0);"
                ""
                "let (value, buffer) = buffer.decode::<u16>().unwrap();"
                "assert_eq!(value, 258);"
                ""
                "let (value, buffer) = buffer.decode::<u32>().unwrap();"
                "assert_eq!(value, 50_595_078);"
                ""
                "assert!(buffer.is_empty());"
                "```";

                #[inline]
                pub fn decode<T: $value<'a>>(self) -> $result<'a, T> {
                    T::$value_call(self)
                }
            }

            doc_comment! {
                "Decode a slice prefixed by type `Length`, splitting the data from the"
                "current buffer."

                "With a `Length` as encoded `u8`:"
                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [5, 0, 1, 2, 3, 4];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                "let (slice, buffer) = buffer.decode_slice_with_len_prefix::<u8>().unwrap();"
                "assert_eq!(slice, [0u8, 1, 2, 3, 4][..]);"
                "assert!(buffer.is_empty())"
                "```"

                "With a `Length` as encoded `u16`:"
                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [0, 5, 0, 1, 2, 3, 4];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                "let (slice, buffer) = buffer.decode_slice_with_len_prefix::<u16>().unwrap();"
                "assert_eq!(slice, [0u8, 1, 2, 3, 4][..]);"
                "assert!(buffer.is_empty())"
                "```";

                #[inline]
                pub fn decode_slice_with_len_prefix<Length: $value<'a> + core::convert::TryInto<usize>>(
                    self,
                ) -> $result<'a, Self> {
                    let (len, buffer) = self.decode::<Length>()?;
                    let len = len.try_into().map_err(|_| DecoderError::LengthCapacityExceeded)?;
                    buffer.decode_slice(len)
                }
            }

            doc_comment! {
                "Decode a value of type `T` prefixed by type `Length`, splitting the data from the"
                "current buffer."

                "With a `Length` as encoded `u8` and `T` as `u16`:"
                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [2, 0, 1, 2, 3];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                "let (value, buffer) = buffer.decode_with_len_prefix::<u8, u16>().unwrap();"
                "assert_eq!(value, 1);"
                "assert_eq!(buffer, [2, 3][..])"
                "```"

                concat!("The `", stringify!($value) ,"` implementation of `T` must consume the entire subslice")
                "otherwise an error will be returned."

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [3, 0, 1, 2];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                "let result = buffer.decode_with_len_prefix::<u8, u16>();"
                "assert!(result.is_err())"
                "```";

                #[inline]
                pub fn decode_with_len_prefix<Length: $value<'a> + core::convert::TryInto<usize>, T: $value<'a>>(
                    self,
                ) -> $result<'a, T> {
                    let (slice, buffer) = self.decode_slice_with_len_prefix::<Length>()?;
                    let (value, slice) = slice.decode::<T>()?;
                    slice.ensure_empty()?;
                    Ok((value, buffer))
                }
            }

            doc_comment! {
                "Decode a parameterized value of type `T` implementing `"
                stringify!($parameterized) "`";

                #[inline]
                pub fn decode_parameterized<T: $parameterized<'a>>(
                    self,
                    parameter: T::Parameter,
                ) -> $result<'a, T> {
                    T::$parameterized_call(parameter, self)
                }
            }

            doc_comment! {
                "Skip a `count` of bytes, discarding the bytes"

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [0, 1, 2, 3, 4];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                "let buffer = buffer.skip(3).unwrap();"
                "assert_eq!(buffer, [3, 4][..]);"
                "```";

                #[inline]
                pub fn skip(self, count: usize) -> Result<$name<'a>, DecoderError> {
                    self.decode_slice(count).map(|(_, buffer)| buffer)
                }
            }

            doc_comment! {
                "Skip a number of bytes encoded as a length prefix of type `Length`"

                "With a `Length` encoded as `u8`:"
                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [5, 0, 1, 2, 3, 4];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                "let buffer = buffer.skip_with_len_prefix::<u8>().unwrap();"
                "assert!(buffer.is_empty());"
                "```";

                #[inline]
                pub fn skip_with_len_prefix<Length: $value<'a> + core::convert::TryInto<usize>>(
                    self,
                ) -> Result<$name<'a>, DecoderError> {
                    let (len, buffer) = self.decode::<Length>()?;
                    let len = len.try_into().map_err(|_| DecoderError::LengthCapacityExceeded)?;
                    buffer.skip(len)
                }
            }

            doc_comment! {
                "Skip a `count` of bytes, returning a `CheckedRange` for later access";
                // TODO add doctest

                #[inline]
                pub fn skip_into_range(
                    self,
                    count: usize,
                    original_buffer: &crate::DecoderBufferMut,
                ) -> $result<'a, crate::CheckedRange> {
                    let start = original_buffer.len() - self.len();
                    let (slice, buffer) = self.decode_slice(count)?;
                    let end = start + count;
                    Ok((
                        crate::CheckedRange::new(start, end, slice.bytes.as_ptr()),
                        buffer,
                    ))
                }
            }

            doc_comment! {
                "Skip a number of bytes encoded as a length prefix of type `Length`"
                "into a `CheckedRange` for later access";
                // TODO add doctest

                #[inline]
                pub fn skip_into_range_with_len_prefix<Length: $value<'a> + core::convert::TryInto<usize>>(
                    self,
                    original_buffer: &crate::DecoderBufferMut,
                ) -> $result<'a, crate::CheckedRange> {
                    let (len, buffer) = self.decode::<Length>()?;
                    let len = len.try_into().map_err(|_| DecoderError::LengthCapacityExceeded)?;
                    buffer.skip_into_range(len, original_buffer)
                }
            }

            doc_comment! {
                "Reads data from a `CheckedRange`";
                // TODO add doctest

                #[inline]
                pub fn get_checked_range(&self, range: &crate::CheckedRange) -> DecoderBuffer {
                    range.get(self.bytes).into()
                }
            }

            doc_comment! {
                "Create a peeking `DecoderBuffer` from the current buffer view"

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [0, 1];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "let peek = buffer.peek();"
                "let (value, peek) = peek.decode::<u16>().unwrap();"
                "assert_eq!(value, 1);"
                "assert!(peek.is_empty());"
                ""
                "// `buffer` still contains the previous view"
                "assert_eq!(buffer, [0, 1][..]);"
                "```";

                #[inline]
                #[must_use]
                pub fn peek(&'a self) -> crate::DecoderBuffer<'a> {
                    crate::DecoderBuffer::new(self.bytes)
                }
            }

            doc_comment! {
                "Returns a single byte at `index`"

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [0, 1, 2];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "assert_eq!(buffer.peek_byte(0).unwrap(), 0);"
                "assert_eq!(buffer.peek_byte(1).unwrap(), 1);"
                "assert_eq!(buffer.peek_byte(2).unwrap(), 2);"
                ""
                "// `buffer` has not changed"
                "assert_eq!(buffer, [0, 1, 2][..]);"
                "```";

                #[inline]
                pub fn peek_byte(&self, index: usize) -> Result<u8, DecoderError> {
                    self.bytes
                        .get(index)
                        .cloned()
                        .ok_or_else(|| DecoderError::UnexpectedEof(index))
                }
            }

            /// Returns a `PeekBuffer` by `range`
            #[inline]
            pub fn peek_range(
                &self,
                range: core::ops::Range<usize>,
            ) -> Result<crate::DecoderBuffer, DecoderError> {
                let end = range.end;
                self.bytes
                    .get(range)
                    .map(|bytes| bytes.into())
                    .ok_or_else(|| DecoderError::UnexpectedEof(end))
            }

            doc_comment! {
                "Returns an error if the buffer is not empty."

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [1];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "assert!(buffer.ensure_empty().is_err());"
                "let (_, buffer) = buffer.decode::<u8>().unwrap();"
                "assert!(buffer.ensure_empty().is_ok());"
                "```";

                #[inline]
                pub fn ensure_empty(&self) -> Result<(), DecoderError> {
                    if !self.is_empty() {
                        Err(DecoderError::UnexpectedBytes(self.len()))
                    } else {
                        Ok(())
                    }
                }
            }

            doc_comment! {
                "Returns an error if the buffer does not have at least `len` bytes."

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [0, 1, 2];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "assert!(buffer.ensure_len(2).is_ok());"
                "assert!(buffer.ensure_len(5).is_err());"
                "```";

                #[inline]
                pub fn ensure_len(&self, len: usize) -> Result<(), DecoderError> {
                    if self.len() < len {
                        Err(DecoderError::UnexpectedEof(len))
                    } else {
                        Ok(())
                    }
                }
            }

            doc_comment! {
                "Returns the number of bytes in the buffer."

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [0, 1, 2];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "assert_eq!(buffer.len(), 3);"
                "let (_, buffer) = buffer.decode::<u8>().unwrap();"
                "assert_eq!(buffer.len(), 2);"
                "```";

                #[inline]
                pub fn len(&self) -> usize {
                    self.bytes.len()
                }
            }

            doc_comment! {
                "Returns true if the buffer has a length of 0."

                "```rust"
                "# use s2n_codec::*;"
                "let mut data = [1];"
                concat!("let buffer = ", stringify!($name), "::new(&mut data);")
                ""
                "assert!(!buffer.is_empty());"
                "let (_, buffer) = buffer.decode::<u8>().unwrap();"
                "assert!(buffer.is_empty());"
                "```";

                #[inline]
                pub fn is_empty(&self) -> bool {
                    self.bytes.is_empty()
                }
            }

            /// Borrows the buffer's slice. This should be used with caution, as it
            /// removes any panic protection this struct provides.
            #[inline]
            pub fn as_less_safe_slice(&'a self) -> &'a [u8] {
                self.bytes
            }
        }

        impl<'a> From<&'a mut [u8]> for $name<'a> {
            #[inline]
            fn from(bytes: &'a mut [u8]) -> Self {
                Self::new(bytes)
            }
        }

        impl<'a> PartialEq<[u8]> for $name<'a> {
            #[inline]
            fn eq(&self, rhs: &[u8]) -> bool {
                let bytes: &[u8] = self.bytes.as_ref();
                bytes.eq(rhs)
            }
        }
    };
}

pub mod buffer;
pub mod buffer_mut;
pub mod checked_range;
#[macro_use]
pub mod value;

pub use buffer::*;
pub use buffer_mut::*;
pub use checked_range::*;
pub use value::*;

#[derive(Debug)]
pub enum DecoderError {
    UnexpectedEof(usize),
    UnexpectedBytes(usize),
    LengthCapacityExceeded,
    InvariantViolation(&'static str), // TODO replace with a &'static Invariant
}

use core::fmt;
impl fmt::Display for DecoderError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // https://github.com/model-checking/kani/issues/1767#issuecomment-1275449305
        if cfg!(kani) {
            return Ok(());
        }

        match self {
            Self::UnexpectedEof(len) => write!(f, "unexpected eof: {len}"),
            Self::UnexpectedBytes(len) => write!(f, "unexpected bytes: {len}"),
            Self::LengthCapacityExceeded => write!(
                f,
                "length could not be represented in platform's usize type"
            ),
            Self::InvariantViolation(msg) => write!(f, "{msg}"),
        }
    }
}

impl From<DecoderError> for &'static str {
    fn from(error: DecoderError) -> Self {
        match error {
            DecoderError::UnexpectedEof(_len) => "unexpected eof",
            DecoderError::UnexpectedBytes(_len) => "unexpected bytes",
            DecoderError::LengthCapacityExceeded => {
                "length could not be represented in platform's usize type"
            }
            DecoderError::InvariantViolation(msg) => msg,
        }
    }
}

#[macro_export]
macro_rules! decoder_invariant {
    ($expr:expr, $invariant:expr) => {
        if !($expr) {
            return ::core::result::Result::Err(
                $crate::decoder::DecoderError::InvariantViolation($invariant).into(),
            );
        }
    };
}