use std::collections::{btree_map::Entry, BTreeMap};
use crate::moonblade::parser::Expr;
use super::DynamicValue;
#[derive(Debug, PartialEq, Clone)]
pub enum ColumIndexationBy {
Name(String),
NameAndNth((String, usize)),
Pos(usize),
ReversePos(usize),
}
impl ColumIndexationBy {
pub fn from_arguments(arguments: &[&Expr]) -> Option<Self> {
if arguments.len() == 1 {
let first_arg = arguments.first().unwrap();
match first_arg {
Expr::Str(column_name) => Some(Self::Name(column_name.clone())),
Expr::Float(_) | Expr::Int(_) => first_arg.try_to_isize().map(|i| {
if i >= 0 {
Self::Pos(i as usize)
} else {
Self::ReversePos(i.unsigned_abs())
}
}),
_ => None,
}
} else if arguments.len() == 2 {
match arguments.first().unwrap() {
Expr::Str(column_name) => {
let second_arg = arguments.get(1).unwrap();
second_arg.try_to_usize().map(|column_index| {
Self::NameAndNth((column_name.to_string(), column_index))
})
}
_ => None,
}
} else {
None
}
}
pub fn from_argument(argument: &Expr) -> Option<Self> {
Self::from_arguments(&[argument])
}
pub fn from_bound_arguments(
name_or_pos: DynamicValue,
pos: Option<DynamicValue>,
) -> Option<Self> {
if let Some(pos_value) = pos {
match pos_value.try_as_usize() {
Err(_) => None,
Ok(i) => match name_or_pos.try_as_str() {
Err(_) => None,
Ok(name) => Some(Self::NameAndNth((name.into_owned(), i))),
},
}
} else {
match name_or_pos.try_as_i64() {
Err(_) => match name_or_pos.try_as_str() {
Err(_) => None,
Ok(name) => Some(Self::Name(name.into_owned())),
},
Ok(i) => Some(if i < 0 {
Self::ReversePos(i.unsigned_abs() as usize)
} else {
Self::Pos(i as usize)
}),
}
}
}
pub fn find_column_index<'a>(
&self,
headers: impl IntoIterator<Item = &'a [u8]>,
len: usize,
) -> Option<usize> {
match self {
Self::Pos(i) => {
if i >= &len {
None
} else {
Some(*i)
}
}
Self::ReversePos(i) => {
if *i > len {
None
} else {
Some(len - i)
}
}
Self::Name(name) => {
let name_bytes = name.as_bytes();
for (i, cell) in headers.into_iter().enumerate() {
if cell == name_bytes {
return Some(i);
}
}
None
}
Self::NameAndNth((name, pos)) => {
let mut c = *pos;
let name_bytes = name.as_bytes();
for (i, cell) in headers.into_iter().enumerate() {
if cell == name_bytes {
if c == 0 {
return Some(i);
}
c -= 1;
}
}
None
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct HeadersIndex {
mapping: BTreeMap<String, Vec<usize>>,
}
impl HeadersIndex {
pub fn new() -> Self {
HeadersIndex {
mapping: BTreeMap::new(),
}
}
pub fn from_headers<'a>(headers: impl IntoIterator<Item = &'a [u8]>) -> Self {
let mut index = Self::new();
for (i, header) in headers.into_iter().enumerate() {
let key = std::str::from_utf8(header).unwrap().to_string();
match index.mapping.entry(key) {
Entry::Vacant(entry) => {
let positions: Vec<usize> = vec![i];
entry.insert(positions);
}
Entry::Occupied(mut entry) => {
entry.get_mut().push(i);
}
}
}
index
}
pub fn get(&self, indexation: &ColumIndexationBy) -> Option<usize> {
match indexation {
ColumIndexationBy::Name(name) => self
.mapping
.get(name)
.and_then(|positions| positions.first())
.copied(),
ColumIndexationBy::Pos(pos) => {
if *pos >= self.mapping.len() {
None
} else {
Some(*pos)
}
}
ColumIndexationBy::ReversePos(pos) => {
if *pos > self.mapping.len() {
None
} else {
Some(self.mapping.len() - pos)
}
}
ColumIndexationBy::NameAndNth((name, pos)) => self
.mapping
.get(name)
.and_then(|positions| positions.get(*pos))
.copied(),
}
}
}