pub struct Ingredient {
    pub name: String,
    pub quantity: String,
    pub unit: Option<String>,
    pub addendum: Option<String>,
}

pub struct Recipe {
    pub title: Option<String>,
    pub ingredients: Vec<Ingredient>,
    pub process: Vec<String>,
}
impl Recipe {
    pub fn parse(recipe: &str) -> Self {
        let mut ingredients = Vec::new();
        let mut process = Vec::new();
        let mut paragraph = String::new();
        let mut title = None;

        for line in recipe.lines() {
            if line.trim().is_empty() {
                let paragraph = std::mem::take(&mut paragraph);
                if !paragraph.is_empty() {
                    process.push(paragraph);
                }
                continue;
            }
            if line.starts_with("# ") && title.is_none() {
                title = Some(line[2..].to_string());
                continue;
            }

            let chars: Vec<char> = line.chars().collect();
            let mut i = 0;
            while i < chars.len() {
                match capture(&chars[i..]) {
                    Some((ingredient, length)) => {
                        paragraph.push_str(&ingredient.name);
                        ingredients.push(ingredient);
                        i += length;
                    }
                    None => {
                        paragraph.push(*chars.get(i).unwrap());
                        i += 1;
                    }
                }
            }
        }

        let paragraph = std::mem::take(&mut paragraph);
        if !paragraph.is_empty() {
            process.push(paragraph);
        }

        Self {
            title,
            ingredients,
            process,
        }
    }
}

fn capture(chars: &[char]) -> Option<(Ingredient, usize)> {
    if chars.get(0) != Some(&'{') {
        return None;
    }
    let mut i = 1;
    let mut name = String::new();
    let mut quantity = String::new();
    let mut unit = None;
    let mut addendum = None;

    // Ingredient name
    loop {
        match chars.get(i) {
            Some(&',') => {
                i += 1;
                break;
            }
            Some(c) => {
                name.push(*c);
                i += 1;
            }
            None => return None,
        }
    }

    // Eat spaces
    loop {
        match chars.get(i) {
            Some(&' ') => i += 1,
            Some(_) => break,
            None => return None,
        }
    }

    // Quantity
    loop {
        match chars.get(i) {
            Some(&' ') => {
                i += 1;
                unit = Some(String::new());
                break;
            }
            Some(&',') => {
                i += 1;
                addendum = Some(String::new());
                break;
            }
            Some(&'}') => {
                i += 1;
                break;
            }
            Some(c) => {
                quantity.push(*c);
                i += 1;
            }
            None => return None,
        }
    }

    // Unit

    if let Some(ref mut unit) = unit {
        loop {
            match chars.get(i) {
                Some(&'}') => {
                    i += 1;
                    break;
                }
                Some(&',') => {
                    i += 1;
                    addendum = Some(String::new());
                    break;
                }
                Some(c) => {
                    unit.push(*c);
                    i += 1;
                }
                None => return None,
            }
        }
    }

    // Addendum
    if let Some(ref mut addendum) = addendum {
        loop {
            match chars.get(i) {
                Some(&'}') => {
                    i += 1;
                    break;
                }
                Some(c) => {
                    addendum.push(*c);
                    i += 1;
                }
                None => return None,
            }
        }
    }

    // Trim values
    let name = name.trim().to_string();
    let quantity = quantity.trim().to_string();
    let unit = unit.and_then(|s| Some(s.trim().to_string()));
    let addendum = addendum.and_then(|s| Some(s.trim().to_string()));

    Some((
        Ingredient {
            name,
            quantity,
            unit,
            addendum,
        },
        i,
    ))
}