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

use llvm::ValueRef;
use middle::def;
use middle::lang_items::{PanicFnLangItem, PanicBoundsCheckFnLangItem};
use trans::base::*;
use trans::basic_block::BasicBlock;
use trans::build::*;
use trans::callee;
use trans::cleanup::CleanupMethods;
use trans::cleanup;
use trans::common::*;
use trans::consts;
use trans::debuginfo;
use trans::debuginfo::{DebugLoc, ToDebugLoc};
use trans::expr;
use trans;
use middle::ty;
use util::ppaux::Repr;

use syntax::ast;
use syntax::ast::Ident;
use syntax::ast_util;
use syntax::parse::token::InternedString;
use syntax::parse::token;
use syntax::visit::Visitor;

pub fn trans_stmt<'blk, 'tcx>(cx: Block<'blk, 'tcx>,
                              s: &ast::Stmt)
                              -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("trans_stmt");
    let fcx = cx.fcx;
    debug!("trans_stmt({})", s.repr(cx.tcx()));

    if cx.unreachable.get() {
        return cx;
    }

    if cx.sess().asm_comments() {
        add_span_comment(cx, s.span, &s.repr(cx.tcx()));
    }

    let mut bcx = cx;

    let id = ast_util::stmt_id(s);
    let cleanup_debug_loc =
        debuginfo::get_cleanup_debug_loc_for_ast_node(bcx.ccx(), id, s.span, false);
    fcx.push_ast_cleanup_scope(cleanup_debug_loc);

    match s.node {
        ast::StmtExpr(ref e, _) | ast::StmtSemi(ref e, _) => {
            bcx = trans_stmt_semi(bcx, &**e);
        }
        ast::StmtDecl(ref d, _) => {
            match d.node {
                ast::DeclLocal(ref local) => {
                    bcx = init_local(bcx, &**local);
                    debuginfo::create_local_var_metadata(bcx, &**local);
                }
                // Inner items are visited by `trans_item`/`trans_meth`.
                ast::DeclItem(_) => {},
            }
        }
        ast::StmtMac(..) => cx.tcx().sess.bug("unexpanded macro")
    }

    bcx = fcx.pop_and_trans_ast_cleanup_scope(bcx, ast_util::stmt_id(s));

    return bcx;
}

pub fn trans_stmt_semi<'blk, 'tcx>(cx: Block<'blk, 'tcx>, e: &ast::Expr)
                                   -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("trans_stmt_semi");

    if cx.unreachable.get() {
        return cx;
    }

    let ty = expr_ty(cx, e);
    if cx.fcx.type_needs_drop(ty) {
        expr::trans_to_lvalue(cx, e, "stmt").bcx
    } else {
        expr::trans_into(cx, e, expr::Ignore)
    }
}

pub fn trans_block<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                               b: &ast::Block,
                               mut dest: expr::Dest)
                               -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("trans_block");

    if bcx.unreachable.get() {
        return bcx;
    }

    let fcx = bcx.fcx;
    let mut bcx = bcx;

    let cleanup_debug_loc =
        debuginfo::get_cleanup_debug_loc_for_ast_node(bcx.ccx(), b.id, b.span, true);
    fcx.push_ast_cleanup_scope(cleanup_debug_loc);

    for s in &b.stmts {
        bcx = trans_stmt(bcx, &**s);
    }

    if dest != expr::Ignore {
        let block_ty = node_id_type(bcx, b.id);

        if b.expr.is_none() || type_is_zero_size(bcx.ccx(), block_ty) {
            dest = expr::Ignore;
        } else if b.expr.is_some() {
            // If the block has an expression, but that expression isn't reachable,
            // don't save into the destination given, ignore it.
            if let Some(ref cfg) = bcx.fcx.cfg {
                if !cfg.node_is_reachable(b.expr.as_ref().unwrap().id) {
                    dest = expr::Ignore;
                }
            }
        }
    }

    match b.expr {
        Some(ref e) => {
            bcx = expr::trans_into(bcx, &**e, dest);
        }
        None => {
            assert!(dest == expr::Ignore || bcx.unreachable.get());
        }
    }

    bcx = fcx.pop_and_trans_ast_cleanup_scope(bcx, b.id);

    return bcx;
}

pub fn trans_if<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                            if_id: ast::NodeId,
                            cond: &ast::Expr,
                            thn: &ast::Block,
                            els: Option<&ast::Expr>,
                            dest: expr::Dest)
                            -> Block<'blk, 'tcx> {
    debug!("trans_if(bcx={}, if_id={}, cond={}, thn={}, dest={})",
           bcx.to_str(), if_id, bcx.expr_to_string(cond), thn.id,
           dest.to_string(bcx.ccx()));
    let _icx = push_ctxt("trans_if");

    if bcx.unreachable.get() {
        return bcx;
    }

    let mut bcx = bcx;

    let cond_val = unpack_result!(bcx, expr::trans(bcx, cond).to_llbool());

    // Drop branches that are known to be impossible
    if is_const(cond_val) && !is_undef(cond_val) {
        if const_to_uint(cond_val) == 1 {
            match els {
                Some(elexpr) => {
                    let mut trans = TransItemVisitor { ccx: bcx.fcx.ccx };
                    trans.visit_expr(&*elexpr);
                }
                None => {}
            }
            // if true { .. } [else { .. }]
            bcx = trans_block(bcx, &*thn, dest);
            trans::debuginfo::clear_source_location(bcx.fcx);
        } else {
            let mut trans = TransItemVisitor { ccx: bcx.fcx.ccx } ;
            trans.visit_block(&*thn);

            match els {
                // if false { .. } else { .. }
                Some(elexpr) => {
                    bcx = expr::trans_into(bcx, &*elexpr, dest);
                    trans::debuginfo::clear_source_location(bcx.fcx);
                }

                // if false { .. }
                None => { }
            }
        }

        return bcx;
    }

    let name = format!("then-block-{}-", thn.id);
    let then_bcx_in = bcx.fcx.new_id_block(&name[..], thn.id);
    let then_bcx_out = trans_block(then_bcx_in, &*thn, dest);
    trans::debuginfo::clear_source_location(bcx.fcx);

    let cond_source_loc = cond.debug_loc();

    let next_bcx;
    match els {
        Some(elexpr) => {
            let else_bcx_in = bcx.fcx.new_id_block("else-block", elexpr.id);
            let else_bcx_out = expr::trans_into(else_bcx_in, &*elexpr, dest);
            next_bcx = bcx.fcx.join_blocks(if_id,
                                           &[then_bcx_out, else_bcx_out]);
            CondBr(bcx, cond_val, then_bcx_in.llbb, else_bcx_in.llbb, cond_source_loc);
        }

        None => {
            next_bcx = bcx.fcx.new_id_block("next-block", if_id);
            Br(then_bcx_out, next_bcx.llbb, DebugLoc::None);
            CondBr(bcx, cond_val, then_bcx_in.llbb, next_bcx.llbb, cond_source_loc);
        }
    }

    // Clear the source location because it is still set to whatever has been translated
    // right before.
    trans::debuginfo::clear_source_location(next_bcx.fcx);

    next_bcx
}

pub fn trans_while<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                               loop_expr: &ast::Expr,
                               cond: &ast::Expr,
                               body: &ast::Block)
                               -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("trans_while");

    if bcx.unreachable.get() {
        return bcx;
    }

    let fcx = bcx.fcx;

    //            bcx
    //             |
    //         cond_bcx_in  <--------+
    //             |                 |
    //         cond_bcx_out          |
    //           |      |            |
    //           |    body_bcx_in    |
    // cleanup_blk      |            |
    //    |           body_bcx_out --+
    // next_bcx_in

    let next_bcx_in = fcx.new_id_block("while_exit", loop_expr.id);
    let cond_bcx_in = fcx.new_id_block("while_cond", cond.id);
    let body_bcx_in = fcx.new_id_block("while_body", body.id);

    fcx.push_loop_cleanup_scope(loop_expr.id, [next_bcx_in, cond_bcx_in]);

    Br(bcx, cond_bcx_in.llbb, loop_expr.debug_loc());

    // compile the block where we will handle loop cleanups
    let cleanup_llbb = fcx.normal_exit_block(loop_expr.id, cleanup::EXIT_BREAK);

    // compile the condition
    let Result {bcx: cond_bcx_out, val: cond_val} =
        expr::trans(cond_bcx_in, cond).to_llbool();

    CondBr(cond_bcx_out, cond_val, body_bcx_in.llbb, cleanup_llbb, cond.debug_loc());

    // loop body:
    let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore);
    Br(body_bcx_out, cond_bcx_in.llbb, DebugLoc::None);

    fcx.pop_loop_cleanup_scope(loop_expr.id);
    return next_bcx_in;
}

pub fn trans_loop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                              loop_expr: &ast::Expr,
                              body: &ast::Block)
                              -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("trans_loop");

    if bcx.unreachable.get() {
        return bcx;
    }

    let fcx = bcx.fcx;

    //            bcx
    //             |
    //         body_bcx_in
    //             |
    //         body_bcx_out
    //
    // next_bcx
    //
    // Links between body_bcx_in and next_bcx are created by
    // break statements.

    let next_bcx_in = bcx.fcx.new_id_block("loop_exit", loop_expr.id);
    let body_bcx_in = bcx.fcx.new_id_block("loop_body", body.id);

    fcx.push_loop_cleanup_scope(loop_expr.id, [next_bcx_in, body_bcx_in]);

    Br(bcx, body_bcx_in.llbb, loop_expr.debug_loc());
    let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore);
    Br(body_bcx_out, body_bcx_in.llbb, DebugLoc::None);

    fcx.pop_loop_cleanup_scope(loop_expr.id);

    // If there are no predecessors for the next block, we just translated an endless loop and the
    // next block is unreachable
    if BasicBlock(next_bcx_in.llbb).pred_iter().next().is_none() {
        Unreachable(next_bcx_in);
    }

    return next_bcx_in;
}

pub fn trans_break_cont<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                    expr: &ast::Expr,
                                    opt_label: Option<Ident>,
                                    exit: usize)
                                    -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("trans_break_cont");

    if bcx.unreachable.get() {
        return bcx;
    }

    let fcx = bcx.fcx;

    // Locate loop that we will break to
    let loop_id = match opt_label {
        None => fcx.top_loop_scope(),
        Some(_) => {
            match bcx.tcx().def_map.borrow().get(&expr.id).map(|d| d.full_def())  {
                Some(def::DefLabel(loop_id)) => loop_id,
                r => {
                    bcx.tcx().sess.bug(&format!("{:?} in def-map for label", r))
                }
            }
        }
    };

    // Generate appropriate cleanup code and branch
    let cleanup_llbb = fcx.normal_exit_block(loop_id, exit);
    Br(bcx, cleanup_llbb, expr.debug_loc());
    Unreachable(bcx); // anything afterwards should be ignored
    return bcx;
}

pub fn trans_break<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                               expr: &ast::Expr,
                               label_opt: Option<Ident>)
                               -> Block<'blk, 'tcx> {
    return trans_break_cont(bcx, expr, label_opt, cleanup::EXIT_BREAK);
}

pub fn trans_cont<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                              expr: &ast::Expr,
                              label_opt: Option<Ident>)
                              -> Block<'blk, 'tcx> {
    return trans_break_cont(bcx, expr, label_opt, cleanup::EXIT_LOOP);
}

pub fn trans_ret<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                             return_expr: &ast::Expr,
                             retval_expr: Option<&ast::Expr>)
                             -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("trans_ret");

    if bcx.unreachable.get() {
        return bcx;
    }

    let fcx = bcx.fcx;
    let mut bcx = bcx;
    let dest = match (fcx.llretslotptr.get(), retval_expr) {
        (Some(_), Some(retval_expr)) => {
            let ret_ty = expr_ty_adjusted(bcx, &*retval_expr);
            expr::SaveIn(fcx.get_ret_slot(bcx, ty::FnConverging(ret_ty), "ret_slot"))
        }
        _ => expr::Ignore,
    };
    if let Some(x) = retval_expr {
        bcx = expr::trans_into(bcx, &*x, dest);
        match dest {
            expr::SaveIn(slot) if fcx.needs_ret_allocas => {
                Store(bcx, slot, fcx.llretslotptr.get().unwrap());
            }
            _ => {}
        }
    }
    let cleanup_llbb = fcx.return_exit_block();
    Br(bcx, cleanup_llbb, return_expr.debug_loc());
    Unreachable(bcx);
    return bcx;
}

pub fn trans_fail<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                              call_info: NodeIdAndSpan,
                              fail_str: InternedString)
                              -> Block<'blk, 'tcx> {
    let ccx = bcx.ccx();
    let _icx = push_ctxt("trans_fail_value");

    if bcx.unreachable.get() {
        return bcx;
    }

    let v_str = C_str_slice(ccx, fail_str);
    let loc = bcx.sess().codemap().lookup_char_pos(call_info.span.lo);
    let filename = token::intern_and_get_ident(&loc.file.name);
    let filename = C_str_slice(ccx, filename);
    let line = C_u32(ccx, loc.line as u32);
    let expr_file_line_const = C_struct(ccx, &[v_str, filename, line], false);
    let expr_file_line = consts::addr_of(ccx, expr_file_line_const,
                                         "panic_loc", call_info.id);
    let args = vec!(expr_file_line);
    let did = langcall(bcx, Some(call_info.span), "", PanicFnLangItem);
    let bcx = callee::trans_lang_call(bcx,
                                      did,
                                      &args[..],
                                      Some(expr::Ignore),
                                      call_info.debug_loc()).bcx;
    Unreachable(bcx);
    return bcx;
}

pub fn trans_fail_bounds_check<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                           call_info: NodeIdAndSpan,
                                           index: ValueRef,
                                           len: ValueRef)
                                           -> Block<'blk, 'tcx> {
    let ccx = bcx.ccx();
    let _icx = push_ctxt("trans_fail_bounds_check");

    if bcx.unreachable.get() {
        return bcx;
    }

    // Extract the file/line from the span
    let loc = bcx.sess().codemap().lookup_char_pos(call_info.span.lo);
    let filename = token::intern_and_get_ident(&loc.file.name);

    // Invoke the lang item
    let filename = C_str_slice(ccx,  filename);
    let line = C_u32(ccx, loc.line as u32);
    let file_line_const = C_struct(ccx, &[filename, line], false);
    let file_line = consts::addr_of(ccx, file_line_const,
                                    "panic_bounds_check_loc", call_info.id);
    let args = vec!(file_line, index, len);
    let did = langcall(bcx, Some(call_info.span), "", PanicBoundsCheckFnLangItem);
    let bcx = callee::trans_lang_call(bcx,
                                      did,
                                      &args[..],
                                      Some(expr::Ignore),
                                      call_info.debug_loc()).bcx;
    Unreachable(bcx);
    return bcx;
}
