// Detecting language items.
//
// Language items are items that represent concepts intrinsic to the language
// itself. Examples are:
//
// * Traits that specify "kinds"; e.g. "const", "copy", "send".
//
// * Traits that represent operators; e.g. "add", "sub", "index".
//
// * Functions called by the compiler itself.

use driver::session::session;
use metadata::csearch::{each_path, get_item_attrs};
use metadata::cstore::{iter_crate_data};
use metadata::decoder::{dl_def, dl_field, dl_impl};
use syntax::ast::{crate, def_fn, def_id, def_ty, lit_str, meta_item};
use syntax::ast::{meta_list, meta_name_value, meta_word};
use syntax::ast_util::{local_def};
use syntax::visit::{default_simple_visitor, mk_simple_visitor};
use syntax::visit::{visit_crate, visit_item};

use std::map::HashMap;
use str_eq = str::eq;

struct LanguageItems {
    mut const_trait: Option<def_id>,
    mut copy_trait: Option<def_id>,
    mut send_trait: Option<def_id>,
    mut owned_trait: Option<def_id>,

    mut add_trait: Option<def_id>,
    mut sub_trait: Option<def_id>,
    mut mul_trait: Option<def_id>,
    mut div_trait: Option<def_id>,
    mut modulo_trait: Option<def_id>,
    mut neg_trait: Option<def_id>,
    mut bitxor_trait: Option<def_id>,
    mut bitand_trait: Option<def_id>,
    mut bitor_trait: Option<def_id>,
    mut shl_trait: Option<def_id>,
    mut shr_trait: Option<def_id>,
    mut index_trait: Option<def_id>,

    mut eq_trait: Option<def_id>,
    mut ord_trait: Option<def_id>,

    mut str_eq_fn: Option<def_id>,
    mut uniq_str_eq_fn: Option<def_id>,
    mut annihilate_fn: Option<def_id>,
    mut log_type_fn: Option<def_id>
}

mod LanguageItems {
    #[legacy_exports];
    fn make() -> LanguageItems {
        LanguageItems {
            const_trait: None,
            copy_trait: None,
            send_trait: None,
            owned_trait: None,

            add_trait: None,
            sub_trait: None,
            mul_trait: None,
            div_trait: None,
            modulo_trait: None,
            neg_trait: None,
            bitxor_trait: None,
            bitand_trait: None,
            bitor_trait: None,
            shl_trait: None,
            shr_trait: None,
            index_trait: None,

            eq_trait: None,
            ord_trait: None,

            str_eq_fn: None,
            uniq_str_eq_fn: None,
            annihilate_fn: None,
            log_type_fn: None
        }
    }
}

fn LanguageItemCollector(crate: @crate, session: session,
                         items: &r/LanguageItems)
    -> LanguageItemCollector/&r {

    let item_refs = HashMap();

    item_refs.insert(~"const", &mut items.const_trait);
    item_refs.insert(~"copy", &mut items.copy_trait);
    item_refs.insert(~"send", &mut items.send_trait);
    item_refs.insert(~"owned", &mut items.owned_trait);

    item_refs.insert(~"add", &mut items.add_trait);
    item_refs.insert(~"sub", &mut items.sub_trait);
    item_refs.insert(~"mul", &mut items.mul_trait);
    item_refs.insert(~"div", &mut items.div_trait);
    item_refs.insert(~"modulo", &mut items.modulo_trait);
    item_refs.insert(~"neg", &mut items.neg_trait);
    item_refs.insert(~"bitxor", &mut items.bitxor_trait);
    item_refs.insert(~"bitand", &mut items.bitand_trait);
    item_refs.insert(~"bitor", &mut items.bitor_trait);
    item_refs.insert(~"shl", &mut items.shl_trait);
    item_refs.insert(~"shr", &mut items.shr_trait);
    item_refs.insert(~"index", &mut items.index_trait);

    item_refs.insert(~"eq", &mut items.eq_trait);
    item_refs.insert(~"ord", &mut items.ord_trait);

    item_refs.insert(~"str_eq", &mut items.str_eq_fn);
    item_refs.insert(~"uniq_str_eq", &mut items.uniq_str_eq_fn);
    item_refs.insert(~"annihilate", &mut items.annihilate_fn);
    item_refs.insert(~"log_type", &mut items.log_type_fn);

    LanguageItemCollector {
        crate: crate,
        session: session,
        items: items,
        item_refs: item_refs
    }
}

struct LanguageItemCollector {
    items: &LanguageItems,

    crate: @crate,
    session: session,

    item_refs: HashMap<~str,&mut Option<def_id>>,
}

impl LanguageItemCollector {

    fn match_and_collect_meta_item(item_def_id: def_id,
                                   meta_item: meta_item) {
        match meta_item.node {
            meta_name_value(key, literal) => {
                match literal.node {
                    lit_str(value) => {
                        self.match_and_collect_item(item_def_id, key, *value);
                    }
                    _ => {} // Skip.
                }
            }
            meta_word(*) | meta_list(*) => {} // Skip.
        }
    }

    fn match_and_collect_item(item_def_id: def_id, key: ~str, value: ~str) {
        if key != ~"lang" {
            return;    // Didn't match.
        }

        match self.item_refs.find(value) {
            None => {
                // Didn't match.
            }
            Some(item_ref) => {
                // Check for duplicates.
                match copy *item_ref {
                    Some(original_def_id)
                            if original_def_id != item_def_id => {

                        self.session.err(fmt!("duplicate entry for `%s`",
                                              value));
                    }
                    Some(_) | None => {
                        // OK.
                    }
                }

                // Matched.
                *item_ref = Some(item_def_id);
            }
        }
    }

    fn collect_local_language_items() {
        let this = unsafe { ptr::addr_of(&self) };
        visit_crate(*self.crate, (), mk_simple_visitor(@{
            visit_item: |item| {
                for item.attrs.each |attribute| {
                    unsafe {
                        (*this).match_and_collect_meta_item(local_def(item
                                                                      .id),
                                                            attribute.node
                                                                     .value);
                    }
                }
            },
            .. *default_simple_visitor()
        }));
    }

    fn collect_external_language_items() {
        let crate_store = self.session.cstore;
        do iter_crate_data(crate_store) |crate_number, _crate_metadata| {
            for each_path(crate_store, crate_number) |path_entry| {
                let def_id;
                match path_entry.def_like {
                    dl_def(def_ty(did)) | dl_def(def_fn(did, _)) => {
                        def_id = did;
                    }
                    dl_def(_) | dl_impl(_) | dl_field => {
                        // Skip this.
                        loop;
                    }
                }

                do get_item_attrs(crate_store, def_id) |meta_items| {
                    for meta_items.each |meta_item| {
                        self.match_and_collect_meta_item(def_id, **meta_item);
                    }
                }
            }
        }
    }

    fn check_completeness() {
        for self.item_refs.each |key, item_ref| {
            match *item_ref {
                None => {
                    self.session.err(fmt!("no item found for `%s`", key));
                }
                Some(_) => {
                    // OK.
                }
            }
        }
    }

    fn collect() {
        self.collect_local_language_items();
        self.collect_external_language_items();
        self.check_completeness();
    }
}

fn collect_language_items(crate: @crate, session: session) -> LanguageItems {
    let items = LanguageItems::make();
    let collector = LanguageItemCollector(crate, session, &items);
    collector.collect();
    copy items
}

