/*!
Divides the document tree into pages.

Each page corresponds is a logical section. There may be pages for
individual modules, pages for the crate, indexes, etc.
*/

use doc::{ItemUtils, PageUtils};
use syntax::ast;

export mk_pass;

fn mk_pass(output_style: config::OutputStyle) -> Pass {
    {
        name: ~"page",
        f: fn~(srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc {
            run(srv, doc, output_style)
        }
    }
}

fn run(
    _srv: astsrv::Srv,
    doc: doc::Doc,
    output_style: config::OutputStyle
) -> doc::Doc {

    if output_style == config::DocPerCrate {
        return doc;
    }

    let (result_port, page_chan) = do task::spawn_conversation
        |page_port, result_chan| {
        comm::send(result_chan, make_doc_from_pages(page_port));
    };

    find_pages(doc, page_chan);
    comm::recv(result_port)
}

type PagePort = comm::Port<Option<doc::Page>>;
type PageChan = comm::Chan<Option<doc::Page>>;

fn make_doc_from_pages(page_port: PagePort) -> doc::Doc {
    let mut pages = ~[];
    loop {
        let val = comm::recv(page_port);
        if val.is_some() {
            pages += ~[option::unwrap(val)];
        } else {
            break;
        }
    }
    doc::Doc_({
        pages: pages
    })
}

fn find_pages(doc: doc::Doc, page_chan: PageChan) {
    let fold = fold::Fold({
        fold_crate: fold_crate,
        fold_mod: fold_mod,
        fold_nmod: fold_nmod,
        .. *fold::default_any_fold(page_chan)
    });
    fold.fold_doc(fold, doc);

    comm::send(page_chan, None);
}

fn fold_crate(
    fold: fold::Fold<PageChan>,
    doc: doc::CrateDoc
) -> doc::CrateDoc {

    let doc = fold::default_seq_fold_crate(fold, doc);

    let page = doc::CratePage({
        topmod: strip_mod(doc.topmod),
        .. doc
    });

    comm::send(fold.ctxt, Some(page));

    doc
}

fn fold_mod(
    fold: fold::Fold<PageChan>,
    doc: doc::ModDoc
) -> doc::ModDoc {

    let doc = fold::default_any_fold_mod(fold, doc);

    if doc.id() != ast::crate_node_id {

        let doc = strip_mod(doc);
        let page = doc::ItemPage(doc::ModTag(doc));
        comm::send(fold.ctxt, Some(page));
    }

    doc
}

fn strip_mod(doc: doc::ModDoc) -> doc::ModDoc {
    doc::ModDoc_({
        items: do vec::filter(doc.items) |item| {
            match *item {
              doc::ModTag(_) => false,
              doc::NmodTag(_) => false,
              _ => true
            }
        },
        .. *doc
    })
}

fn fold_nmod(
    fold: fold::Fold<PageChan>,
    doc: doc::NmodDoc
) -> doc::NmodDoc {
    let doc = fold::default_seq_fold_nmod(fold, doc);
    let page = doc::ItemPage(doc::NmodTag(doc));
    comm::send(fold.ctxt, Some(page));
    return doc;
}

#[test]
fn should_not_split_the_doc_into_pages_for_doc_per_crate() {
    let doc = test::mk_doc_(
        config::DocPerCrate,
        ~"mod a { } mod b { mod c { } }"
    );
    assert doc.pages.len() == 1u;
}

#[test]
fn should_make_a_page_for_every_mod() {
    let doc = test::mk_doc(~"mod a { }");
    assert doc.pages.mods()[0].name() == ~"a";
}

#[test]
fn should_remove_mods_from_containing_mods() {
    let doc = test::mk_doc(~"mod a { }");
    assert vec::is_empty(doc.cratemod().mods());
}

#[test]
fn should_make_a_page_for_every_foreign_mod() {
    let doc = test::mk_doc(~"extern mod a { }");
    assert doc.pages.nmods()[0].name() == ~"a";
}

#[test]
fn should_remove_foreign_mods_from_containing_mods() {
    let doc = test::mk_doc(~"extern mod a { }");
    assert vec::is_empty(doc.cratemod().nmods());
}

#[cfg(test)]
mod test {
    #[legacy_exports];
    fn mk_doc_(
        output_style: config::OutputStyle,
        source: ~str
    ) -> doc::Doc {
        do astsrv::from_str(source) |srv| {
            let doc = extract::from_srv(srv, ~"");
            run(srv, doc, output_style)
        }
    }

    fn mk_doc(source: ~str) -> doc::Doc {
        mk_doc_(config::DocPerMod, source)
    }
}
