use std::cmp::Ordering::*;

use crate::IVec;

pub(crate) fn binary_search_lub<'a, T>(
    key: &[u8],
    s: &'a [(IVec, T)],
) -> Option<usize> {
    match binary_search(key, s) {
        Ok(i) => Some(i),
        Err(i) if i == 0 => None,
        Err(i) => Some(i - 1),
    }
}

pub fn binary_search<'a, T>(
    key: &[u8],
    s: &'a [(IVec, T)],
) -> Result<usize, usize> {
    let mut size = s.len();
    if size == 0 || *key < *s[0].0 {
        return Err(0);
    }
    let mut base = 0_usize;
    while size > 1 {
        let half = size / 2;
        let mid = base + half;
        // mid is always in [0, size), that means mid is >= 0 and < size.
        // mid >= 0: by definition
        // mid < size: mid = size / 2 + size / 4 + size / 8 ...
        #[allow(unsafe_code)]
        let cmp = unsafe { s.get_unchecked(mid).0.as_ref().cmp(key) };
        base = if cmp == Greater { base } else { mid };
        size -= half;
    }
    // base is always in [0, size) because base <= mid.
    #[allow(unsafe_code)]
    let cmp = unsafe { s.get_unchecked(base).0.as_ref().cmp(key) };
    if cmp == Equal {
        Ok(base)
    } else {
        Err(base + (cmp == Less) as usize)
    }
}

#[test]
fn test_binary_search_lub() {
    let s = vec![
        (vec![4].into(), ()),
        (vec![5].into(), ()),
        (vec![5].into(), ()),
        (vec![6].into(), ()),
        (vec![9].into(), ()),
    ];
    assert_eq!(binary_search_lub(&[3], &*s), None);
    assert_eq!(binary_search_lub(&[4], &*s), Some(0));
    assert_eq!(binary_search_lub(&[5], &*s), Some(2));
    assert_eq!(binary_search_lub(&[6], &*s), Some(3));
    assert_eq!(binary_search_lub(&[7], &*s), Some(3));
    assert_eq!(binary_search_lub(&[8], &*s), Some(3));
    assert_eq!(binary_search_lub(&[9], &*s), Some(4));
    assert_eq!(binary_search_lub(&[10], &*s), Some(4));

    let mut s = s;
    s.clear();
    assert_eq!(binary_search_lub(&[8], &*s), None);
}
