use std::sync::mpsc;
use std::thread;
use std::time::Duration;

pub fn sleep_sort(vec: &[usize]) -> Vec<usize> {
    let len = vec.len();
    let (tx, rx) = mpsc::channel();

    for &x in vec.iter() {
        let tx: mpsc::Sender<usize> = tx.clone();
        thread::spawn(move || {
            thread::sleep(Duration::from_millis((20 * x) as u64));
            tx.send(x).expect("panic");
        });
    }
    let mut sorted_list: Vec<usize> = Vec::new();

    for _ in 0..len {
        sorted_list.push(rx.recv().unwrap())
    }

    sorted_list
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty() {
        let res = sleep_sort(&[]);
        assert_eq!(res, &[]);
    }

    #[test]
    fn single_element() {
        let res = sleep_sort(&[1]);
        assert_eq!(res, &[1]);
    }

    #[test]
    fn sorted_array() {
        let res = sleep_sort(&[1, 2, 3, 4]);
        assert_eq!(res, &[1, 2, 3, 4]);
    }

    #[test]
    fn unsorted_array() {
        let res = sleep_sort(&[3, 4, 2, 1]);
        assert_eq!(res, &[1, 2, 3, 4]);
    }

    #[test]
    fn odd_number_of_elements() {
        let res = sleep_sort(&[3, 1, 7]);
        assert_eq!(res, &[1, 3, 7]);
    }

    #[test]
    fn repeated_elements() {
        let res = sleep_sort(&[1, 1, 1, 1]);
        assert_eq!(res, &[1, 1, 1, 1]);
    }

    #[test]
    fn random_elements() {
        let res = sleep_sort(&[5, 3, 7, 10, 1, 0, 8]);
        assert_eq!(res, &[0, 1, 3, 5, 7, 8, 10]);
    }
}


fn _stooge_sort<T: Ord>(arr: &mut [T], start: usize, end: usize) {
    if arr[start] > arr[end] {
        arr.swap(start, end);
    }

    if start + 1 >= end {
        return;
    }

    let k = (end - start + 1) / 3;

    _stooge_sort(arr, start, end - k);
    _stooge_sort(arr, start + k, end);
    _stooge_sort(arr, start, end - k);
}

pub fn stooge_sort<T: Ord>(arr: &mut [T]) {
    let len = arr.len();
    if len == 0 {
        return;
    }

    _stooge_sort(arr, 0, len - 1);
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::sorting::have_same_elements;
    use crate::sorting::is_sorted;

    #[test]
    fn basic() {
        let mut vec = vec![3, 5, 6, 3, 1, 4];
        let cloned = vec.clone();
        stooge_sort(&mut vec);
        assert!(is_sorted(&vec) && have_same_elements(&vec, &cloned));
    }

    #[test]
    fn empty() {
        let mut vec: Vec<i32> = vec![];
        let cloned = vec.clone();
        stooge_sort(&mut vec);
        assert!(is_sorted(&vec) && have_same_elements(&vec, &cloned));
    }

    #[test]
    fn reverse() {
        let mut vec = vec![6, 5, 4, 3, 2, 1];
        let cloned = vec.clone();
        stooge_sort(&mut vec);
        assert!(is_sorted(&vec) && have_same_elements(&vec, &cloned));
    }

    #[test]
    fn already_sorted() {
        let mut vec = vec![1, 2, 3, 4, 5, 6];
        let cloned = vec.clone();
        stooge_sort(&mut vec);
        assert!(is_sorted(&vec) && have_same_elements(&vec, &cloned));
    }
}


use std::cmp;

static MIN_MERGE: usize = 32;

fn min_run_length(mut n: usize) -> usize {
    let mut r = 0;
    while n >= MIN_MERGE {
        r |= n & 1;
        n >>= 1;
    }
    n + r
}

fn insertion_sort(arr: &mut Vec<i32>, left: usize, right: usize) -> &Vec<i32> {
    for i in (left + 1)..(right + 1) {
        let temp = arr[i];
        let mut j = (i - 1) as i32;

        while j >= (left as i32) && arr[j as usize] > temp {
            arr[(j + 1) as usize] = arr[j as usize];
            j -= 1;
        }
        arr[(j + 1) as usize] = temp;
    }
    arr
}

fn merge(arr: &mut Vec<i32>, l: usize, m: usize, r: usize) -> &Vec<i32> {
    let len1 = m - l + 1;
    let len2 = r - m;
    let mut left = vec![0; len1];
    let mut right = vec![0; len2];

    left[..len1].clone_from_slice(&arr[l..(len1 + l)]);

    for x in 0..len2 {
        right[x] = arr[m + 1 + x];
    }

    let mut i = 0;
    let mut j = 0;
    let mut k = l;

    while i < len1 && j < len2 {
        if left[i] <= right[j] {
            arr[k] = left[i];
            i += 1;
        } else {
            arr[k] = right[j];
            j += 1;
        }
        k += 1;
    }

    while i < len1 {
        arr[k] = left[i];
        k += 1;
        i += 1;
    }

    while j < len2 {
        arr[k] = right[j];
        k += 1;
        j += 1;
    }
    arr
}

pub fn tim_sort(arr: &mut Vec<i32>, n: usize) {
    let min_run = min_run_length(MIN_MERGE);

    let mut i = 0;
    while i < n {
        insertion_sort(arr, i, cmp::min(i + MIN_MERGE - 1, n - 1));
        i += min_run;
    }

    let mut size = min_run;
    while size < n {
        let mut left = 0;
        while left < n {
            let mid = left + size - 1;
            let right = cmp::min(left + 2 * size - 1, n - 1);
            if mid < right {
                merge(arr, left, mid, right);
            }

            left += 2 * size;
        }
        size *= 2;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sorting::have_same_elements;
    use crate::sorting::is_sorted;

    #[test]
    fn basic() {
        let mut array = vec![-2, 7, 15, -14, 0, 15, 0, 7, -7, -4, -13, 5, 8, -14, 12];
        let cloned = array.clone();
        let arr_len = array.len();
        tim_sort(&mut array, arr_len);
        assert!(is_sorted(&array) && have_same_elements(&array, &cloned));
    }

    #[test]
    fn empty() {
        let mut array = Vec::<i32>::new();
        let cloned = array.clone();
        let arr_len = array.len();
        tim_sort(&mut array, arr_len);
        assert!(is_sorted(&array) && have_same_elements(&array, &cloned));
    }

    #[test]
    fn one_element() {
        let mut array = vec![3];
        let cloned = array.clone();
        let arr_len = array.len();
        tim_sort(&mut array, arr_len);
        assert!(is_sorted(&array) && have_same_elements(&array, &cloned));
    }

    #[test]
    fn pre_sorted() {
        let mut array = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        let cloned = array.clone();
        let arr_len = array.len();
        tim_sort(&mut array, arr_len);
        assert!(is_sorted(&array) && have_same_elements(&array, &cloned));
    }
}


//Wiggle Sort.
//Given an unsorted array nums, reorder it such
//that nums[0] < nums[1] > nums[2] < nums[3]....
//For example:
//if input numbers = [3, 5, 2, 1, 6, 4]
//one possible Wiggle Sorted answer is [3, 5, 1, 6, 2, 4].

pub fn wiggle_sort(nums: Vec<i32>) -> Vec<i32> {
    //Rust implementation of wiggle.
    //    Example:
    //    >>> wiggle_sort([0, 5, 3, 2, 2])
    //    [0, 5, 2, 3, 2]
    //    >>> wiggle_sort([])
    //    []
    //    >>> wiggle_sort([-2, -5, -45])
    //    [-45, -2, -5]

    let len = nums.len();
    let mut p = nums;
    for i in 1..len {
        let num_x = p[i - 1];
        let num_y = p[i];
        if (i % 2 == 1) == (num_x > num_y) {
            p[i - 1] = num_y;
            p[i] = num_x;
        }
    }
    return p;
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sorting::have_same_elements;
    use crate::sorting::is_sorted;
    #[test]
    fn wingle_elements() {
        let arr = vec![3, 5, 2, 1, 6, 4];
        let cloned = arr.clone();
        let res = wiggle_sort(cloned);
        assert!(is_sorted(&res) && have_same_elements(&res, &cloned));
    }

    #[test]
    fn odd_number_of_elements() {
        let arr = vec![4, 1, 3, 5, 2];
        let cloned = arr.clone();
        let res = wiggle_sort(cloned);
        assert!(is_sorted(&res) && have_same_elements(&res, &cloned));
    }

    #[test]
    fn repeated_elements() {
        let arr = vec![5, 5, 5, 5];
        let cloned = arr.clone();
        let res = wiggle_sort(cloned);
        assert!(is_sorted(&res) && have_same_elements(&res, &cloned));
    }
}


pub fn check_anagram(s: &str, t: &str) -> bool {
    let mut s = s.to_ascii_lowercase().chars().collect::<Vec<_>>();
    let mut t = t.to_ascii_lowercase().chars().collect::<Vec<_>>();
    s.sort_unstable();
    t.sort_unstable();
    s == t
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_check_anagram() {
        assert!(check_anagram("anagram", "nagaram"));
        assert!(!check_anagram("rat", "car"));
        assert!(check_anagram("abcde", "edcba"));
        assert!(check_anagram("sIlEnT", "LiStEn"));
    }
}


/*
    It autocomplete by prefix using added words.

    word List => ["apple", "orange", "oregano"]
    prefix => "or"
    matches => ["orange", "oregano"]
*/

use std::collections::HashMap;

const END: char = '#';

#[derive(Debug)]
struct Trie(HashMap<char, Box<Trie>>);

impl Trie {
    fn new() -> Self {
        Trie(HashMap::new())
    }

    fn insert(&mut self, text: String) {
        let mut trie = self;

        for c in text.chars().collect::<Vec<char>>() {
            trie = trie.0.entry(c).or_insert_with(|| Box::new(Trie::new()));
        }

        trie.0.insert(END, Box::new(Trie::new()));
    }

    fn find(&self, prefix: String) -> Vec<String> {
        let mut trie = self;

        for c in prefix.chars().collect::<Vec<char>>() {
            let char_trie = trie.0.get(&c);
            if let Some(char_trie) = char_trie {
                trie = char_trie;
            } else {
                return vec![];
            }
        }

        Self::_elements(trie)
            .iter()
            .map(|s| prefix.clone() + s)
            .collect()
    }

    fn _elements(map: &Trie) -> Vec<String> {
        let mut results = vec![];

        for (c, v) in map.0.iter() {
            let mut sub_result = vec![];
            if c == &END {
                sub_result.push("".to_owned())
            } else {
                Self::_elements(v)
                    .iter()
                    .map(|s| sub_result.push(c.to_string() + s))
                    .collect()
            }

            results.extend(sub_result)
        }

        results
    }
}

pub struct Autocomplete {
    trie: Trie,
}

impl Autocomplete {
    fn new() -> Self {
        Self { trie: Trie::new() }
    }

    pub fn insert_words(&mut self, words: Vec<String>) {
        for word in words {
            self.trie.insert(word);
        }
    }

    pub fn find_words(&self, prefix: String) -> Vec<String> {
        self.trie.find(prefix)
    }
}

impl Default for Autocomplete {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::Autocomplete;

    #[test]
    fn test_autocomplete() {
        let words = vec![
            "apple".to_owned(),
            "orange".to_owned(),
            "oregano".to_owned(),
        ];

        let mut auto_complete = Autocomplete::new();
        auto_complete.insert_words(words);

        let prefix = "app".to_owned();
        let mut auto_completed_words = auto_complete.find_words(prefix);

        let mut apple = vec!["apple".to_owned()];
        apple.sort();

        auto_completed_words.sort();
        assert_eq!(auto_completed_words, apple);

        let prefix = "or".to_owned();
        let mut auto_completed_words = auto_complete.find_words(prefix);

        let mut prefix_or = vec!["orange".to_owned(), "oregano".to_owned()];
        prefix_or.sort();

        auto_completed_words.sort();
        assert_eq!(auto_completed_words, prefix_or);
    }
}


// In computer science, the Boyerâ€“Moore string-search algorithm is an efficient string-searching algorithm,
// that is the standard benchmark for practical string-search literature. Source: https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm

use std::collections::HashMap;

pub fn boyer_moore_search(text: &str, pattern: &str) -> Vec<usize> {
    let mut positions = Vec::new();
    let n = text.len() as i32;
    let m = pattern.len() as i32;
    let pattern: Vec<char> = pattern.chars().collect();
    let text: Vec<char> = text.chars().collect();
    if n == 0 || m == 0 {
        return positions;
    }
    let mut collection = HashMap::new();
    for (i, c) in pattern.iter().enumerate() {
        collection.insert(c, i as i32);
    }
    let mut shift: i32 = 0;
    while shift <= (n - m) {
        let mut j = m - 1;
        while j >= 0 && pattern[j as usize] == text[(shift + j) as usize] {
            j -= 1;
        }
        if j < 0 {
            positions.push(shift as usize);
            let add_to_shift = {
                if (shift + m) < n {
                    let c = text[(shift + m) as usize];
                    let index = collection.get(&c).unwrap_or(&-1);
                    m - index
                } else {
                    1
                }
            };
            shift += add_to_shift;
        } else {
            let c = text[(shift + j) as usize];
            let index = collection.get(&c).unwrap_or(&-1);
            let add_to_shift = std::cmp::max(1, j - index);
            shift += add_to_shift;
        }
    }
    positions
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_boyer_moore_search() {
        let a = boyer_moore_search("AABCAB12AFAABCABFFEGABCAB", "ABCAB");
        assert_eq!(a, [1, 11, 20]);
        let a = boyer_moore_search("AABCAB12AFAABCABFFEGABCAB", "FFF");
        assert_eq!(a, []);
        let a = boyer_moore_search("AABCAB12AFAABCABFFEGABCAB", "CAB");
        assert_eq!(a, [3, 13, 22]);
    }
}


pub fn burrows_wheeler_transform(input: String) -> (String, usize) {
    let len = input.len();

    let mut table = Vec::<String>::with_capacity(len);
    for i in 0..len {
        table.push(input[i..].to_owned() + &input[..i]);
    }
    table.sort_by_key(|a| a.to_lowercase());

    let mut encoded = String::new();
    let mut index: usize = 0;
    for (i, item) in table.iter().enumerate().take(len) {
        encoded.push(item.chars().last().unwrap());
        if item.eq(&input) {
            index = i;
        }
    }

    (encoded, index)
}

pub fn inv_burrows_wheeler_transform(input: (String, usize)) -> String {
    let len = input.0.len();
    let mut table = Vec::<(usize, char)>::with_capacity(len);
    for i in 0..len {
        table.push((i, input.0.chars().nth(i).unwrap()));
    }

    table.sort_by(|a, b| a.1.cmp(&b.1));

    let mut decoded = String::new();
    let mut idx = input.1;
    for _ in 0..len {
        decoded.push(table[idx].1);
        idx = table[idx].0;
    }

    decoded
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    //Ensure function stand-alone legitimacy
    fn stand_alone_function() {
        assert_eq!(
            burrows_wheeler_transform("CARROT".to_string()),
            ("CTRRAO".to_string(), 1usize)
        );
        assert_eq!(
            inv_burrows_wheeler_transform(("CTRRAO".to_string(), 1usize)),
            ("CARROT".to_string())
        );
        assert_eq!(
            burrows_wheeler_transform("THEALGORITHMS".to_string()),
            ("EHLTTRAHGOMSI".to_string(), 11usize)
        );
        assert_eq!(
            inv_burrows_wheeler_transform(("EHLTTRAHGOMSI".to_string(), 11usize)),
            ("THEALGORITHMS".to_string())
        );
        assert_eq!(
            burrows_wheeler_transform("!.!.!??.=::".to_string()),
            (":..!!?:=.?!".to_string(), 0usize)
        );
        assert_eq!(
            inv_burrows_wheeler_transform((":..!!?:=.?!".to_string(), 0usize)),
            "!.!.!??.=::"
        );
    }
    #[test]
    fn basic_characters() {
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("CARROT".to_string())),
            "CARROT"
        );
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("TOMATO".to_string())),
            "TOMATO"
        );
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("THISISATEST".to_string())),
            "THISISATEST"
        );
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("THEALGORITHMS".to_string())),
            "THEALGORITHMS"
        );
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("RUST".to_string())),
            "RUST"
        );
    }

    #[test]
    fn special_characters() {
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("!.!.!??.=::".to_string())),
            "!.!.!??.=::"
        );
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform(
                "!{}{}(((&&%%!??.=::".to_string()
            )),
            "!{}{}(((&&%%!??.=::"
        );
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("//&$[]".to_string())),
            "//&$[]"
        );
    }

    #[test]
    fn empty() {
        assert_eq!(
            inv_burrows_wheeler_transform(burrows_wheeler_transform("".to_string())),
            ""
        );
    }
}


// A string is called simple (or a Lyndon word), if it is strictly smaller than any of its own nontrivial suffixes.
// Duval (1983) developed an algorithm for finding the standard factorization that runs in linear time and constant space. Source: https://en.wikipedia.org/wiki/Lyndon_word
fn factorization_with_duval(s: &[u8]) -> Vec<String> {
    let n = s.len();
    let mut i = 0;
    let mut factorization: Vec<String> = Vec::new();

    while i < n {
        let mut j = i + 1;
        let mut k = i;

        while j < n && s[k] <= s[j] {
            if s[k] < s[j] {
                k = i;
            } else {
                k += 1;
            }
            j += 1;
        }

        while i <= k {
            factorization.push(String::from_utf8(s[i..i + j - k].to_vec()).unwrap());
            i += j - k;
        }
    }

    factorization
}

pub fn duval_algorithm(s: &str) -> Vec<String> {
    return factorization_with_duval(s.as_bytes());
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_duval_multiple() {
        let text = "abcdabcdababc";
        assert_eq!(duval_algorithm(text), ["abcd", "abcd", "ababc"]);
    }

    #[test]
    fn test_duval_all() {
        let text = "aaa";
        assert_eq!(duval_algorithm(text), ["a", "a", "a"]);
    }

    #[test]
    fn test_duval_single() {
        let text = "ababb";
        assert_eq!(duval_algorithm(text), ["ababb"]);
    }

    #[test]
    fn test_factorization_with_duval_multiple() {
        let text = "abcdabcdababc";
        assert_eq!(
            factorization_with_duval(text.as_bytes()),
            ["abcd", "abcd", "ababc"]
        );
    }
}


pub fn hamming_distance(string1: &str, string2: &str) -> usize {
    let mut distance = 0;
    let mut string1 = string1.chars();
    let mut string2 = string2.chars();

    loop {
        match (string1.next(), string2.next()) {
            (Some(char1), Some(char2)) if char1 != char2 => distance += 1,
            (Some(char1), Some(char2)) if char1 == char2 => continue,
            (None, Some(_)) | (Some(_), None) => panic!("Strings must have the same length"),
            (None, None) => break,
            _ => unreachable!(),
        }
    }
    distance
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_strings() {
        let result = hamming_distance("", "");
        assert_eq!(result, 0);
    }
    #[test]
    fn distance_zero() {
        let result = hamming_distance("rust", "rust");
        assert_eq!(result, 0);
    }
    #[test]
    fn distance_three() {
        let result = hamming_distance("karolin", "kathrin");
        assert_eq!(result, 3);
    }
    #[test]
    fn distance_four() {
        let result = hamming_distance("kathrin", "kerstin");
        assert_eq!(result, 4);
    }
    #[test]
    fn distance_five() {
        let result = hamming_distance("00000", "11111");
        assert_eq!(result, 5);
    }
}


// In computer science and statistics,
// the Jaroâ€“Winkler distance is a string metric measuring an edit distance
// between two sequences.
// It is a variant proposed in 1990 by William E. Winkler
// of the Jaro distance metric (1989, Matthew A. Jaro).

pub fn jaro_winkler_distance(str1: &str, str2: &str) -> f64 {
    if str1.is_empty() || str2.is_empty() {
        return 0.0;
    }
    fn get_matched_characters(s1: &str, s2: &str) -> String {
        let mut s2 = s2.to_string();
        let mut matched: Vec<char> = Vec::new();
        let limit = std::cmp::min(s1.len(), s2.len()) / 2;
        for (i, l) in s1.chars().enumerate() {
            let left = std::cmp::max(0, i as i32 - limit as i32) as usize;
            let right = std::cmp::min(i + limit + 1, s2.len());
            if s2[left..right].contains(l) {
                matched.push(l);
                let a = &s2[0..s2.find(l).expect("this exists")];
                let b = &s2[(s2.find(l).expect("this exists") + 1)..];
                s2 = format!("{a} {b}");
            }
        }
        matched.iter().collect::<String>()
    }

    let matching_1 = get_matched_characters(str1, str2);
    let matching_2 = get_matched_characters(str2, str1);
    let match_count = matching_1.len();

    // transposition
    let transpositions = {
        let mut count = 0;
        for (c1, c2) in matching_1.chars().zip(matching_2.chars()) {
            if c1 != c2 {
                count += 1;
            }
        }
        count / 2
    };

    let jaro: f64 = {
        if match_count == 0 {
            return 0.0;
        } else {
            (1_f64 / 3_f64)
                * (match_count as f64 / str1.len() as f64
                    + match_count as f64 / str2.len() as f64
                    + (match_count - transpositions) as f64 / match_count as f64)
        }
    };

    let mut prefix_len = 0.0;
    let bound = std::cmp::min(std::cmp::min(str1.len(), str2.len()), 4);
    for (c1, c2) in str1[..bound].chars().zip(str2[..bound].chars()) {
        if c1 == c2 {
            prefix_len += 1.0;
        } else {
            break;
        }
    }
    jaro + (0.1 * prefix_len * (1.0 - jaro))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_jaro_winkler_distance() {
        let a = jaro_winkler_distance("hello", "world");
        assert_eq!(a, 0.4666666666666666);
        let a = jaro_winkler_distance("martha", "marhta");
        assert_eq!(a, 0.9611111111111111);
        let a = jaro_winkler_distance("martha", "marhat");
        assert_eq!(a, 0.9611111111111111);
        let a = jaro_winkler_distance("test", "test");
        assert_eq!(a, 1.0);
        let a = jaro_winkler_distance("test", "");
        assert_eq!(a, 0.0);
        let a = jaro_winkler_distance("hello world", "HeLLo W0rlD");
        assert_eq!(a, 0.6363636363636364);
    }
}


pub fn knuth_morris_pratt(st: String, pat: String) -> Vec<usize> {
    if st.is_empty() || pat.is_empty() {
        return vec![];
    }

    let string = st.into_bytes();
    let pattern = pat.into_bytes();

    // build the partial match table
    let mut partial = vec![0];
    for i in 1..pattern.len() {
        let mut j = partial[i - 1];
        while j > 0 && pattern[j] != pattern[i] {
            j = partial[j - 1];
        }
        partial.push(if pattern[j] == pattern[i] { j + 1 } else { j });
    }

    // and read 'string' to find 'pattern'
    let mut ret = vec![];
    let mut j = 0;

    for (i, &c) in string.iter().enumerate() {
        while j > 0 && c != pattern[j] {
            j = partial[j - 1];
        }
        if c == pattern[j] {
            j += 1;
        }
        if j == pattern.len() {
            ret.push(i + 1 - j);
            j = partial[j - 1];
        }
    }

    ret
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn each_letter_matches() {
        let index = knuth_morris_pratt("aaa".to_string(), "a".to_string());
        assert_eq!(index, vec![0, 1, 2]);
    }

    #[test]
    fn a_few_separate_matches() {
        let index = knuth_morris_pratt("abababa".to_string(), "ab".to_string());
        assert_eq!(index, vec![0, 2, 4]);
    }

    #[test]
    fn one_match() {
        let index =
            knuth_morris_pratt("ABC ABCDAB ABCDABCDABDE".to_string(), "ABCDABD".to_string());
        assert_eq!(index, vec![15]);
    }

    #[test]
    fn lots_of_matches() {
        let index = knuth_morris_pratt("aaabaabaaaaa".to_string(), "aa".to_string());
        assert_eq!(index, vec![0, 1, 4, 7, 8, 9, 10]);
    }

    #[test]
    fn lots_of_intricate_matches() {
        let index = knuth_morris_pratt("ababababa".to_string(), "aba".to_string());
        assert_eq!(index, vec![0, 2, 4, 6]);
    }

    #[test]
    fn not_found0() {
        let index = knuth_morris_pratt("abcde".to_string(), "f".to_string());
        assert_eq!(index, vec![]);
    }

    #[test]
    fn not_found1() {
        let index = knuth_morris_pratt("abcde".to_string(), "ac".to_string());
        assert_eq!(index, vec![]);
    }

    #[test]
    fn not_found2() {
        let index = knuth_morris_pratt("ababab".to_string(), "bababa".to_string());
        assert_eq!(index, vec![]);
    }

    #[test]
    fn empty_string() {
        let index = knuth_morris_pratt("".to_string(), "abcdef".to_string());
        assert_eq!(index, vec![]);
    }
}


use std::cmp::min;

/// The Levenshtein distance (or edit distance) between 2 strings.\
/// This edit distance is defined as being 1 point per insertion, substitution, or deletion which must be made to make the strings equal.
/// This function iterates over the bytes in the string, so it may not behave entirely as expected for non-ASCII strings.
///
/// For a detailed explanation, check the example on Wikipedia: <https://en.wikipedia.org/wiki/Levenshtein_distance>\
/// (see the examples with the matrices, for instance between KITTEN and SITTING)
///
/// Note that although we compute a matrix, left-to-right, top-to-bottom, at each step all we need to compute `cell[i][j]` is:
///   - `cell[i][j-1]`
///   - `cell[i-j][j]`
///   - `cell[i-i][j-1]`
///
/// This can be achieved by only using one "rolling" row and one additional variable, when computed `cell[i][j]` (or `row[i]`):
///   - `cell[i][j-1]` is the value to the left, on the same row (the one we just computed, `row[i-1]`)
///   - `cell[i-1][j]` is the value at `row[i]`, the one we're changing
///   - `cell[i-1][j-1]` was the value at `row[i-1]` before we changed it, for that we'll use a variable
///
/// Doing this reduces space complexity from O(nm) to O(n)
///
/// Second note: if we want to minimize space, since we're now O(n) make sure you use the shortest string horizontally, and the longest vertically
///
/// # Complexity
///   - time complexity: O(nm),
///   - space complexity: O(n),
///
/// where n and m are lengths of `str_a` and `str_b`
pub fn levenshtein_distance(string1: &str, string2: &str) -> usize {
    if string1.is_empty() {
        return string2.len();
    }
    let l1 = string1.len();
    let mut prev_dist: Vec<usize> = (0..=l1).collect();

    for (row, c2) in string2.chars().enumerate() {
        let mut prev_substitution_cost = prev_dist[0]; // we'll keep a reference to matrix[i-1][j-1] (top-left cell)
        prev_dist[0] = row + 1; // diff with empty string, since `row` starts at 0, it's `row + 1`

        for (col, c1) in string1.chars().enumerate() {
            let deletion_cost = prev_dist[col] + 1; // "on the left" in the matrix (i.e. the value we just computed)
            let insertion_cost = prev_dist[col + 1] + 1; // "on the top" in the matrix (means previous)
            let substitution_cost = if c1 == c2 {
                prev_substitution_cost // last char is the same on both ends, so the min_distance is left unchanged from matrix[i-1][i+1]
            } else {
                prev_substitution_cost + 1 // substitute the last character
            };

            prev_substitution_cost = prev_dist[col + 1]; // save the old value at (i-1, j-1)
            prev_dist[col + 1] = min3(deletion_cost, insertion_cost, substitution_cost);
        }
    }
    prev_dist[l1]
}

#[cfg(test)]
mod levenshtein_distance_should {
    use super::levenshtein_distance;

    #[test]
    fn test_doc_example() {
        assert_eq!(2, levenshtein_distance("FROG", "DOG"));
    }

    #[test]
    fn return_0_with_empty_strings() {
        assert_eq!(0, levenshtein_distance("", ""));
    }

    #[test]
    fn return_1_with_empty_and_a() {
        assert_eq!(1, levenshtein_distance("", "a"));
    }

    #[test]
    fn return_1_with_a_and_empty() {
        assert_eq!(1, levenshtein_distance("a", ""));
    }

    #[test]
    fn return_1_with_ab_and_a() {
        assert_eq!(1, levenshtein_distance("ab", "a"));
    }

    #[test]
    fn return_0_with_foobar_and_foobar() {
        assert_eq!(0, levenshtein_distance("foobar", "foobar"));
    }

    #[test]
    fn return_6_with_foobar_and_barfoo() {
        assert_eq!(6, levenshtein_distance("foobar", "barfoo"));
    }

    #[test]
    fn return_1_with_kind_and_bind() {
        assert_eq!(1, levenshtein_distance("kind", "bind"));
    }

    #[test]
    fn return_3_with_winner_and_win() {
        assert_eq!(3, levenshtein_distance("winner", "win"));
    }

    #[test]
    fn equal_strings() {
        assert_eq!(0, levenshtein_distance("Hello, world!", "Hello, world!"));
        assert_eq!(0, levenshtein_distance("Hello, world!", "Hello, world!"));
        assert_eq!(0, levenshtein_distance("Test_Case_#1", "Test_Case_#1"));
        assert_eq!(0, levenshtein_distance("Test_Case_#1", "Test_Case_#1"));
    }

    #[test]
    fn one_edit_difference() {
        assert_eq!(1, levenshtein_distance("Hello, world!", "Hell, world!"));
        assert_eq!(1, levenshtein_distance("Test_Case_#1", "Test_Case_#2"));
        assert_eq!(1, levenshtein_distance("Test_Case_#1", "Test_Case_#10"));
        assert_eq!(1, levenshtein_distance("Hello, world!", "Hell, world!"));
        assert_eq!(1, levenshtein_distance("Test_Case_#1", "Test_Case_#2"));
        assert_eq!(1, levenshtein_distance("Test_Case_#1", "Test_Case_#10"));
    }

    #[test]
    fn several_differences() {
        assert_eq!(2, levenshtein_distance("My Cat", "My Case"));
        assert_eq!(7, levenshtein_distance("Hello, world!", "Goodbye, world!"));
        assert_eq!(6, levenshtein_distance("Test_Case_#3", "Case #3"));
        assert_eq!(2, levenshtein_distance("My Cat", "My Case"));
        assert_eq!(7, levenshtein_distance("Hello, world!", "Goodbye, world!"));
        assert_eq!(6, levenshtein_distance("Test_Case_#3", "Case #3"));
    }
}

fn min3(a: usize, b: usize, c: usize) -> usize {
    min(a, min(b, c))
}

#[cfg(test)]
mod min3_should {
    use super::min3;

    #[test]
    fn return_1_with_1_2_3() {
        assert_eq!(1, min3(1, 2, 3));
    }

    #[test]
    fn return_1_with_3_2_1() {
        assert_eq!(1, min3(3, 2, 1));
    }

    #[test]
    fn return_1_with_2_3_1() {
        assert_eq!(1, min3(2, 3, 1));
    }
}


pub fn manacher(s: String) -> String {
    let l = s.len();
    if l <= 1 {
        return s;
    }

    // MEMO: We need to detect odd palindrome as well,
    // therefore, inserting dummy string so that
    // we can find a pair with dummy center character.
    let mut chars: Vec<char> = Vec::with_capacity(s.len() * 2 + 1);
    for c in s.chars() {
        chars.push('#');
        chars.push(c);
    }
    chars.push('#');

    // List: storing the length of palindrome at each index of string
    let mut length_of_palindrome = vec![1usize; chars.len()];
    // Integer: Current checking palindrome's center index
    let mut current_center: usize = 0;
    // Integer: Right edge index existing the radius away from current center
    let mut right_from_current_center: usize = 0;

    for i in 0..chars.len() {
        // 1: Check if we are looking at right side of palindrome.
        if right_from_current_center > i && i > current_center {
            // 1-1: If so copy from the left side of palindrome.
            // If the value + index exceeds the right edge index, we should cut and check palindrome later #3.
            length_of_palindrome[i] = std::cmp::min(
                right_from_current_center - i,
                length_of_palindrome[2 * current_center - i],
            );
            // 1-2: Move the checking palindrome to new index if it exceeds the right edge.
            if length_of_palindrome[i] + i >= right_from_current_center {
                current_center = i;
                right_from_current_center = length_of_palindrome[i] + i;
                // 1-3: If radius exceeds the end of list, it means checking is over.
                // You will never get the larger value because the string will get only shorter.
                if right_from_current_center >= chars.len() - 1 {
                    break;
                }
            } else {
                // 1-4: If the checking index doesn't exceeds the right edge,
                // it means the length is just as same as the left side.
                // You don't need to check anymore.
                continue;
            }
        }

        // Integer: Current radius from checking index
        // If it's copied from left side and more than 1,
        // it means it's ensured so you don't need to check inside radius.
        let mut radius: usize = (length_of_palindrome[i] - 1) / 2;
        radius += 1;
        // 2: Checking palindrome.
        // Need to care about overflow usize.
        while i >= radius && i + radius <= chars.len() - 1 && chars[i - radius] == chars[i + radius]
        {
            length_of_palindrome[i] += 2;
            radius += 1;
        }
    }

    // 3: Find the maximum length and generate answer.
    let center_of_max = length_of_palindrome
        .iter()
        .enumerate()
        .max_by_key(|(_, &value)| value)
        .map(|(idx, _)| idx)
        .unwrap();
    let radius_of_max = (length_of_palindrome[center_of_max] - 1) / 2;
    let answer = &chars[(center_of_max - radius_of_max)..(center_of_max + radius_of_max + 1)]
        .iter()
        .collect::<String>();
    answer.replace('#', "")
}

#[cfg(test)]
mod tests {
    use super::manacher;

    #[test]
    fn get_longest_palindrome_by_manacher() {
        assert_eq!(manacher("babad".to_string()), "aba".to_string());
        assert_eq!(manacher("cbbd".to_string()), "bb".to_string());
        assert_eq!(manacher("a".to_string()), "a".to_string());

        let ac_ans = manacher("ac".to_string());
        assert!(ac_ans == *"a" || ac_ans == *"c");
    }
}


mod aho_corasick;
mod anagram;
mod autocomplete_using_trie;
mod boyer_moore_search;
mod burrows_wheeler_transform;
mod duval_algorithm;
mod hamming_distance;
mod jaro_winkler_distance;
mod knuth_morris_pratt;
mod levenshtein_distance;
mod manacher;
mod palindrome;
mod rabin_karp;
mod reverse;
mod run_length_encoding;
mod suffix_array;
mod suffix_tree;
mod z_algorithm;

pub use self::aho_corasick::AhoCorasick;
pub use self::anagram::check_anagram;
pub use self::autocomplete_using_trie::Autocomplete;
pub use self::boyer_moore_search::boyer_moore_search;
pub use self::burrows_wheeler_transform::{
    burrows_wheeler_transform, inv_burrows_wheeler_transform,
};
pub use self::duval_algorithm::duval_algorithm;
pub use self::hamming_distance::hamming_distance;
pub use self::jaro_winkler_distance::jaro_winkler_distance;
pub use self::knuth_morris_pratt::knuth_morris_pratt;
pub use self::levenshtein_distance::levenshtein_distance;
pub use self::manacher::manacher;
pub use self::palindrome::is_palindrome;
pub use self::rabin_karp::rabin_karp;
pub use self::reverse::reverse;
pub use self::run_length_encoding::{run_length_decoding, run_length_encoding};
pub use self::suffix_array::generate_suffix_array;
pub use self::suffix_tree::{Node, SuffixTree};
pub use self::z_algorithm::match_pattern;
pub use self::z_algorithm::z_array;


pub fn is_palindrome(s: &str) -> bool {
    let mut chars = s.chars();
    while let (Some(c1), Some(c2)) = (chars.next(), chars.next_back()) {
        if c1 != c2 {
            return false;
        }
    }
    true
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn palindromes() {
        assert!(is_palindrome("abcba"));
        assert!(is_palindrome("abba"));
        assert!(is_palindrome("a"));
        assert!(is_palindrome("arcra"));
        assert!(!is_palindrome("abcde"));
        assert!(!is_palindrome("aaaabbbb"));
    }
}


const MODULUS: u16 = 101;
const BASE: u16 = 256;

pub fn rabin_karp(target: String, pattern: String) -> Vec<usize> {
    // Quick exit
    if target.is_empty() || pattern.is_empty() || pattern.len() > target.len() {
        return vec![];
    }

    let pattern_hash = hash(pattern.as_str());

    // Pre-calculate BASE^(n-1)
    let mut pow_rem: u16 = 1;
    for _ in 0..pattern.len() - 1 {
        pow_rem *= BASE;
        pow_rem %= MODULUS;
    }

    let mut rolling_hash = 0;
    let mut ret = vec![];
    for i in 0..=target.len() - pattern.len() {
        rolling_hash = if i == 0 {
            hash(&target[0..pattern.len()])
        } else {
            recalculate_hash(
                target.as_str(),
                i - 1,
                i + pattern.len() - 1,
                rolling_hash,
                pow_rem,
            )
        };
        if rolling_hash == pattern_hash && pattern[..] == target[i..i + pattern.len()] {
            ret.push(i);
        }
    }
    ret
}

// hash(s) is defined as BASE^(n-1) * s_0 + BASE^(n-2) * s_1 + ... + BASE^0 * s_(n-1)
fn hash(s: &str) -> u16 {
    let mut res: u16 = 0;
    for &c in s.as_bytes().iter() {
        res = (res * BASE % MODULUS + c as u16) % MODULUS;
    }
    res
}

// new_hash = (old_hash - BASE^(n-1) * s_(i-n)) * BASE + s_i
fn recalculate_hash(
    s: &str,
    old_index: usize,
    new_index: usize,
    old_hash: u16,
    pow_rem: u16,
) -> u16 {
    let mut new_hash = old_hash;
    let (old_ch, new_ch) = (
        s.as_bytes()[old_index] as u16,
        s.as_bytes()[new_index] as u16,
    );
    new_hash = (new_hash + MODULUS - pow_rem * old_ch % MODULUS) % MODULUS;
    new_hash = (new_hash * BASE + new_ch) % MODULUS;
    new_hash
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hi_hash() {
        let hash_result = hash("hi");
        assert_eq!(hash_result, 65);
    }

    #[test]
    fn abr_hash() {
        let hash_result = hash("abr");
        assert_eq!(hash_result, 4);
    }

    #[test]
    fn bra_hash() {
        let hash_result = hash("bra");
        assert_eq!(hash_result, 30);
    }

    // Attribution to @pgimalac for his tests from Knuth-Morris-Pratt
    #[test]
    fn each_letter_matches() {
        let index = rabin_karp("aaa".to_string(), "a".to_string());
        assert_eq!(index, vec![0, 1, 2]);
    }

    #[test]
    fn a_few_separate_matches() {
        let index = rabin_karp("abababa".to_string(), "ab".to_string());
        assert_eq!(index, vec![0, 2, 4]);
    }

    #[test]
    fn one_match() {
        let index = rabin_karp("ABC ABCDAB ABCDABCDABDE".to_string(), "ABCDABD".to_string());
        assert_eq!(index, vec![15]);
    }

    #[test]
    fn lots_of_matches() {
        let index = rabin_karp("aaabaabaaaaa".to_string(), "aa".to_string());
        assert_eq!(index, vec![0, 1, 4, 7, 8, 9, 10]);
    }

    #[test]
    fn lots_of_intricate_matches() {
        let index = rabin_karp("ababababa".to_string(), "aba".to_string());
        assert_eq!(index, vec![0, 2, 4, 6]);
    }

    #[test]
    fn not_found0() {
        let index = rabin_karp("abcde".to_string(), "f".to_string());
        assert_eq!(index, vec![]);
    }

    #[test]
    fn not_found1() {
        let index = rabin_karp("abcde".to_string(), "ac".to_string());
        assert_eq!(index, vec![]);
    }

    #[test]
    fn not_found2() {
        let index = rabin_karp("ababab".to_string(), "bababa".to_string());
        assert_eq!(index, vec![]);
    }

    #[test]
    fn empty_string() {
        let index = rabin_karp("".to_string(), "abcdef".to_string());
        assert_eq!(index, vec![]);
    }
}


pub fn reverse(text: &str) -> String {
    text.chars().rev().collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_simple() {
        assert_eq!(reverse("racecar"), "racecar");
    }

    #[test]
    fn test_assymetric() {
        assert_eq!(reverse("abcdef"), "fedcba")
    }

    #[test]
    fn test_sentence() {
        assert_eq!(reverse("step on no pets"), "step on no pets");
    }
}


pub fn run_length_encoding(target: String) -> String {
    if target.trim().is_empty() {
        return "String is Empty!".to_string();
    }
    let mut count: i32 = 0;
    let mut base_character: String = "".to_string();
    let mut encoded_target = String::new();

    for c in target.as_str().chars() {
        if base_character == *"" {
            base_character = c.to_string();
        }
        if c.to_string() == base_character {
            count += 1;
        } else {
            encoded_target.push_str(&count.to_string());
            count = 1;
            encoded_target.push_str(&base_character);
            base_character = c.to_string();
        }
    }
    encoded_target.push_str(&count.to_string());
    encoded_target.push_str(&base_character);

    encoded_target
}

pub fn run_length_decoding(target: String) -> String {
    if target.trim().is_empty() {
        return "String is Empty!".to_string();
    }

    let mut character_count: String = String::new();
    let mut decoded_target = String::new();

    for c in target.as_str().chars() {
        character_count.push(c);
        let is_numeric: bool = character_count.parse::<i32>().is_ok();

        if !is_numeric {
            let pop_char: char = character_count.pop().unwrap();
            decoded_target.push_str(
                &pop_char
                    .to_string()
                    .repeat(character_count.parse().unwrap()),
            );
            character_count = "".to_string();
        }
    }

    decoded_target
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn encode_empty() {
        assert_eq!(
            (run_length_encoding("".to_string())),
            "String is Empty!".to_string()
        )
    }

    #[test]
    fn encode_identical_character() {
        assert_eq!(
            (run_length_encoding("aaaaaaaaaa".to_string())),
            "10a".to_string()
        )
    }
    #[test]
    fn encode_continuous_character() {
        assert_eq!(
            (run_length_encoding("abcdefghijk".to_string())),
            "1a1b1c1d1e1f1g1h1i1j1k".to_string()
        )
    }

    #[test]
    fn encode_random_character() {
        assert_eq!(
            (run_length_encoding("aaaaabbbcccccdddddddddd".to_string())),
            "5a3b5c10d".to_string()
        )
    }

    #[test]
    fn encode_long_character() {
        assert_eq!(
            (run_length_encoding(
                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbcccccdddddddddd".to_string()
            )),
            "200a3b5c10d".to_string()
        )
    }
    #[test]
    fn decode_empty() {
        assert_eq!(
            (run_length_decoding("".to_string())),
            "String is Empty!".to_string()
        )
    }

    #[test]
    fn decode_identical_character() {
        assert_eq!(
            (run_length_decoding("10a".to_string())),
            "aaaaaaaaaa".to_string()
        )
    }
    #[test]
    fn decode_continuous_character() {
        assert_eq!(
            (run_length_decoding("1a1b1c1d1e1f1g1h1i1j1k".to_string())),
            "abcdefghijk".to_string()
        )
    }

    #[test]
    fn decode_random_character() {
        assert_eq!(
            (run_length_decoding("5a3b5c10d".to_string())),
            "aaaaabbbcccccdddddddddd".to_string()
        )
    }

    #[test]
    fn decode_long_character() {
        assert_eq!(
            (run_length_decoding(
                "200a3b5c10d".to_string()
            )),
            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbcccccdddddddddd".to_string()
        )
    }
}


// In computer science, a suffix array is a sorted array of all suffixes of a string.
// It is a data structure used in, among others, full-text indices, data-compression algorithms,
// and the field of bibliometrics. Source: https://en.wikipedia.org/wiki/Suffix_array

use std::cmp::Ordering;

#[derive(Clone)]
struct Suffix {
    index: usize,
    rank: (i32, i32),
}

impl Suffix {
    fn cmp(&self, b: &Self) -> Ordering {
        let a = self;
        let ((a1, a2), (b1, b2)) = (a.rank, b.rank);
        match a1.cmp(&b1) {
            Ordering::Equal => {
                if a2 < b2 {
                    Ordering::Less
                } else {
                    Ordering::Greater
                }
            }
            o => o,
        }
    }
}

pub fn generate_suffix_array(txt: &str) -> Vec<usize> {
    let n = txt.len();
    let mut suffixes: Vec<Suffix> = vec![
        Suffix {
            index: 0,
            rank: (-1, -1)
        };
        n
    ];
    for (i, suf) in suffixes.iter_mut().enumerate() {
        suf.index = i;
        suf.rank.0 = (txt.chars().nth(i).expect("this should exist") as u32 - 'a' as u32) as i32;
        suf.rank.1 = if (i + 1) < n {
            (txt.chars().nth(i + 1).expect("this should exist") as u32 - 'a' as u32) as i32
        } else {
            -1
        }
    }
    suffixes.sort_by(|a, b| a.cmp(b));
    let mut ind = vec![0; n];
    let mut k = 4;
    while k < 2 * n {
        let mut rank = 0;
        let mut prev_rank = suffixes[0].rank.0;
        suffixes[0].rank.0 = rank;
        ind[suffixes[0].index] = 0;

        for i in 1..n {
            if suffixes[i].rank.0 == prev_rank && suffixes[i].rank.1 == suffixes[i - 1].rank.1 {
                prev_rank = suffixes[i].rank.0;
                suffixes[i].rank.0 = rank;
            } else {
                prev_rank = suffixes[i].rank.0;
                rank += 1;
                suffixes[i].rank.0 = rank;
            }
            ind[suffixes[i].index] = i;
        }
        for i in 0..n {
            let next_index = suffixes[i].index + (k / 2);
            suffixes[i].rank.1 = if next_index < n {
                suffixes[ind[next_index]].rank.0
            } else {
                -1
            }
        }
        suffixes.sort_by(|a, b| a.cmp(b));
        k *= 2;
    }
    let mut suffix_arr = Vec::new();
    for suf in suffixes {
        suffix_arr.push(suf.index);
    }
    suffix_arr
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_suffix_array() {
        let a = generate_suffix_array("banana");
        assert_eq!(a, vec![5, 3, 1, 0, 4, 2]);
    }
}


// In computer science, a suffix tree (also called PAT tree or, in an earlier form, position tree)
// is a compressed trie containing all the suffixes of the given text as their keys and positions
// in the text as their values. Suffix trees allow particularly fast implementations of many
// important string operations. Source: https://en.wikipedia.org/wiki/Suffix_tree

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Node {
    pub sub: String,    // substring of input string
    pub ch: Vec<usize>, // vector of child nodes
}

impl Node {
    fn new(sub: String, children: Vec<usize>) -> Self {
        Node {
            sub,
            ch: children.to_vec(),
        }
    }
    pub fn empty() -> Self {
        Node {
            sub: "".to_string(),
            ch: vec![],
        }
    }
}

pub struct SuffixTree {
    pub nodes: Vec<Node>,
}

impl SuffixTree {
    pub fn new(s: String) -> Self {
        let mut suf_tree = SuffixTree {
            nodes: vec![Node::empty()],
        };
        for i in 0..s.len() {
            let (_, substr) = s.split_at(i);
            suf_tree.add_suffix(substr);
        }
        suf_tree
    }
    fn add_suffix(&mut self, suf: &str) {
        let mut n = 0;
        let mut i = 0;
        while i < suf.len() {
            let b = suf.chars().nth(i);
            let mut x2 = 0;
            let mut n2: usize;
            loop {
                let children = &self.nodes[n].ch;
                if children.len() == x2 {
                    n2 = self.nodes.len();
                    self.nodes.push(Node::new(
                        {
                            let (_, sub) = suf.split_at(i);
                            sub.to_string()
                        },
                        vec![],
                    ));
                    self.nodes[n].ch.push(n2);
                    return;
                }
                n2 = children[x2];
                if self.nodes[n2].sub.chars().next() == b {
                    break;
                }
                x2 += 1;
            }
            let sub2 = self.nodes[n2].sub.clone();
            let mut j = 0;
            while j < sub2.len() {
                if suf.chars().nth(i + j) != sub2.chars().nth(j) {
                    let n3 = n2;
                    n2 = self.nodes.len();
                    self.nodes.push(Node::new(
                        {
                            let (sub, _) = sub2.split_at(j);
                            sub.to_string()
                        },
                        vec![n3],
                    ));
                    let (_, temp_sub) = sub2.split_at(j);
                    self.nodes[n3].sub = temp_sub.to_string();
                    self.nodes[n].ch[x2] = n2;
                    break;
                }
                j += 1;
            }
            i += j;
            n = n2;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_suffix_tree() {
        let suf_tree = SuffixTree::new("banana$".to_string());
        assert_eq!(
            suf_tree.nodes,
            vec![
                Node {
                    sub: "".to_string(),
                    ch: vec![1, 8, 6, 10]
                },
                Node {
                    sub: "banana$".to_string(),
                    ch: vec![]
                },
                Node {
                    sub: "na$".to_string(),
                    ch: vec![]
                },
                Node {
                    sub: "na$".to_string(),
                    ch: vec![]
                },
                Node {
                    sub: "na".to_string(),
                    ch: vec![2, 5]
                },
                Node {
                    sub: "$".to_string(),
                    ch: vec![]
                },
                Node {
                    sub: "na".to_string(),
                    ch: vec![3, 7]
                },
                Node {
                    sub: "$".to_string(),
                    ch: vec![]
                },
                Node {
                    sub: "a".to_string(),
                    ch: vec![4, 9]
                },
                Node {
                    sub: "$".to_string(),
                    ch: vec![]
                },
                Node {
                    sub: "$".to_string(),
                    ch: vec![]
                }
            ]
        );
    }
}


fn match_with_z_array<T: Eq>(
    input_string: &[T],
    pattern: &[T],
    start_index: usize,
    only_full_matches: bool,
) -> Vec<usize> {
    let size = input_string.len();
    let pattern_size = pattern.len();
    let mut last_match: usize = 0;
    let mut match_end: usize = 0;
    let mut array = vec![0usize; size];
    for i in start_index..size {
        // getting plain z array of a string requires matching from index
        // 1 instead of 0 (which gives a trivial result instead)
        if i <= match_end {
            array[i] = std::cmp::min(array[i - last_match], match_end - i + 1);
        }
        while (i + array[i]) < size && array[i] < pattern_size {
            if input_string[i + array[i]] != pattern[array[i]] {
                break;
            }
            array[i] += 1;
        }
        if (i + array[i]) > (match_end + 1) {
            match_end = i + array[i] - 1;
            last_match = i;
        }
    }
    if !only_full_matches {
        array
    } else {
        let mut answer: Vec<usize> = vec![];
        for (idx, number) in array.iter().enumerate() {
            if *number == pattern_size {
                answer.push(idx);
            }
        }
        answer
    }
}

pub fn z_array<T: Eq>(input: &[T]) -> Vec<usize> {
    match_with_z_array(input, input, 1, false)
}

pub fn match_pattern<T: Eq>(input: &[T], pattern: &[T]) -> Vec<usize> {
    match_with_z_array(input, pattern, 0, true)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_z_array() {
        let string = "aabaabab";
        let array = z_array(string.as_bytes());
        assert_eq!(array, vec![0, 1, 0, 4, 1, 0, 1, 0]);
    }

    #[test]
    fn pattern_in_text() {
        let text: &str = concat!(
            "lorem ipsum dolor sit amet, consectetur ",
            "adipiscing elit, sed do eiusmod tempor ",
            "incididunt ut labore et dolore magna aliqua"
        );
        let pattern1 = "rem";
        let pattern2 = "em";
        let pattern3 = ";alksdjfoiwer";
        let pattern4 = "m";

        assert_eq!(match_pattern(text.as_bytes(), pattern1.as_bytes()), vec![2]);
        assert_eq!(
            match_pattern(text.as_bytes(), pattern2.as_bytes()),
            vec![3, 73]
        );
        assert_eq!(match_pattern(text.as_bytes(), pattern3.as_bytes()), vec![]);
        assert_eq!(
            match_pattern(text.as_bytes(), pattern4.as_bytes()),
            vec![4, 10, 23, 68, 74, 110]
        );

        let text2 = "aaaaaaaa";
        let pattern5 = "aaa";
        assert_eq!(
            match_pattern(text2.as_bytes(), pattern5.as_bytes()),
            vec![0, 1, 2, 3, 4, 5]
        )
    }

    #[test]
    fn long_pattern_in_text() {
        let text = vec![65u8; 1e5 as usize];
        let pattern = vec![65u8; 5e4 as usize];

        let mut expected_answer = vec![0usize; (1e5 - 5e4 + 1f64) as usize];
        for (idx, i) in expected_answer.iter_mut().enumerate() {
            *i = idx;
        }
        assert_eq!(
            match_pattern(text.as_slice(), pattern.as_slice()),
            expected_answer
        );
    }
}


use std::collections::HashMap;
use std::env;
use std::io::{Read, Write};
use std::net::TcpStream;

const IMDS_URL: &str = "169.254.169.254:80";

fn request(
    method: &str,
    path: &str,
    headers: HashMap<String, String>,
) -> std::io::Result<(u64, String)> {
    let mut socket = TcpStream::connect(IMDS_URL)?;

    let header = format!(
        "{} /{} HTTP/1.1\r\n{}\r\n",
        method,
        path,
        headers
            .iter()
            .map(|(i, x)| format!("{}: {}\r\n", i, x))
            .collect::<Vec<_>>()
            .join("")
    );
    socket.write(header.as_bytes())?;
    socket.flush()?;

    let mut text = String::new();
    socket
        .read_to_string(&mut text)
        .expect("failed to read response");

    // The text should be delimited by \r\n
    let (response_headers, response_text) = text.split_once("\r\n\r\n").unwrap();

    // We could just extract just the first line, but in case
    // we need to expand this to read header values leaving it as is
    let header_lines: Vec<&str> = response_headers.split("\r\n").collect();

    // The first line will contain the response type
    let response_status: Vec<&str> = header_lines[0].split_whitespace().collect();
    // The important part here is the part 2 status code
    let status_code = response_status[1];

    Ok((
        status_code.parse::<u64>().unwrap(),
        response_text.to_string(),
    ))
}

fn imdsv2_handle(headers: &mut HashMap<String, String>) -> std::io::Result<()> {
    let (status, token) = request(
        "PUT",
        "latest/api/token",
        HashMap::from([(
            "X-aws-ec2-metadata-token-ttl-seconds".to_string(),
            "1".to_string(),
        )]),
    )?;

    if status != 200 {
        return Err(std::io::Error::new(
            std::io::ErrorKind::ConnectionRefused,
            "failed to fetch token",
        ));
    }

    headers.insert("X-aws-ec2-metadata-token".to_string(), token);
    Ok(())
}

fn main() -> std::io::Result<()> {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        eprintln!("imds_get <path>");
        std::process::exit(1);
    }
    let sub_uri = args[1].clone();

    // First let's check if imdsv2 is enabled
    let imdsv2 = match request("GET", "/", HashMap::new()) {
        Ok((status, _)) => status == 401,
        Err(e) => {
            if e.to_string().to_lowercase().contains("Unauthorized") {
                true
            } else {
                false
            }
        }
    };

    let mut headers: HashMap<String, String> = HashMap::new();

    if imdsv2 {
        imdsv2_handle(&mut headers)?;
    }

    let (status_code, text) = request("GET", sub_uri.as_str(), headers)?;
    if status_code == 404 {
        std::process::exit(1);
    }
    print!("{}", text);
    Ok(())
}


use lambda::{lambda, Context};
use serde_json::Value;

// fails to compile because missing semi colon
type Error = Box<dyn std::error::Error + Send + Sync + 'static>

#[tokio::main]
async fn main(event: Value, _: Context) -> Result<Value, Error> {
    Ok(event)
}


// This example requires the following input to succeed:
// { "command": "do something" }

use lambda_runtime::{service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};

/// This is also a made-up example. Requests come into the runtime as unicode
/// strings in json format, which can map to any structure that implements `serde::Deserialize`
/// The runtime pays no attention to the contents of the request payload.
#[derive(Deserialize)]
struct Request {
    command: String,
}

/// This is a made-up example of what a response structure may look like.
/// There is no restriction on what it can be. The runtime requires responses
/// to be serialized into json. The runtime pays no attention
/// to the contents of the response payload.
#[derive(Serialize)]
struct Response {
    req_id: String,
    msg: String,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let func = service_fn(my_handler);
    lambda_runtime::run(func).await?;
    Ok(())
}

pub(crate) async fn my_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    // extract some useful info from the request
    let command = event.payload.command;

    // prepare the response
    let resp = Response {
        req_id: event.context.request_id,
        msg: format!("Command {} executed at {:?}.", command, std::time::SystemTime::now()),
    };

    // return `Response` (it will be serialized to JSON automatically by the runtime)
    Ok(resp)
}


use lambda_runtime::{run, service_fn, Error, LambdaEvent};

use serde::{Deserialize, Serialize};

/// This is a made-up example. Requests come into the runtime as unicode
/// strings in json format, which can map to any structure that implements `serde::Deserialize`
/// The runtime pays no attention to the contents of the request payload.
#[derive(Deserialize)]
struct Request {
    command: String,
}

/// This is a made-up example of what a response structure may look like.
/// There is no restriction on what it can be. The runtime requires responses
/// to be serialized into json. The runtime pays no attention
/// to the contents of the response payload.
#[derive(Serialize)]
struct Response {
    req_id: String,
    msg: String,
}

/// This is the main body for the function.
/// Write your code inside it.
/// There are some code example in the following URLs:
/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples
/// - https://github.com/aws-samples/serverless-rust-demo/
async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    // Extract some useful info from the request
    let command = event.payload.command;

    // Prepare the response
    let resp = Response {
        req_id: event.context.request_id,
        msg: format!("Command {}.", command),
    };

    // Return `Response` (it will be serialized to JSON automatically by the runtime)
    Ok(resp)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        // disable printing the name of the module in every log line.
        .with_target(false)
        // disabling time is handy because CloudWatch will add the ingestion time.
        .without_time()
        .init();

    run(service_fn(function_handler)).await
}


use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};

use aws_sdk_codebuild::{types::BuildBatchFilter, Client};
use lambda_runtime::{service_fn, Error, LambdaEvent};
use serde_json::{json, Value};
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Error> {
    env_logger::init();
    let func = service_fn(handle);
    lambda_runtime::run(func).await?;
    Ok(())
}

async fn handle(_event: LambdaEvent<Value>) -> Result<(), Error> {
    let region_provider =
        aws_config::meta::region::RegionProviderChain::default_provider().or_else("us-west-2");

    let config = aws_config::from_env().region(region_provider).load().await;

    let sm_client = aws_sdk_secretsmanager::Client::new(&config);

    let github_token = retrieve_secret(
        &sm_client,
        std::env::var("GITHUB_TOKEN_SECRET_NAME")
            .map_err(|_| "failed to find github access token secret name")?,
    )
    .await?;

    let github = octocrab::initialise(octocrab::Octocrab::builder().personal_token(github_token))
        .map_err(|e| format!("failed to build github client: {e}"))?;

    let codebuild_client = Client::new(&config);

    let project = std::env::var("CODEBUILD_PROJECT_NAME").unwrap();

    let github_repo_owner = std::env::var("GITHUB_REPO_OWNER").unwrap();

    let mut pull_requests: HashMap<u64, Vec<CommitBuild>> = HashMap::new();

    log::info!("Pulling builds for {project}");

    let builds = get_project_build_batches(&codebuild_client, project.clone())
        .await
        .unwrap();

    let project_pull_requests = gather_pull_request_builds(&codebuild_client, builds).await?;

    for (k, v) in project_pull_requests {
        let mut builds = v;
        pull_requests
            .entry(k)
            .and_modify(|ev| {
                ev.append(&mut builds);
                ev.dedup();
            })
            .or_insert(builds);
    }

    let mut stopped_builds: u64 = 0;

    for (k, v) in pull_requests.iter() {
        if v.len() <= 1 {
            continue;
        }
        let pull = github
            .pulls(&github_repo_owner, "aws-lc")
            .get(*k)
            .await
            .map_err(|e| format!("failed to retrieve GitHub pull requests: {}", e))?;
        let commit: String = pull.head.sha;
        for cb in v.iter() {
            let build_id = cb.build();
            if cb.commit() == commit {
                log::info!("{build_id} pr/{k} at current head({commit})");
                continue;
            }
            let old_commit = cb.commit();
            log::info!("{build_id} pr/{k} at old head({old_commit}) will be canceled");
            codebuild_client
                .stop_build_batch()
                .set_id(Some(String::from(build_id)))
                .send()
                .await
                .map_err(|e| format!("failed to stop_build_batch: {}", e))?;
            stopped_builds += 1;
        }
    }

    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis();

    println!(
        "{}",
        json!({
            "_aws": {
                "CloudWatchMetrics": [{
                    "Namespace": "AWS-LC",
                    "Dimensions": [
                        ["Project"]
                    ],
                    "Metrics": [
                        {
                            "Name": "PrunedGitHubBuilds",
                            "Unit": "Count",
                            "StorageResolution": 60
                        }
                    ]
                }],
                "Timestamp": timestamp
            },
            "Project": &project,
            "PrunedGitHubBuilds": stopped_builds
        })
        .to_string()
    );

    Ok(())
}

async fn retrieve_secret(
    client: &aws_sdk_secretsmanager::Client,
    secret_id: String,
) -> Result<String, String> {
    let github_secret_output = client
        .get_secret_value()
        .set_secret_id(Some(secret_id))
        .send()
        .await
        .map_err(|e| format!("failed to retrieve GitHub secret: {e}"))?;

    if github_secret_output.secret_string().is_none() {
        return Err("retrieved secret did not have content".to_string());
    }

    Ok(String::from(github_secret_output.secret_string().unwrap()))
}

#[derive(PartialEq, Eq)]
struct CommitBuild(String, String);

impl CommitBuild {
    fn new(commit: String, build: String) -> Self {
        Self(commit, build)
    }

    fn commit(&self) -> &str {
        self.0.as_str()
    }

    fn build(&self) -> &str {
        self.1.as_str()
    }
}

async fn gather_pull_request_builds(
    client: &Client,
    builds: Vec<String>,
) -> Result<HashMap<u64, Vec<CommitBuild>>, String> {
    let mut pull_requests: HashMap<u64, Vec<CommitBuild>> = HashMap::new();

    for chunk in builds.chunks(100) {
        if chunk.is_empty() {
            break;
        }

        let batch = client
            .batch_get_build_batches()
            .set_ids(Some(Vec::from(chunk)))
            .send()
            .await;
        if batch.is_err() {
            return Err(format!(
                "failed to get batch details: {}",
                batch.unwrap_err()
            ));
        }

        for bb in batch.unwrap().build_batches().unwrap_or_default() {
            if bb.source().is_none() {
                continue;
            }
            let source = bb.source().unwrap();
            if source.r#type().is_none()
                || source.r#type().unwrap().as_str()
                    != aws_sdk_codebuild::types::SourceType::Github.as_str()
                || bb.source_version().is_none()
                || bb.resolved_source_version().is_none()
            {
                continue;
            }

            let source_version = bb.source_version().unwrap_or("");

            if !source_version.contains("pr/") {
                continue;
            }

            let pr_number = String::from(source_version).replace("pr/", "");
            let commit = String::from(bb.resolved_source_version().unwrap_or_default());
            let build_id = String::from(bb.id().unwrap_or_default());

            if pr_number.is_empty() || commit.is_empty() || build_id.is_empty() {
                continue;
            }

            pull_requests
                .entry(pr_number.parse::<u64>().unwrap())
                .and_modify(|e| e.push(CommitBuild::new(commit.clone(), build_id.clone())))
                .or_insert(vec![CommitBuild::new(commit, build_id)]);
        }
    }

    for (_, v) in pull_requests.iter_mut() {
        v.dedup();
    }

    Ok(pull_requests)
}

async fn get_project_build_batches(
    client: &Client,
    project: String,
) -> Result<Vec<String>, String> {
    let mut builds: Vec<String> = vec![];

    let mut paginator = client
        .list_build_batches_for_project()
        .set_project_name(Some(project))
        .set_filter(Some(
            BuildBatchFilter::builder()
                .set_status(Some(aws_sdk_codebuild::types::StatusType::InProgress))
                .build(),
        ))
        .into_paginator()
        .send();

    while let Some(result) = paginator.next().await {
        if result.is_err() {
            return Err(format!(
                "failed to list_build_batches_for_project: {}",
                result.unwrap_err()
            ));
        }

        let mut ids = Vec::from(result.unwrap().ids().unwrap_or(&[]));

        builds.append(&mut ids);
    }

    Ok(builds)
}


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

use crate::{
    get_aws_lc_fips_sys_includes_path, get_aws_lc_include_path, get_generated_include_path,
    get_rust_include_path,
};
use bindgen::callbacks::{ItemInfo, ParseCallbacks};
use std::fmt::Debug;
use std::path::Path;

#[derive(Debug)]
struct StripPrefixCallback {
    remove_prefix: Option<String>,
}

impl StripPrefixCallback {
    fn new(prefix: &str) -> StripPrefixCallback {
        StripPrefixCallback {
            remove_prefix: Some(prefix.to_string()),
        }
    }
}

impl ParseCallbacks for StripPrefixCallback {
    fn generated_name_override(&self, item_info: ItemInfo<'_>) -> Option<String> {
        self.remove_prefix.as_ref().and_then(|s| {
            let prefix = format!("{s}_");
            item_info
                .name
                .strip_prefix(prefix.as_str())
                .map(String::from)
        })
    }
}

fn prepare_clang_args(manifest_dir: &Path, build_prefix: Option<&str>) -> Vec<String> {
    let mut clang_args: Vec<String> = vec![
        "-I".to_string(),
        get_rust_include_path(manifest_dir).display().to_string(),
        "-I".to_string(),
        get_aws_lc_include_path(manifest_dir).display().to_string(),
    ];

    if let Some(include_paths) = get_aws_lc_fips_sys_includes_path() {
        for path in include_paths {
            clang_args.push("-I".to_string());
            clang_args.push(path.display().to_string());
        }
    }

    if let Some(prefix) = build_prefix {
        clang_args.push(format!("-DBORINGSSL_PREFIX={prefix}"));
        clang_args.push("-I".to_string());
        clang_args.push(
            get_generated_include_path(manifest_dir)
                .display()
                .to_string(),
        );
    }

    clang_args
}

const COPYRIGHT: &str = r#"
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
"#;

const PRELUDE: &str = r#"
#![allow(
    unused_imports,
    non_camel_case_types,
    non_snake_case,
    non_upper_case_globals,
    improper_ctypes,
    clippy::cast_lossless,
    clippy::cast_possible_truncation,
    clippy::default_trait_access,
    clippy::must_use_candidate,
    clippy::not_unsafe_ptr_arg_deref,
    clippy::ptr_as_ptr,
    clippy::semicolon_if_nothing_returned,
    clippy::too_many_lines,
    clippy::unreadable_literal,
    clippy::used_underscore_binding,
    clippy::useless_transmute
)]
"#;

#[derive(Default)]
pub(crate) struct BindingOptions<'a> {
    pub build_prefix: Option<&'a str>,
    pub include_ssl: bool,
    pub disable_prelude: bool,
}

fn prepare_bindings_builder(manifest_dir: &Path, options: &BindingOptions<'_>) -> bindgen::Builder {
    let clang_args = prepare_clang_args(manifest_dir, options.build_prefix);

    let mut builder = bindgen::Builder::default()
        .derive_copy(true)
        .derive_debug(true)
        .derive_default(true)
        .derive_eq(true)
        .allowlist_file(r".*(/|\\)openssl(/|\\)[^/\\]+\.h")
        .allowlist_file(r".*(/|\\)rust_wrapper\.h")
        .rustified_enum(r"point_conversion_form_t")
        .default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
        .generate_comments(true)
        .fit_macro_constants(false)
        .size_t_is_usize(true)
        .layout_tests(true)
        .prepend_enum_name(true)
        .formatter(bindgen::Formatter::Rustfmt)
        .clang_args(clang_args)
        .raw_line(COPYRIGHT)
        .header(
            get_rust_include_path(manifest_dir)
                .join("rust_wrapper.h")
                .display()
                .to_string(),
        );

    if !options.disable_prelude {
        builder = builder.raw_line(PRELUDE);
    }

    if options.include_ssl {
        builder = builder.clang_arg("-DAWS_LC_RUST_INCLUDE_SSL");
    }

    if let Some(ps) = &options.build_prefix {
        builder = builder.parse_callbacks(Box::new(StripPrefixCallback::new(ps)));
    }

    builder
}

pub(crate) fn generate_bindings(
    manifest_dir: &Path,
    options: &BindingOptions<'_>,
) -> bindgen::Bindings {
    prepare_bindings_builder(manifest_dir, options)
        .generate()
        .expect("Unable to generate bindings.")
}


// Copyright (c) 2022, Google Inc.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#[cfg(feature = "bindgen")]
use std::default::Default;
use std::env;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;

#[cfg(any(
    feature = "bindgen",
    not(any(
        all(target_os = "linux", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64")
    ))
))]
mod bindgen;

pub(crate) fn get_aws_lc_include_path(manifest_dir: &Path) -> PathBuf {
    manifest_dir.join("aws-lc").join("include")
}

pub(crate) fn get_rust_include_path(manifest_dir: &Path) -> PathBuf {
    manifest_dir.join("include")
}

pub(crate) fn get_generated_include_path(manifest_dir: &Path) -> PathBuf {
    manifest_dir.join("generated-include")
}

pub(crate) fn get_aws_lc_fips_sys_includes_path() -> Option<Vec<PathBuf>> {
    env::var("AWS_LC_FIPS_SYS_INCLUDES")
        .map(|colon_delim_paths| colon_delim_paths.split(':').map(PathBuf::from).collect())
        .ok()
}

#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum OutputLib {
    RustWrapper,
    Crypto,
    Ssl,
}

#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum OutputLibType {
    Static,
    Dynamic,
}

impl Default for OutputLibType {
    fn default() -> Self {
        let build_type_result = env::var("AWS_LC_FIPS_SYS_STATIC");
        if let Ok(build_type) = build_type_result {
            eprintln!("AWS_LC_FIPS_SYS_STATIC={build_type}");
            // If the environment variable is set, we ignore every other factor.
            let build_type = build_type.to_lowercase();
            if build_type.starts_with('0')
                || build_type.starts_with('n')
                || build_type.starts_with("off")
            {
                // Only dynamic if the value is set and is a "negative" value
                return OutputLibType::Dynamic;
            }

            return OutputLibType::Static;
        }

        let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
        let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();

        if target_os == "linux" && (target_arch == "x86_64" || target_arch == "aarch64") {
            OutputLibType::Static
        } else {
            OutputLibType::Dynamic
        }
    }
}

impl OutputLibType {
    fn rust_lib_type(&self) -> &str {
        match self {
            OutputLibType::Static => "static",
            OutputLibType::Dynamic => "dylib",
        }
    }
}

impl OutputLib {
    fn libname(self, prefix: Option<&str>) -> String {
        format!(
            "{}{}",
            if let Some(pfix) = prefix { pfix } else { "" },
            match self {
                OutputLib::Crypto => "crypto",
                OutputLib::Ssl => "ssl",
                OutputLib::RustWrapper => {
                    "rust_wrapper"
                }
            }
        )
    }
}

fn artifact_output_dir(path: &Path) -> PathBuf {
    path.join("build")
        .join("artifacts")
        .join(get_platform_output_path())
}

fn get_platform_output_path() -> PathBuf {
    PathBuf::new()
}

const VERSION: &str = env!("CARGO_PKG_VERSION");

fn prefix_string() -> String {
    format!("aws_lc_fips_{}", VERSION.to_string().replace('.', "_"))
}

fn test_perl_command() -> bool {
    test_command("perl".as_ref(), &["--version".as_ref()])
}

fn test_go_command() -> bool {
    test_command("go".as_ref(), &["version".as_ref()])
}

#[cfg(feature = "bindgen")]
fn target_platform_prefix(name: &str) -> String {
    format!("{}_{}_{}", env::consts::OS, env::consts::ARCH, name)
}

fn test_command(executable: &OsStr, args: &[&OsStr]) -> bool {
    if let Ok(output) = Command::new(executable).args(args).output() {
        return output.status.success();
    }
    false
}

fn find_cmake_command() -> Option<&'static OsStr> {
    if test_command("cmake3".as_ref(), &["--version".as_ref()]) {
        Some("cmake3".as_ref())
    } else if test_command("cmake".as_ref(), &["--version".as_ref()]) {
        Some("cmake".as_ref())
    } else {
        None
    }
}

fn get_cmake_config(manifest_dir: &PathBuf) -> cmake::Config {
    cmake::Config::new(manifest_dir)
}

fn prepare_cmake_build(manifest_dir: &PathBuf, build_prefix: Option<&str>) -> cmake::Config {
    let mut cmake_cfg = get_cmake_config(manifest_dir);

    if OutputLibType::default() == OutputLibType::Dynamic {
        cmake_cfg.define("BUILD_SHARED_LIBS", "1");
    } else {
        cmake_cfg.define("BUILD_SHARED_LIBS", "0");
    }

    let opt_level = env::var("OPT_LEVEL").unwrap_or_else(|_| "0".to_string());
    if opt_level.ne("0") {
        if opt_level.eq("1") || opt_level.eq("2") {
            cmake_cfg.define("CMAKE_BUILD_TYPE", "relwithdebinfo");
        } else {
            cmake_cfg.define("CMAKE_BUILD_TYPE", "release");
        }
    }

    if let Some(symbol_prefix) = build_prefix {
        cmake_cfg.define("BORINGSSL_PREFIX", symbol_prefix);
        let include_path = manifest_dir.join("generated-include");
        cmake_cfg.define(
            "BORINGSSL_PREFIX_HEADERS",
            include_path.display().to_string(),
        );
    }

    // Build flags that minimize our crate size.
    cmake_cfg.define("BUILD_TESTING", "OFF");
    if cfg!(feature = "ssl") {
        cmake_cfg.define("BUILD_LIBSSL", "ON");
    } else {
        cmake_cfg.define("BUILD_LIBSSL", "OFF");
    }
    cmake_cfg.define("FIPS", "1");

    if cfg!(feature = "asan") {
        env::set_var("CC", "/usr/bin/clang");
        env::set_var("CXX", "/usr/bin/clang++");
        env::set_var("ASM", "/usr/bin/clang");

        cmake_cfg.define("ASAN", "1");
    }

    cmake_cfg
}

fn build_rust_wrapper(manifest_dir: &PathBuf) -> PathBuf {
    prepare_cmake_build(manifest_dir, Some(&prefix_string()))
        .configure_arg("--no-warn-unused-cli")
        .build()
}

#[cfg(any(
    feature = "bindgen",
    not(any(
        all(target_os = "linux", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64")
    ))
))]
fn generate_bindings(manifest_dir: &Path, prefix: &str, bindings_path: &PathBuf) {
    let options = bindgen::BindingOptions {
        build_prefix: Some(prefix),
        include_ssl: cfg!(feature = "ssl"),
        disable_prelude: true,
    };

    let bindings = bindgen::generate_bindings(manifest_dir, &options);

    bindings
        .write(Box::new(std::fs::File::create(bindings_path).unwrap()))
        .expect("written bindings");
}

#[cfg(feature = "bindgen")]
fn generate_src_bindings(manifest_dir: &Path, prefix: &str, src_bindings_path: &Path) {
    bindgen::generate_bindings(
        manifest_dir,
        &bindgen::BindingOptions {
            build_prefix: Some(prefix),
            include_ssl: false,
            ..Default::default()
        },
    )
    .write_to_file(src_bindings_path.join(format!("{}.rs", target_platform_prefix("crypto"))))
    .expect("write bindings");

    bindgen::generate_bindings(
        manifest_dir,
        &bindgen::BindingOptions {
            build_prefix: Some(prefix),
            include_ssl: true,
            ..Default::default()
        },
    )
    .write_to_file(src_bindings_path.join(format!("{}.rs", target_platform_prefix("crypto_ssl"))))
    .expect("write bindings");
}

fn emit_rustc_cfg(cfg: &str) {
    println!("cargo:rustc-cfg={cfg}");
}

macro_rules! cfg_bindgen_platform {
    ($binding:ident, $os:literal, $arch:literal, $additional:expr) => {
        let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
        let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
        let $binding = {
            (target_os == $os && target_arch == $arch && $additional)
                .then(|| {
                    emit_rustc_cfg(concat!($os, "_", $arch));
                    true
                })
                .unwrap_or(false)
        };
    };
}

fn main() {
    use crate::OutputLib::{Crypto, RustWrapper, Ssl};

    let output_lib_type = OutputLibType::default();

    let mut is_bindgen_required = cfg!(feature = "bindgen");

    let is_internal_generate = env::var("AWS_LC_RUST_INTERNAL_BINDGEN")
        .unwrap_or_else(|_| String::from("0"))
        .eq("1");

    let pregenerated = !is_bindgen_required || is_internal_generate;

    cfg_bindgen_platform!(linux_x86_64, "linux", "x86_64", pregenerated);
    cfg_bindgen_platform!(linux_aarch64, "linux", "aarch64", pregenerated);

    if !(linux_x86_64 || linux_aarch64) {
        emit_rustc_cfg("use_bindgen_generated");
        is_bindgen_required = true;
    }

    check_dependencies();

    let manifest_dir = env::current_dir().unwrap();
    let manifest_dir = dunce::canonicalize(Path::new(&manifest_dir)).unwrap();
    let prefix = prefix_string();

    let out_dir = build_rust_wrapper(&manifest_dir);

    #[allow(unused_assignments)]
    let mut bindings_available = false;
    if is_internal_generate {
        #[cfg(feature = "bindgen")]
        {
            let src_bindings_path = Path::new(&manifest_dir).join("src");
            generate_src_bindings(&manifest_dir, &prefix, &src_bindings_path);
            bindings_available = true;
        }
    } else if is_bindgen_required {
        #[cfg(any(
            feature = "bindgen",
            not(any(
                all(target_os = "linux", target_arch = "x86_64"),
                all(target_os = "linux", target_arch = "aarch64")
            ))
        ))]
        {
            let gen_bindings_path = Path::new(&env::var("OUT_DIR").unwrap()).join("bindings.rs");
            generate_bindings(&manifest_dir, &prefix, &gen_bindings_path);
            bindings_available = true;
        }
    } else {
        bindings_available = true;
    }

    assert!(
        bindings_available,
        "aws-lc-fip-sys build failed. Please enable the 'bindgen' feature on aws-lc-rs or aws-lc-fips-sys"
    );

    println!(
        "cargo:rustc-link-search=native={}",
        artifact_output_dir(&out_dir).display()
    );

    println!(
        "cargo:rustc-link-lib={}={}",
        output_lib_type.rust_lib_type(),
        Crypto.libname(Some(&prefix))
    );

    if cfg!(feature = "ssl") {
        println!(
            "cargo:rustc-link-lib={}={}",
            output_lib_type.rust_lib_type(),
            Ssl.libname(Some(&prefix))
        );
    }

    println!(
        "cargo:rustc-link-lib={}={}",
        output_lib_type.rust_lib_type(),
        RustWrapper.libname(Some(&prefix))
    );

    for include_path in vec![
        get_rust_include_path(&manifest_dir),
        get_generated_include_path(&manifest_dir),
        get_aws_lc_include_path(&manifest_dir),
    ] {
        println!("cargo:include={}", include_path.display());
    }
    if let Some(include_paths) = get_aws_lc_fips_sys_includes_path() {
        for path in include_paths {
            println!("cargo:include={}", path.display());
        }
    }

    println!("cargo:rerun-if-changed=builder/");
    println!("cargo:rerun-if-changed=aws-lc/");
    println!("cargo:rerun-if-env-changed=AWS_LC_FIPS_SYS_STATIC");
}

fn check_dependencies() {
    let mut missing_dependency = false;
    if !test_go_command() {
        eprintln!("Missing dependency: Go is required for FIPS.");
        missing_dependency = true;
    }
    if !test_perl_command() {
        eprintln!("Missing dependency: perl is required for FIPS.");
        missing_dependency = true;
    }
    if let Some(cmake_cmd) = find_cmake_command() {
        env::set_var("CMAKE", cmake_cmd);
    } else {
        eprintln!("Missing dependency: cmake");
        missing_dependency = true;
    };

    assert!(
        !missing_dependency,
        "Required build dependency is missing. Halting build."
    );
}


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

use paste::paste;
use std::os::raw::{c_char, c_long, c_void};

#[allow(unused_macros)]
macro_rules! use_bindings {
    ($bindings:ident) => {
        mod $bindings;
        pub use $bindings::*;
    };
}

macro_rules! platform_binding {
    ($platform:ident) => {
        paste! {
            #[cfg(all($platform, not(feature = "ssl"), not(use_bindgen_generated)))]
            use_bindings!([< $platform _crypto >]);

            #[cfg(all($platform, feature = "ssl", not(use_bindgen_generated)))]
            use_bindings!([< $platform _crypto_ssl >]);
        }
    };
}

platform_binding!(linux_x86_64);

platform_binding!(linux_aarch64);

#[cfg(use_bindgen_generated)]
mod generated {
    #![allow(
        unused_imports,
        non_camel_case_types,
        non_snake_case,
        non_upper_case_globals,
        improper_ctypes,
        clippy::cast_lossless,
        clippy::cast_possible_truncation,
        clippy::default_trait_access,
        clippy::must_use_candidate,
        clippy::not_unsafe_ptr_arg_deref,
        clippy::ptr_as_ptr,
        clippy::semicolon_if_nothing_returned,
        clippy::too_many_lines,
        clippy::unreadable_literal,
        clippy::used_underscore_binding,
        clippy::useless_transmute
    )]

    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
#[cfg(use_bindgen_generated)]
pub use generated::*;

#[allow(non_snake_case)]
#[must_use]
pub fn ERR_GET_LIB(packed_error: u32) -> i32 {
    unsafe { ERR_GET_LIB_RUST(packed_error) }
}

#[allow(non_snake_case)]
#[must_use]
pub fn ERR_GET_REASON(packed_error: u32) -> i32 {
    unsafe { ERR_GET_REASON_RUST(packed_error) }
}

#[allow(non_snake_case)]
#[must_use]
pub fn ERR_GET_FUNC(packed_error: u32) -> i32 {
    unsafe { ERR_GET_FUNC_RUST(packed_error) }
}

#[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
pub fn BIO_get_mem_data(b: *mut BIO, pp: *mut *mut c_char) -> c_long {
    unsafe { BIO_ctrl(b, BIO_CTRL_INFO, 0, pp.cast::<c_void>()) }
}

pub fn init() {
    unsafe { CRYPTO_library_init() }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
use aws_lc_fips_sys as aws_lc_sys;

#[test]
fn test_fips_mode() {
    unsafe {
        assert_eq!(aws_lc_sys::FIPS_mode(), 1);
    }
}

#[test]
fn error_checking() {
    unsafe {
        let error = aws_lc_sys::ERR_get_error();
        let err_lib = aws_lc_sys::ERR_GET_LIB(error);
        let err_reason = aws_lc_sys::ERR_GET_REASON(error);
        let err_func = aws_lc_sys::ERR_GET_FUNC(error);
        assert_eq!(err_lib, 0);
        assert_eq!(err_reason, 0);
        assert_eq!(err_func, 0);
    }
}


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

fn main() {
    let mutually_exclusives_count = vec![cfg!(feature = "non-fips"), cfg!(feature = "fips")]
        .iter()
        .filter(|x| **x)
        .count();

    if mutually_exclusives_count > 1 {
        eprint!("fips and non-fips are mutually exclusive crate features.");
        std::process::exit(1);
    }

    // This appears asymmetric, but it reflects the `cfg` statements in lib.rs that
    // require `aws-lc-sys` to be present when "fips" is not enabled.
    let at_least_one_count = vec![cfg!(feature = "aws-lc-sys"), cfg!(feature = "fips")]
        .iter()
        .filter(|x| **x)
        .count();

    if at_least_one_count < 1 {
        eprint!(
            "one of the following features must be specified: \
        aws-lc-sys, non-fips, or fips."
        );
        std::process::exit(1);
    }
}


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

use aws_lc_rs::{test, test_file};
use criterion::{criterion_group, criterion_main, Criterion};

#[derive(Debug)]
pub enum AeadAlgorithm {
    Aes128Gcm,
    Aes256Gcm,
    Chacha20Poly1305,
}

pub struct AeadConfig {
    algorithm: AeadAlgorithm,
    key: Vec<u8>,
    nonce: Vec<u8>,
    aad: String,
    in_out: Vec<u8>,
}

impl AeadConfig {
    #[must_use]
    pub fn new(
        algorithm: AeadAlgorithm,
        key: &[u8],
        nonce: &[u8],
        aad: &str,
        in_out: &[u8],
    ) -> AeadConfig {
        AeadConfig {
            algorithm,
            key: Vec::from(key),
            nonce: Vec::from(nonce),
            aad: String::from(aad),
            in_out: Vec::from(in_out),
        }
    }
}
macro_rules! benchmark_aead
{( $pkg:ident ) =>
{
    paste::item! {
mod [<$pkg _benchmarks>]  {

    use $pkg::{aead, error};

    use criterion::black_box;
    use crate::AeadConfig;
    use aead::{
        Aad, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, Tag, UnboundKey,
    };
    use error::Unspecified;

    struct NotANonce(Vec<u8>);

    impl NotANonce {
        fn from(value: Vec<u8>) -> Self {
            NotANonce(value)
        }
    }

    impl NonceSequence for NotANonce {
        fn advance(&mut self) -> Result<Nonce, Unspecified> {
            let mut nonce = [0u8; aead::NONCE_LEN];
            nonce.copy_from_slice(&self.0[0..aead::NONCE_LEN]);
            Ok(Nonce::assume_unique_for_key(nonce))
        }
    }

    fn algorithm(config: &crate::AeadConfig) ->  &'static aead::Algorithm {
        black_box(match &config.algorithm {
            crate::AeadAlgorithm::Aes128Gcm => &aead::AES_128_GCM,
            crate::AeadAlgorithm::Aes256Gcm => &aead::AES_256_GCM,
            crate::AeadAlgorithm::Chacha20Poly1305 => &aead::CHACHA20_POLY1305,
        })
    }

    fn key(config: &crate::AeadConfig) -> UnboundKey {
       black_box(UnboundKey::new(algorithm(config), &config.key).unwrap())
    }

    fn nonce(config: &crate::AeadConfig) -> impl NonceSequence {
       black_box(NotANonce::from(config.nonce.clone()))
    }

    pub fn aad(config: &crate::AeadConfig) -> Aad<String> {
        black_box(Aad::from(config.aad.clone()))
    }

    pub fn create_sealing_key(config: &AeadConfig) -> SealingKey<impl NonceSequence> {
        SealingKey::new(key(&config), nonce(&config))
    }

    pub fn create_opening_key(config: &AeadConfig) -> OpeningKey<impl NonceSequence> {
        OpeningKey::new(key(&config), nonce(&config))
    }

    pub fn seal_separate(sealing_key: &mut SealingKey<impl NonceSequence>, aad: Aad<String>, in_out: &mut [u8]) -> Tag {
       sealing_key
            .seal_in_place_separate_tag(aad, in_out)
            .map_err(|x| x.to_string()).unwrap()
    }

    pub fn seal_append(sealing_key: &mut SealingKey<impl NonceSequence>, aad: Aad<String>, in_out: &mut Vec<u8>) {
       sealing_key
            .seal_in_place_append_tag(aad, in_out)
            .map_err(|x| x.to_string()).unwrap()
    }

    pub fn open(opening_key: &mut OpeningKey<impl NonceSequence>, aad: Aad<String>, in_out: &mut [u8]) {
       opening_key
            .open_in_place(aad, in_out)
            .map_err(|x| x.to_string()).unwrap();
    }

}
}}}

benchmark_aead!(aws_lc_rs);
#[cfg(feature = "ring-benchmarks")]
benchmark_aead!(ring);

fn test_aes_128_gcm(c: &mut Criterion) {
    test::run(
        test_file!("data/aead_aes_128_gcm_benchmarks.txt"),
        |_section, test_case| {
            let config = AeadConfig::new(
                AeadAlgorithm::Aes128Gcm,
                test_case.consume_bytes("KEY").as_slice(),
                test_case.consume_bytes("NONCE").as_slice(),
                test_case.consume_string("AD").as_str(),
                test_case.consume_bytes("IN").as_slice(),
            );
            test_aead_separate(c, &config);
            test_aead_append(c, &config);
            test_aead_open(c, &config);
            Ok(())
        },
    );
}

fn test_aes_256_gcm(c: &mut Criterion) {
    test::run(
        test_file!("data/aead_aes_256_gcm_benchmarks.txt"),
        |_section, test_case| {
            let config = AeadConfig::new(
                AeadAlgorithm::Aes256Gcm,
                test_case.consume_bytes("KEY").as_slice(),
                test_case.consume_bytes("NONCE").as_slice(),
                test_case.consume_string("AD").as_str(),
                test_case.consume_bytes("IN").as_slice(),
            );
            test_aead_separate(c, &config);
            test_aead_append(c, &config);
            test_aead_open(c, &config);
            Ok(())
        },
    );
}

fn test_chacha20(c: &mut Criterion) {
    test::run(
        test_file!("data/aead_chacha20_poly1305_benchmarks.txt"),
        |_section, test_case| {
            let config = AeadConfig::new(
                AeadAlgorithm::Chacha20Poly1305,
                test_case.consume_bytes("KEY").as_slice(),
                test_case.consume_bytes("NONCE").as_slice(),
                test_case.consume_string("AD").as_str(),
                test_case.consume_bytes("IN").as_slice(),
            );
            test_aead_separate(c, &config);
            test_aead_append(c, &config);
            test_aead_open(c, &config);
            Ok(())
        },
    );
}

fn test_aead_separate(c: &mut Criterion, config: &AeadConfig) {
    let mut in_out = config.in_out.clone();

    let bench_group_name = format!(
        "AEAD-{:?}-separate-{}-bytes",
        config.algorithm,
        in_out.len()
    );
    let mut group = c.benchmark_group(bench_group_name);

    let mut aws_sealing_key = aws_lc_rs_benchmarks::create_sealing_key(config);
    group.bench_function("AWS-LC", |b| {
        b.iter(|| {
            let aws_aad = aws_lc_rs_benchmarks::aad(config);
            let _tag =
                aws_lc_rs_benchmarks::seal_separate(&mut aws_sealing_key, aws_aad, &mut in_out);
        });
    });

    #[cfg(feature = "ring-benchmarks")]
    {
        let mut ring_sealing_key = ring_benchmarks::create_sealing_key(config);
        group.bench_function("Ring", |b| {
            b.iter(|| {
                let ring_aad = ring_benchmarks::aad(config);
                let _tag =
                    ring_benchmarks::seal_separate(&mut ring_sealing_key, ring_aad, &mut in_out);
            });
        });
    }
}

fn test_aead_append(c: &mut Criterion, config: &AeadConfig) {
    let in_out = config.in_out.clone();

    let bench_group_name = format!("AEAD-{:?}-append-{}-bytes", config.algorithm, in_out.len());
    let mut group = c.benchmark_group(bench_group_name);

    let mut aws_sealing_key = aws_lc_rs_benchmarks::create_sealing_key(config);
    group.bench_function("AWS-LC", |b| {
        b.iter(|| {
            let mut aws_in_out = in_out.clone();
            let aws_aad = aws_lc_rs_benchmarks::aad(config);
            aws_lc_rs_benchmarks::seal_append(&mut aws_sealing_key, aws_aad, &mut aws_in_out);
        });
    });

    #[cfg(feature = "ring-benchmarks")]
    {
        let mut ring_sealing_key = ring_benchmarks::create_sealing_key(config);
        group.bench_function("Ring", |b| {
            b.iter(|| {
                let mut ring_in_out = in_out.clone();
                let ring_aad = ring_benchmarks::aad(config);
                ring_benchmarks::seal_append(&mut ring_sealing_key, ring_aad, &mut ring_in_out);
            });
        });
    }
}

fn test_aead_open(c: &mut Criterion, config: &AeadConfig) {
    let mut in_out = config.in_out.clone();

    let aws_aad = aws_lc_rs_benchmarks::aad(config);
    let mut aws_sealing_key = aws_lc_rs_benchmarks::create_sealing_key(config);
    aws_lc_rs_benchmarks::seal_append(&mut aws_sealing_key, aws_aad, &mut in_out);

    let bench_group_name = format!("AEAD-{:?}-open-{}-bytes", config.algorithm, in_out.len());
    let mut group = c.benchmark_group(bench_group_name);

    let mut aws_opening_key = aws_lc_rs_benchmarks::create_opening_key(config);
    group.bench_function("AWS-LC", |b| {
        b.iter(|| {
            let mut aws_in_out = in_out.clone();
            let aws_aad = aws_lc_rs_benchmarks::aad(config);
            aws_lc_rs_benchmarks::open(&mut aws_opening_key, aws_aad, &mut aws_in_out);
        });
    });

    #[cfg(feature = "ring-benchmarks")]
    {
        let mut ring_opening_key = ring_benchmarks::create_opening_key(config);
        group.bench_function("Ring", |b| {
            b.iter(|| {
                let mut ring_in_out = in_out.clone();
                let ring_aad = ring_benchmarks::aad(config);
                ring_benchmarks::open(&mut ring_opening_key, ring_aad, &mut ring_in_out);
            });
        });
    }
}

criterion_group!(benches, test_aes_128_gcm, test_aes_256_gcm, test_chacha20,);
criterion_main!(benches);