// Copyright 2012 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.

/*!

Data structures used for tracking moves. Please see the extensive
comments in the section "Moves and initialization" and in `doc.rs`.

*/


use std::hashmap::{HashMap, HashSet};
use std::uint;
use middle::borrowck::*;
use middle::dataflow::DataFlowContext;
use middle::dataflow::DataFlowOperator;
use middle::ty;
use middle::typeck;
use syntax::ast;
use syntax::ast_util;
use syntax::codemap::span;
use syntax::opt_vec::OptVec;
use syntax::opt_vec;
use util::ppaux::Repr;

pub struct MoveData {
    /// Move paths. See section "Move paths" in `doc.rs`.
    paths: ~[MovePath],

    /// Cache of loan path to move path index, for easy lookup.
    path_map: HashMap<@LoanPath, MovePathIndex>,

    /// Each move or uninitialized variable gets an entry here.
    moves: ~[Move],

    /// Assignments to a variable, like `x = foo`. These are assigned
    /// bits for dataflow, since we must track them to ensure that
    /// immutable variables are assigned at most once along each path.
    var_assignments: ~[Assignment],

    /// Assignments to a path, like `x.f = foo`. These are not
    /// assigned dataflow bits, but we track them because they still
    /// kill move bits.
    path_assignments: ~[Assignment],
    assignee_ids: HashSet<ast::NodeId>,
}

pub struct FlowedMoveData {
    move_data: @mut MoveData,
    //         ^~~~~~~~~~~~~
    // It makes me sad to use @mut here, except that due to
    // the visitor design, this is what gather_loans
    // must produce.

    dfcx_moves: MoveDataFlow,

    // We could (and maybe should, for efficiency) combine both move
    // and assign data flow into one, but this way it's easier to
    // distinguish the bits that correspond to moves and assignments.
    dfcx_assign: AssignDataFlow
}

/// Index into `MoveData.paths`, used like a pointer
#[deriving(Eq)]
pub struct MovePathIndex(uint);

impl Clone for MovePathIndex {
    fn clone(&self) -> MovePathIndex {
        MovePathIndex(**self)
    }
}

static InvalidMovePathIndex: MovePathIndex =
    MovePathIndex(uint::max_value);

/// Index into `MoveData.moves`, used like a pointer
#[deriving(Eq)]
pub struct MoveIndex(uint);

static InvalidMoveIndex: MoveIndex =
    MoveIndex(uint::max_value);

pub struct MovePath {
    /// Loan path corresponding to this move path
    loan_path: @LoanPath,

    /// Parent pointer, `InvalidMovePathIndex` if root
    parent: MovePathIndex,

    /// Head of linked list of moves to this path,
    /// `InvalidMoveIndex` if not moved
    first_move: MoveIndex,

    /// First node in linked list of children, `InvalidMovePathIndex` if leaf
    first_child: MovePathIndex,

    /// Next node in linked list of parent's children (siblings),
    /// `InvalidMovePathIndex` if none.
    next_sibling: MovePathIndex,
}

pub enum MoveKind {
    Declared,               // When declared, variables start out "moved".
    MoveExpr(@ast::expr),   // Expression or binding that moves a variable
    MovePat(@ast::pat),     // By-move binding
    Captured(@ast::expr),   // Closure creation that moves a value
}

pub struct Move {
    /// Path being moved.
    path: MovePathIndex,

    /// id of node that is doing the move.
    id: ast::NodeId,

    /// Kind of move, for error messages.
    kind: MoveKind,

    /// Next node in linked list of moves from `path`, or `InvalidMoveIndex`
    next_move: MoveIndex,
}

pub struct Assignment {
    /// Path being assigned.
    path: MovePathIndex,

    /// id where assignment occurs
    id: ast::NodeId,

    /// span of node where assignment occurs
    span: span,
}

pub struct MoveDataFlowOperator;

/// XXX(pcwalton): Should just be #[deriving(Clone)], but that doesn't work
/// yet on unit structs.
impl Clone for MoveDataFlowOperator {
    fn clone(&self) -> MoveDataFlowOperator {
        MoveDataFlowOperator
    }
}

pub type MoveDataFlow = DataFlowContext<MoveDataFlowOperator>;

pub struct AssignDataFlowOperator;

/// XXX(pcwalton): Should just be #[deriving(Clone)], but that doesn't work
/// yet on unit structs.
impl Clone for AssignDataFlowOperator {
    fn clone(&self) -> AssignDataFlowOperator {
        AssignDataFlowOperator
    }
}

pub type AssignDataFlow = DataFlowContext<AssignDataFlowOperator>;

impl MoveData {
    pub fn new() -> MoveData {
        MoveData {
            paths: ~[],
            path_map: HashMap::new(),
            moves: ~[],
            path_assignments: ~[],
            var_assignments: ~[],
            assignee_ids: HashSet::new(),
        }
    }

    fn path<'a>(&'a self, index: MovePathIndex) -> &'a MovePath {
        //! Type safe indexing operator
        &self.paths[*index]
    }

    fn mut_path<'a>(&'a mut self, index: MovePathIndex) -> &'a mut MovePath {
        //! Type safe indexing operator
        &mut self.paths[*index]
    }

    fn move<'a>(&'a self, index: MoveIndex) -> &'a Move {
        //! Type safe indexing operator
        &self.moves[*index]
    }

    fn is_var_path(&self, index: MovePathIndex) -> bool {
        //! True if `index` refers to a variable
        self.path(index).parent == InvalidMovePathIndex
    }

    pub fn move_path(&mut self,
                     tcx: ty::ctxt,
                     lp: @LoanPath) -> MovePathIndex {
        /*!
         * Returns the existing move path index for `lp`, if any,
         * and otherwise adds a new index for `lp` and any of its
         * base paths that do not yet have an index.
         */

        match self.path_map.find(&lp) {
            Some(&index) => {
                return index;
            }
            None => {}
        }

        let index = match *lp {
            LpVar(*) => {
                let index = MovePathIndex(self.paths.len());

                self.paths.push(MovePath {
                    loan_path: lp,
                    parent: InvalidMovePathIndex,
                    first_move: InvalidMoveIndex,
                    first_child: InvalidMovePathIndex,
                    next_sibling: InvalidMovePathIndex,
                });

                index
            }

            LpExtend(base, _, _) => {
                let parent_index = self.move_path(tcx, base);
                let index = MovePathIndex(self.paths.len());

                let next_sibling = self.path(parent_index).first_child;
                self.mut_path(parent_index).first_child = index;

                self.paths.push(MovePath {
                    loan_path: lp,
                    parent: parent_index,
                    first_move: InvalidMoveIndex,
                    first_child: InvalidMovePathIndex,
                    next_sibling: next_sibling,
                });

                index
            }
        };

        debug!("move_path(lp=%s, index=%?)",
               lp.repr(tcx),
               index);

        assert_eq!(*index, self.paths.len() - 1);
        self.path_map.insert(lp, index);
        return index;
    }

    fn existing_move_path(&self,
                          lp: @LoanPath)
                          -> Option<MovePathIndex> {
        self.path_map.find_copy(&lp)
    }

    fn existing_base_paths(&self,
                           lp: @LoanPath)
                           -> OptVec<MovePathIndex> {
        let mut result = opt_vec::Empty;
        self.add_existing_base_paths(lp, &mut result);
        result
    }

    fn add_existing_base_paths(&self,
                               lp: @LoanPath,
                               result: &mut OptVec<MovePathIndex>) {
        /*!
         * Adds any existing move path indices for `lp` and any base
         * paths of `lp` to `result`, but does not add new move paths
         */

        match self.path_map.find_copy(&lp) {
            Some(index) => {
                do self.each_base_path(index) |p| {
                    result.push(p);
                    true
                };
            }
            None => {
                match *lp {
                    LpVar(*) => { }
                    LpExtend(b, _, _) => {
                        self.add_existing_base_paths(b, result);
                    }
                }
            }
        }

    }

    pub fn add_move(&mut self,
                    tcx: ty::ctxt,
                    lp: @LoanPath,
                    id: ast::NodeId,
                    kind: MoveKind) {
        /*!
         * Adds a new move entry for a move of `lp` that occurs at
         * location `id` with kind `kind`.
         */

        debug!("add_move(lp=%s, id=%?, kind=%?)",
               lp.repr(tcx),
               id,
               kind);

        let path_index = self.move_path(tcx, lp);
        let move_index = MoveIndex(self.moves.len());

        let next_move = self.path(path_index).first_move;
        self.mut_path(path_index).first_move = move_index;

        self.moves.push(Move {
            path: path_index,
            id: id,
            kind: kind,
            next_move: next_move
        });
    }

    pub fn add_assignment(&mut self,
                          tcx: ty::ctxt,
                          lp: @LoanPath,
                          assign_id: ast::NodeId,
                          span: span,
                          assignee_id: ast::NodeId) {
        /*!
         * Adds a new record for an assignment to `lp` that occurs at
         * location `id` with the given `span`.
         */

        debug!("add_assignment(lp=%s, assign_id=%?, assignee_id=%?",
               lp.repr(tcx), assign_id, assignee_id);

        let path_index = self.move_path(tcx, lp);

        self.assignee_ids.insert(assignee_id);

        let assignment = Assignment {
            path: path_index,
            id: assign_id,
            span: span,
        };

        if self.is_var_path(path_index) {
            debug!("add_assignment[var](lp=%s, assignment=%u, path_index=%?)",
                   lp.repr(tcx), self.var_assignments.len(), path_index);

            self.var_assignments.push(assignment);
        } else {
            debug!("add_assignment[path](lp=%s, path_index=%?)",
                   lp.repr(tcx), path_index);

            self.path_assignments.push(assignment);
        }
    }

    fn add_gen_kills(&self,
                     tcx: ty::ctxt,
                     dfcx_moves: &mut MoveDataFlow,
                     dfcx_assign: &mut AssignDataFlow) {
        /*!
         * Adds the gen/kills for the various moves and
         * assignments into the provided data flow contexts.
         * Moves are generated by moves and killed by assignments and
         * scoping. Assignments are generated by assignment to variables and
         * killed by scoping. See `doc.rs` for more details.
         */

        for (i, move) in self.moves.iter().enumerate() {
            dfcx_moves.add_gen(move.id, i);
        }

        for (i, assignment) in self.var_assignments.iter().enumerate() {
            dfcx_assign.add_gen(assignment.id, i);
            self.kill_moves(assignment.path, assignment.id, dfcx_moves);
        }

        for assignment in self.path_assignments.iter() {
            self.kill_moves(assignment.path, assignment.id, dfcx_moves);
        }

        // Kill all moves related to a variable `x` when it goes out
        // of scope:
        for path in self.paths.iter() {
            match *path.loan_path {
                LpVar(id) => {
                    let kill_id = tcx.region_maps.encl_scope(id);
                    let path = *self.path_map.get(&path.loan_path);
                    self.kill_moves(path, kill_id, dfcx_moves);
                }
                LpExtend(*) => {}
            }
        }

        // Kill all assignments when the variable goes out of scope:
        for (assignment_index, assignment) in self.var_assignments.iter().enumerate() {
            match *self.path(assignment.path).loan_path {
                LpVar(id) => {
                    let kill_id = tcx.region_maps.encl_scope(id);
                    dfcx_assign.add_kill(kill_id, assignment_index);
                }
                LpExtend(*) => {
                    tcx.sess.bug("Var assignment for non var path");
                }
            }
        }
    }

    fn each_base_path(&self,
                      index: MovePathIndex,
                      f: &fn(MovePathIndex) -> bool)
                      -> bool {
        let mut p = index;
        while p != InvalidMovePathIndex {
            if !f(p) {
                return false;
            }
            p = self.path(p).parent;
        }
        return true;
    }

    fn each_extending_path(&self,
                           index: MovePathIndex,
                           f: &fn(MovePathIndex) -> bool) -> bool {
        if !f(index) {
            return false;
        }

        let mut p = self.path(index).first_child;
        while p != InvalidMovePathIndex {
            if !self.each_extending_path(p, |x| f(x)) {
                return false;
            }
            p = self.path(p).next_sibling;
        }

        return true;
    }

    fn each_applicable_move(&self,
                            index0: MovePathIndex,
                            f: &fn(MoveIndex) -> bool) -> bool {
        let mut ret = true;
        do self.each_extending_path(index0) |index| {
            let mut p = self.path(index).first_move;
            while p != InvalidMoveIndex {
                if !f(p) {
                    ret = false;
                    break;
                }
                p = self.move(p).next_move;
            }
            ret
        };
        ret
    }

    fn kill_moves(&self,
                  path: MovePathIndex,
                  kill_id: ast::NodeId,
                  dfcx_moves: &mut MoveDataFlow) {
        do self.each_applicable_move(path) |move_index| {
            dfcx_moves.add_kill(kill_id, *move_index);
            true
        };
    }
}

impl FlowedMoveData {
    pub fn new(move_data: @mut MoveData,
               tcx: ty::ctxt,
               method_map: typeck::method_map,
               id_range: ast_util::id_range,
               body: &ast::Block)
               -> FlowedMoveData
    {
        let mut dfcx_moves =
            DataFlowContext::new(tcx,
                                 method_map,
                                 MoveDataFlowOperator,
                                 id_range,
                                 move_data.moves.len());
        let mut dfcx_assign =
            DataFlowContext::new(tcx,
                                 method_map,
                                 AssignDataFlowOperator,
                                 id_range,
                                 move_data.var_assignments.len());
        move_data.add_gen_kills(tcx, &mut dfcx_moves, &mut dfcx_assign);
        dfcx_moves.propagate(body);
        dfcx_assign.propagate(body);
        FlowedMoveData {
            move_data: move_data,
            dfcx_moves: dfcx_moves,
            dfcx_assign: dfcx_assign,
        }
    }

    pub fn each_path_moved_by(&self,
                              id: ast::NodeId,
                              f: &fn(&Move, @LoanPath) -> bool)
                              -> bool {
        /*!
         * Iterates through each path moved by `id`
         */

        do self.dfcx_moves.each_gen_bit_frozen(id) |index| {
            let move = &self.move_data.moves[index];
            let moved_path = move.path;
            f(move, self.move_data.path(moved_path).loan_path)
        }
    }

    pub fn each_move_of(&self,
                        id: ast::NodeId,
                        loan_path: @LoanPath,
                        f: &fn(&Move, @LoanPath) -> bool)
                        -> bool {
        /*!
         * Iterates through each move of `loan_path` (or some base path
         * of `loan_path`) that *may* have occurred on entry to `id` without
         * an intervening assignment. In other words, any moves that
         * would invalidate a reference to `loan_path` at location `id`.
         */

        // Bad scenarios:
        //
        // 1. Move of `a.b.c`, use of `a.b.c`
        // 2. Move of `a.b.c`, use of `a.b.c.d`
        // 3. Move of `a.b.c`, use of `a` or `a.b`
        //
        // OK scenario:
        //
        // 4. move of `a.b.c`, use of `a.b.d`

        let base_indices = self.move_data.existing_base_paths(loan_path);
        if base_indices.is_empty() {
            return true;
        }

        let opt_loan_path_index = self.move_data.existing_move_path(loan_path);

        let mut ret = true;

        do self.dfcx_moves.each_bit_on_entry_frozen(id) |index| {
            let move = &self.move_data.moves[index];
            let moved_path = move.path;
            if base_indices.iter().any(|x| x == &moved_path) {
                // Scenario 1 or 2: `loan_path` or some base path of
                // `loan_path` was moved.
                if !f(move, self.move_data.path(moved_path).loan_path) {
                    ret = false;
                }
            } else {
                for &loan_path_index in opt_loan_path_index.iter() {
                    let cont = do self.move_data.each_base_path(moved_path) |p| {
                        if p == loan_path_index {
                            // Scenario 3: some extension of `loan_path`
                            // was moved
                            f(move, self.move_data.path(moved_path).loan_path)
                        } else {
                            true
                        }
                    };
                    if !cont { ret = false; break }
                }
            }
            ret
        }
    }

    pub fn is_assignee(&self,
                       id: ast::NodeId)
                       -> bool {
        //! True if `id` is the id of the LHS of an assignment

        self.move_data.assignee_ids.iter().any(|x| x == &id)
    }

    pub fn each_assignment_of(&self,
                              id: ast::NodeId,
                              loan_path: @LoanPath,
                              f: &fn(&Assignment) -> bool)
                              -> bool {
        /*!
         * Iterates through every assignment to `loan_path` that
         * may have occurred on entry to `id`. `loan_path` must be
         * a single variable.
         */

        let loan_path_index = {
            match self.move_data.existing_move_path(loan_path) {
                Some(i) => i,
                None => {
                    // if there were any assignments, it'd have an index
                    return true;
                }
            }
        };

        do self.dfcx_assign.each_bit_on_entry_frozen(id) |index| {
            let assignment = &self.move_data.var_assignments[index];
            if assignment.path == loan_path_index && !f(assignment) {
                false
            } else {
                true
            }
        }
    }
}

impl DataFlowOperator for MoveDataFlowOperator {
    #[inline]
    fn initial_value(&self) -> bool {
        false // no loans in scope by default
    }

    #[inline]
    fn join(&self, succ: uint, pred: uint) -> uint {
        succ | pred // moves from both preds are in scope
    }

    #[inline]
    fn walk_closures(&self) -> bool {
        true
    }
}

impl DataFlowOperator for AssignDataFlowOperator {
    #[inline]
    fn initial_value(&self) -> bool {
        false // no assignments in scope by default
    }

    #[inline]
    fn join(&self, succ: uint, pred: uint) -> uint {
        succ | pred // moves from both preds are in scope
    }

    #[inline]
    fn walk_closures(&self) -> bool {
        true
    }
}
