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

use libc::c_uint;
use llvm::{self, ValueRef};
use rustc::mir::repr as mir;
use rustc::mir::tcx::LvalueTy;
use trans::base;
use trans::build;
use trans::common::{self, Block};
use trans::debuginfo::DebugLoc;
use trans::expr;
use trans::type_of;

use self::lvalue::LvalueRef;
use self::operand::OperandRef;

// FIXME DebugLoc is always None right now

/// Master context for translating MIR.
pub struct MirContext<'bcx, 'tcx:'bcx> {
    mir: &'bcx mir::Mir<'tcx>,

    /// When unwinding is initiated, we have to store this personality
    /// value somewhere so that we can load it and re-use it in the
    /// resume instruction. The personality is (afaik) some kind of
    /// value used for C++ unwinding, which must filter by type: we
    /// don't really care about it very much. Anyway, this value
    /// contains an alloca into which the personality is stored and
    /// then later loaded when generating the DIVERGE_BLOCK.
    llpersonalityslot: Option<ValueRef>,

    /// A `Block` for each MIR `BasicBlock`
    blocks: Vec<Block<'bcx, 'tcx>>,

    /// An LLVM alloca for each MIR `VarDecl`
    vars: Vec<LvalueRef<'tcx>>,

    /// The location where each MIR `TempDecl` is stored. This is
    /// usually an `LvalueRef` representing an alloca, but not always:
    /// sometimes we can skip the alloca and just store the value
    /// directly using an `OperandRef`, which makes for tighter LLVM
    /// IR. The conditions for using an `OperandRef` are as follows:
    ///
    /// - the type of the temporary must be judged "immediate" by `type_is_immediate`
    /// - the operand must never be referenced indirectly
    ///     - we should not take its address using the `&` operator
    ///     - nor should it appear in an lvalue path like `tmp.a`
    /// - the operand must be defined by an rvalue that can generate immediate
    ///   values
    ///
    /// Avoiding allocs can also be important for certain intrinsics,
    /// notably `expect`.
    temps: Vec<TempRef<'tcx>>,

    /// The arguments to the function; as args are lvalues, these are
    /// always indirect, though we try to avoid creating an alloca
    /// when we can (and just reuse the pointer the caller provided).
    args: Vec<LvalueRef<'tcx>>,
}

enum TempRef<'tcx> {
    Lvalue(LvalueRef<'tcx>),
    Operand(Option<OperandRef<'tcx>>),
}

///////////////////////////////////////////////////////////////////////////

pub fn trans_mir<'bcx, 'tcx>(bcx: Block<'bcx, 'tcx>) {
    let fcx = bcx.fcx;
    let mir = bcx.mir();

    let mir_blocks = bcx.mir().all_basic_blocks();

    // Analyze the temps to determine which must be lvalues
    // FIXME
    let lvalue_temps = analyze::lvalue_temps(bcx, mir);

    // Allocate variable and temp allocas
    let vars = mir.var_decls.iter()
                            .map(|decl| (bcx.monomorphize(&decl.ty), decl.name))
                            .map(|(mty, name)| LvalueRef::alloca(bcx, mty, &name.as_str()))
                            .collect();
    let temps = mir.temp_decls.iter()
                              .map(|decl| bcx.monomorphize(&decl.ty))
                              .enumerate()
                              .map(|(i, mty)| if lvalue_temps.contains(&i) {
                                  TempRef::Lvalue(LvalueRef::alloca(bcx,
                                                                    mty,
                                                                    &format!("temp{:?}", i)))
                              } else {
                                  // If this is an immediate temp, we do not create an
                                  // alloca in advance. Instead we wait until we see the
                                  // definition and update the operand there.
                                  TempRef::Operand(None)
                              })
                              .collect();
    let args = arg_value_refs(bcx, mir);

    // Allocate a `Block` for every basic block
    let block_bcxs: Vec<Block<'bcx,'tcx>> =
        mir_blocks.iter()
                  .map(|&bb| fcx.new_block(false, &format!("{:?}", bb), None))
                  .collect();

    // Branch to the START block
    let start_bcx = block_bcxs[mir::START_BLOCK.index()];
    build::Br(bcx, start_bcx.llbb, DebugLoc::None);

    let mut mircx = MirContext {
        mir: mir,
        llpersonalityslot: None,
        blocks: block_bcxs,
        vars: vars,
        temps: temps,
        args: args,
    };

    // Translate the body of each block
    for &bb in &mir_blocks {
        if bb != mir::DIVERGE_BLOCK {
            mircx.trans_block(bb);
        }
    }

    // Total hack: translate DIVERGE_BLOCK last. This is so that any
    // panics which the fn may do can initialize the
    // `llpersonalityslot` cell. We don't do this up front because the
    // LLVM type of it is (frankly) annoying to compute.
    mircx.trans_block(mir::DIVERGE_BLOCK);
}

/// Produce, for each argument, a `ValueRef` pointing at the
/// argument's value. As arguments are lvalues, these are always
/// indirect.
fn arg_value_refs<'bcx, 'tcx>(bcx: Block<'bcx, 'tcx>,
                              mir: &mir::Mir<'tcx>)
                              -> Vec<LvalueRef<'tcx>> {
    // FIXME tupled_args? I think I'd rather that mapping is done in MIR land though
    let fcx = bcx.fcx;
    let tcx = bcx.tcx();
    let mut idx = fcx.arg_offset() as c_uint;
    mir.arg_decls
       .iter()
       .enumerate()
       .map(|(arg_index, arg_decl)| {
           let arg_ty = bcx.monomorphize(&arg_decl.ty);
           let llval = if type_of::arg_is_indirect(bcx.ccx(), arg_ty) {
               // Don't copy an indirect argument to an alloca, the caller
               // already put it in a temporary alloca and gave it up, unless
               // we emit extra-debug-info, which requires local allocas :(.
               // FIXME: lifetimes, debug info
               let llarg = llvm::get_param(fcx.llfn, idx);
               idx += 1;
               llarg
           } else if common::type_is_fat_ptr(tcx, arg_ty) {
               // we pass fat pointers as two words, but we want to
               // represent them internally as a pointer to two words,
               // so make an alloca to store them in.
               let lldata = llvm::get_param(fcx.llfn, idx);
               let llextra = llvm::get_param(fcx.llfn, idx + 1);
               idx += 2;
               let lltemp = base::alloc_ty(bcx, arg_ty, &format!("arg{}", arg_index));
               build::Store(bcx, lldata, expr::get_dataptr(bcx, lltemp));
               build::Store(bcx, llextra, expr::get_meta(bcx, lltemp));
               lltemp
           } else {
               // otherwise, arg is passed by value, so make a
               // temporary and store it there
               let llarg = llvm::get_param(fcx.llfn, idx);
               idx += 1;
               let lltemp = base::alloc_ty(bcx, arg_ty, &format!("arg{}", arg_index));
               base::store_ty(bcx, llarg, lltemp, arg_ty);
               lltemp
           };
           LvalueRef::new_sized(llval, LvalueTy::from_ty(arg_ty))
       })
       .collect()
}

mod analyze;
mod block;
mod constant;
mod lvalue;
mod rvalue;
mod operand;
mod statement;
