// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.


//
// Unused import checking
//
// Although this is mostly a lint pass, it lives in here because it depends on
// resolve data structures and because it finalises the privacy information for
// `use` directives.
//

use std::ops::{Deref, DerefMut};

use Resolver;
use Namespace::{TypeNS, ValueNS};

use rustc::lint;
use rustc::middle::privacy::{DependsOn, LastImport, Used, Unused};
use syntax::ast;
use syntax::ast::{ViewPathGlob, ViewPathList, ViewPathSimple};
use syntax::codemap::{Span, DUMMY_SP};
use syntax::visit::{self, Visitor};

struct UnusedImportCheckVisitor<'a, 'b:'a, 'tcx:'b> {
    resolver: &'a mut Resolver<'b, 'tcx>
}

// Deref and DerefMut impls allow treating UnusedImportCheckVisitor as Resolver.
impl<'a, 'b, 'tcx:'b> Deref for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
    type Target = Resolver<'b, 'tcx>;

    fn deref<'c>(&'c self) -> &'c Resolver<'b, 'tcx> {
        &*self.resolver
    }
}

impl<'a, 'b, 'tcx:'b> DerefMut for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
    fn deref_mut<'c>(&'c mut self) -> &'c mut Resolver<'b, 'tcx> {
        &mut *self.resolver
    }
}

impl<'a, 'b, 'tcx> UnusedImportCheckVisitor<'a, 'b, 'tcx> {
    // We have information about whether `use` (import) directives are actually used now.
    // If an import is not used at all, we signal a lint error. If an import is only used
    // for a single namespace, we remove the other namespace from the recorded privacy
    // information. That means in privacy.rs, we will only check imports and namespaces
    // which are used. In particular, this means that if an import could name either a
    // public or private item, we will check the correct thing, dependent on how the import
    // is used.
    fn finalize_import(&mut self, id: ast::NodeId, span: Span) {
        debug!("finalizing import uses for {:?}",
                self.session.codemap().span_to_snippet(span));

        if !self.used_imports.contains(&(id, TypeNS)) &&
           !self.used_imports.contains(&(id, ValueNS)) {
            self.session.add_lint(lint::builtin::UNUSED_IMPORTS,
                                  id,
                                  span,
                                  "unused import".to_string());
        }

        let mut def_map = self.def_map.borrow_mut();
        let path_res = if let Some(r) = def_map.get_mut(&id) {
            r
        } else {
            return;
        };
        let (v_priv, t_priv) = match path_res.last_private {
            LastImport { value_priv, type_priv, .. } => (value_priv, type_priv),
            _ => {
                panic!("we should only have LastImport for `use` directives")
            }
        };

        let mut v_used = if self.used_imports.contains(&(id, ValueNS)) {
            Used
        } else {
            Unused
        };
        let t_used = if self.used_imports.contains(&(id, TypeNS)) {
            Used
        } else {
            Unused
        };

        match (v_priv, t_priv) {
            // Since some items may be both in the value _and_ type namespaces (e.g., structs)
            // we might have two LastPrivates pointing at the same thing. There is no point
            // checking both, so lets not check the value one.
            (Some(DependsOn(def_v)), Some(DependsOn(def_t))) if def_v == def_t => v_used = Unused,
            _ => {},
        }

        path_res.last_private = LastImport {
            value_priv: v_priv,
            value_used: v_used,
            type_priv: t_priv,
            type_used: t_used
        };
    }
}

impl<'a, 'b, 'v, 'tcx> Visitor<'v> for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
    fn visit_item(&mut self, item: &ast::Item) {
        // Ignore is_public import statements because there's no way to be sure
        // whether they're used or not. Also ignore imports with a dummy span
        // because this means that they were generated in some fashion by the
        // compiler and we don't need to consider them.
        if item.vis == ast::Public || item.span == DUMMY_SP {
            visit::walk_item(self, item);
            return;
        }

        match item.node {
            ast::ItemExternCrate(_) => {
                if let Some(crate_num) = self.session.cstore.find_extern_mod_stmt_cnum(item.id) {
                    if !self.used_crates.contains(&crate_num) {
                        self.session.add_lint(lint::builtin::UNUSED_EXTERN_CRATES,
                                              item.id,
                                              item.span,
                                              "unused extern crate".to_string());
                    }
                }
            },
            ast::ItemUse(ref p) => {
                match p.node {
                    ViewPathSimple(_, _) => {
                        self.finalize_import(item.id, p.span)
                    }

                    ViewPathList(_, ref list) => {
                        for i in list {
                            self.finalize_import(i.node.id(), i.span);
                        }
                    }
                    ViewPathGlob(_) => {
                        if !self.used_imports.contains(&(item.id, TypeNS)) &&
                           !self.used_imports.contains(&(item.id, ValueNS)) {
                            self.session
                                .add_lint(lint::builtin::UNUSED_IMPORTS,
                                          item.id,
                                          p.span,
                                          "unused import".to_string());
                        }
                    }
                }
            }
            _ => {}
        }

        visit::walk_item(self, item);
    }
}

pub fn check_crate(resolver: &mut Resolver, krate: &ast::Crate) {
    let mut visitor = UnusedImportCheckVisitor { resolver: resolver };
    visit::walk_crate(&mut visitor, krate);
}
