Documentation
use crate::BsdlError;

/// strip whitespace then 'c' then any more whitespace from the beginning of a string
/// return an error if 'c' don't exist
pub fn strip_prefix<'a>(s: &'a str, c: char) -> Result<&'a str, BsdlError> {
    let s = s.trim_start();
    let s = s
        .strip_prefix(c)
        .ok_or_else(|| BsdlError::MissingCharError(format!("expected {}", c)))?;
    let s = s.trim_start();
    Ok(s)
}

/// strip whitespace then outer () the any more whitespace from a string
/// return an error if they don't exist
pub fn strip_parenthesis<'a>(s: &'a str) -> Result<&'a str, BsdlError> {
    let s = s.trim();
    let s = s.strip_prefix('(').ok_or(BsdlError::ParenthesisError)?;
    let s = s.strip_suffix(')').ok_or(BsdlError::ParenthesisError)?;
    let s = s.trim();
    Ok(s)
}

/// strip leading whitespace then return first string in () then any remaining
/// return an error if there is no string in ()
/// first non-whitespace char must be '('
pub fn split_parenthesis<'a>(s: &'a str) -> Result<(&'a str, &'a str), BsdlError> {
    let s = s.trim_start();
    if !s.starts_with('(') {
        return Err(BsdlError::ParenthesisError);
    }
    let mut open_paren = 1;
    let s = &s[1..];
    for (i, c) in s.chars().enumerate() {
        if c == ')' {
            open_paren -= 1;
            if open_paren == 0 {
                return Ok((&s[..i], &s[i + 1..]));
            }
        }
        if c == '(' {
            open_paren += 1;
        }
    }
    let close = s.find(')').ok_or(BsdlError::ParenthesisError)?;
    Ok((&s[1..close], &s[close + 1..]))
}

#[cfg(test)]
mod test_split_parenthesis {
    use super::*;
    #[test]
    fn test_split_parenthesis() {
        let (a, b) = split_parenthesis(" (a,(b)) c,d () ").unwrap();
        println!("{a}, {b}");
        assert!(a == "a,(b)");
        assert!(b == " c,d () ");
        let (a, b) = split_parenthesis(" (a,b)").unwrap();
        assert!(a == "a,b");
        assert!(b == "");
        split_parenthesis(" no )").expect_err("missing open paren");
    }
}

/// strip whitespace then outer "" the any more whitespace from a string
/// return an error if they don't exist
pub fn strip_quotes<'a>(s: &'a str) -> Result<&'a str, BsdlError> {
    let s = s.trim();
    let s = s.strip_prefix('"').ok_or(BsdlError::QuotesMissingError)?;
    let s = s.strip_suffix('"').ok_or(BsdlError::QuotesMissingError)?;
    let s = s.trim();
    Ok(s)
}

// strip comments then any leading or trailing whitespace from a line
// aware of quotes to avoid false comment detection
pub fn strip_whitespace_comments(buf: &str) -> &str {
    let mut buf = buf;
    let mut c_prev = ' ';
    let mut in_quote = false;
    let mut comment = buf.len();
    for (i, c) in buf.chars().enumerate() {
        if in_quote {
            in_quote = c != '"';
            continue;
        }
        if (c == '-') & (c_prev == '-') {
            comment = i - 1;
            break;
        }
        if c == '"' {
            in_quote = true;
        }
        c_prev = c;
    }
    buf = &buf[0..comment];
    buf.trim()
}

pub fn find_first_comma_outside_paren(buf: &str) -> Result<Option<usize>, BsdlError> {
    let mut open_paren = 0;
    for (i, c) in buf.chars().enumerate() {
        if (open_paren == 0) & (c == ',') {
            return Ok(Some(i));
        }
        if c == '(' {
            open_paren += 1;
        }
        if c == ')' {
            if open_paren == 0 {
                return Err(BsdlError::ParenthesisError);
            }
            open_paren -= 1;
        }
    }
    Ok(None)
}

pub fn split_once_at_first_comma_outside_paren(buf: &str) -> Result<(&str, &str), BsdlError> {
    let comma = find_first_comma_outside_paren(buf)?;
    match comma {
        None => Ok((buf, "")),
        Some(comma) => Ok((&buf[0..comma], &buf[comma + 1..])),
    }
}

#[cfg(test)]
mod test_find_first_comma_outside_paren {
    use super::*;
    #[test]
    fn test_find_first_comma_outside_paren() {
        let x = find_first_comma_outside_paren("(,),");
        println!("{x:?}");
        assert!(x.unwrap().unwrap() == 3);
        let x = find_first_comma_outside_paren("(,)");
        println!("{x:?}");
        assert!(x.unwrap() == None);
        let x = find_first_comma_outside_paren("  ),");
        println!("{x:?}");
        assert!(x.is_err());
    }
}