#[doc = "Breaks rustdocs into sections according to their headers"];

export mk_pass;

fn mk_pass() -> pass {
    {
        name: "sectionalize",
        f: run
    }
}

fn run(_srv: astsrv::srv, doc: doc::doc) -> doc::doc {
    let fold = fold::fold({
        fold_item: fold_item,
        fold_iface: fold_iface,
        fold_impl: fold_impl
        with *fold::default_any_fold(())
    });
    fold.fold_doc(fold, doc)
}

fn fold_item(fold: fold::fold<()>, doc: doc::itemdoc) -> doc::itemdoc {
    let doc = fold::default_seq_fold_item(fold, doc);
    let (desc, sections) = sectionalize(doc.desc);

    {
        desc: desc,
        sections: sections
        with doc
    }
}

fn fold_iface(fold: fold::fold<()>, doc: doc::ifacedoc) -> doc::ifacedoc {
    let doc = fold::default_seq_fold_iface(fold, doc);

    {
        methods: par::anymap(doc.methods) {|method|
            let (desc, sections) = sectionalize(method.desc);

            {
                desc: desc,
                sections: sections
                with method
            }
        }
        with doc
    }
}

fn fold_impl(fold: fold::fold<()>, doc: doc::impldoc) -> doc::impldoc {
    let doc = fold::default_seq_fold_impl(fold, doc);

    {
        methods: par::anymap(doc.methods) {|method|
            let (desc, sections) = sectionalize(method.desc);

            {
                desc: desc,
                sections: sections
                with method
            }
        }
        with doc
    }
}

fn sectionalize(desc: option<str>) -> (option<str>, [doc::section]) {

    #[doc = "

    Take a description of the form

        General text

        # Section header

        Section text

        # Section header

        Section text

    and remove each header and accompanying text into section records.

    "];

    if option::is_none(desc) {
        ret (none, []);
    }

    let lines = str::lines(option::get(desc));

    let mut new_desc = none::<str>;
    let mut current_section = none;
    let mut sections = [];

    for lines.each {|line|
        alt parse_header(line) {
          some(header) {
            if option::is_some(current_section) {
                sections += [option::get(current_section)];
            }
            current_section = some({
                header: header,
                body: ""
            });
          }
          none {
            alt current_section {
              some(section) {
                current_section = some({
                    body: section.body + "\n" + line
                    with section
                });
              }
              none {
                alt new_desc {
                  some(desc) {
                    new_desc = some(desc + "\n" + line);
                  }
                  none {
                    new_desc = some(line);
                  }
                }
              }
            }
          }
        }
    }

    if option::is_some(current_section) {
        sections += [option::get(current_section)];
    }

    (new_desc, sections)
}

fn parse_header(line: str) -> option<str> {
    if str::starts_with(line, "# ") {
        some(str::slice(line, 2u, str::len(line)))
    } else {
        none
    }
}

#[test]
fn should_create_section_headers() {
    let doc = test::mk_doc(
        "#[doc = \"\
         # Header\n\
         Body\"]\
         mod a { }");
    assert str::contains(
        doc.cratemod().mods()[0].item.sections[0].header,
        "Header");
}

#[test]
fn should_create_section_bodies() {
    let doc = test::mk_doc(
        "#[doc = \"\
         # Header\n\
         Body\"]\
         mod a { }");
    assert str::contains(
        doc.cratemod().mods()[0].item.sections[0].body,
        "Body");
}

#[test]
fn should_not_create_sections_from_indented_headers() {
    let doc = test::mk_doc(
        "#[doc = \"\n\
         Text\n             # Header\n\
         Body\"]\
         mod a { }");
    assert vec::is_empty(doc.cratemod().mods()[0].item.sections);
}

#[test]
fn should_remove_section_text_from_main_desc() {
    let doc = test::mk_doc(
        "#[doc = \"\
         Description\n\n\
         # Header\n\
         Body\"]\
         mod a { }");
    assert !str::contains(
        option::get(doc.cratemod().mods()[0].desc()),
        "Header");
    assert !str::contains(
        option::get(doc.cratemod().mods()[0].desc()),
        "Body");
}

#[test]
fn should_eliminate_desc_if_it_is_just_whitespace() {
    let doc = test::mk_doc(
        "#[doc = \"\
         # Header\n\
         Body\"]\
         mod a { }");
    assert doc.cratemod().mods()[0].desc() == none;
}

#[test]
fn should_sectionalize_iface_methods() {
    let doc = test::mk_doc(
        "iface i {
         #[doc = \"\
         # Header\n\
         Body\"]\
         fn a(); }");
    assert doc.cratemod().ifaces()[0].methods[0].sections.len() == 1u;
}

#[test]
fn should_sectionalize_impl_methods() {
    let doc = test::mk_doc(
        "impl i for bool {
         #[doc = \"\
         # Header\n\
         Body\"]\
         fn a() { } }");
    assert doc.cratemod().impls()[0].methods[0].sections.len() == 1u;
}

#[cfg(test)]
mod test {
    fn mk_doc(source: str) -> doc::doc {
        astsrv::from_str(source) {|srv|
            let doc = extract::from_srv(srv, "");
            let doc = attr_pass::mk_pass().f(srv, doc);
            run(srv, doc)
        }
    }
}