use serde_derive::Serialize;

/// Populated while receiving events from the markdown parser
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Heading {
    #[serde(skip_serializing)]
    pub level: u32,
    pub id: String,
    pub permalink: String,
    pub title: String,
    pub children: Vec<Heading>,
}

impl Heading {
    pub fn new(level: u32) -> Heading {
        Heading {
            level,
            id: String::new(),
            permalink: String::new(),
            title: String::new(),
            children: Vec::new(),
        }
    }
}

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

// Takes a potential (mutable) parent and a heading to try and insert into
// Returns true when it performed the insertion, false otherwise
fn insert_into_parent(potential_parent: Option<&mut Heading>, heading: &Heading) -> bool {
    match potential_parent {
        None => {
            // No potential parent to insert into so it needs to be insert higher
            false
        }
        Some(parent) => {
            if heading.level <= parent.level {
                // Heading is same level or higher so we don't insert here
                return false;
            }
            if heading.level + 1 == parent.level {
                // We have a direct child of the parent
                parent.children.push(heading.clone());
                return true;
            }
            // We need to go deeper
            if !insert_into_parent(parent.children.iter_mut().last(), heading) {
                // No, we need to insert it here
                parent.children.push(heading.clone());
            }
            true
        }
    }
}

/// Converts the flat temp headings into a nested set of headings
/// representing the hierarchy
pub fn make_table_of_contents(headings: Vec<Heading>) -> Vec<Heading> {
    let mut toc = vec![];
    for heading in headings {
        // First heading or we try to insert the current heading in a previous one
        if toc.is_empty() || !insert_into_parent(toc.iter_mut().last(), &heading) {
            toc.push(heading);
        }
    }

    toc
}

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

    #[test]
    fn can_make_basic_toc() {
        let input = vec![Heading::new(1), Heading::new(1), Heading::new(1)];
        let toc = make_table_of_contents(input);
        assert_eq!(toc.len(), 3);
    }

    #[test]
    fn can_make_more_complex_toc() {
        let input = vec![
            Heading::new(1),
            Heading::new(2),
            Heading::new(2),
            Heading::new(3),
            Heading::new(2),
            Heading::new(1),
            Heading::new(2),
            Heading::new(3),
            Heading::new(3),
        ];
        let toc = make_table_of_contents(input);
        assert_eq!(toc.len(), 2);
        assert_eq!(toc[0].children.len(), 3);
        assert_eq!(toc[1].children.len(), 1);
        assert_eq!(toc[0].children[1].children.len(), 1);
        assert_eq!(toc[1].children[0].children.len(), 2);
    }

    #[test]
    fn can_make_deep_toc() {
        let input = vec![
            Heading::new(1),
            Heading::new(2),
            Heading::new(3),
            Heading::new(4),
            Heading::new(5),
            Heading::new(4),
        ];
        let toc = make_table_of_contents(input);
        assert_eq!(toc.len(), 1);
        assert_eq!(toc[0].children.len(), 1);
        assert_eq!(toc[0].children[0].children.len(), 1);
        assert_eq!(toc[0].children[0].children[0].children.len(), 2);
        assert_eq!(toc[0].children[0].children[0].children[0].children.len(), 1);
    }

    #[test]
    fn can_make_deep_messy_toc() {
        let input = vec![
            Heading::new(2), // toc[0]
            Heading::new(3),
            Heading::new(4),
            Heading::new(5),
            Heading::new(4),
            Heading::new(2), // toc[1]
            Heading::new(1), // toc[2]
            Heading::new(2),
            Heading::new(3),
            Heading::new(4),
        ];
        let toc = make_table_of_contents(input);
        assert_eq!(toc.len(), 3);
        assert_eq!(toc[0].children.len(), 1);
        assert_eq!(toc[0].children[0].children.len(), 2);
        assert_eq!(toc[0].children[0].children[0].children.len(), 1);
        assert_eq!(toc[1].children.len(), 0);
        assert_eq!(toc[2].children.len(), 1);
        assert_eq!(toc[2].children[0].children.len(), 1);
        assert_eq!(toc[2].children[0].children[0].children.len(), 1);
    }

    #[test]
    fn can_make_messy_toc() {
        let input = vec![
            Heading::new(3),
            Heading::new(2),
            Heading::new(2),
            Heading::new(3),
            Heading::new(2),
            Heading::new(1),
            Heading::new(4),
        ];
        let toc = make_table_of_contents(input);
        println!("{:#?}", toc);
        assert_eq!(toc.len(), 5);
        assert_eq!(toc[2].children.len(), 1);
        assert_eq!(toc[4].children.len(), 1);
    }
}
