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

#![allow(non_camel_case_types)]

use back::abi;
use llvm;
use llvm::{ValueRef};
use trans::base::*;
use trans::base;
use trans::build::*;
use trans::cleanup;
use trans::cleanup::CleanupMethods;
use trans::common::*;
use trans::consts;
use trans::datum::*;
use trans::debuginfo::DebugLoc;
use trans::expr::{Dest, Ignore, SaveIn};
use trans::expr;
use trans::glue;
use trans::machine;
use trans::machine::llsize_of_alloc;
use trans::type_::Type;
use trans::type_of;
use middle::ty::{self, Ty};
use util::ppaux::ty_to_string;

use syntax::ast;
use syntax::parse::token::InternedString;

fn get_len(bcx: Block, vptr: ValueRef) -> ValueRef {
    let _icx = push_ctxt("tvec::get_lenl");
    Load(bcx, expr::get_len(bcx, vptr))
}

fn get_dataptr(bcx: Block, vptr: ValueRef) -> ValueRef {
    let _icx = push_ctxt("tvec::get_dataptr");
    Load(bcx, expr::get_dataptr(bcx, vptr))
}

pub fn make_drop_glue_unboxed<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                          vptr: ValueRef,
                                          unit_ty: Ty<'tcx>,
                                          should_deallocate: bool)
                                          -> Block<'blk, 'tcx> {
    let not_null = IsNotNull(bcx, vptr);
    with_cond(bcx, not_null, |bcx| {
        let ccx = bcx.ccx();
        let tcx = bcx.tcx();
        let _icx = push_ctxt("tvec::make_drop_glue_unboxed");

        let dataptr = get_dataptr(bcx, vptr);
        let bcx = if type_needs_drop(tcx, unit_ty) {
            let len = get_len(bcx, vptr);
            iter_vec_raw(bcx,
                         dataptr,
                         unit_ty,
                         len,
                         |bb, vv, tt| glue::drop_ty(bb, vv, tt, DebugLoc::None))
        } else {
            bcx
        };

        if should_deallocate {
            let llty = type_of::type_of(ccx, unit_ty);
            let unit_size = llsize_of_alloc(ccx, llty);
            if unit_size != 0 {
                let len = get_len(bcx, vptr);
                let not_empty = ICmp(bcx,
                                     llvm::IntNE,
                                     len,
                                     C_uint(ccx, 0us),
                                     DebugLoc::None);
                with_cond(bcx, not_empty, |bcx| {
                    let llalign = C_uint(ccx, machine::llalign_of_min(ccx, llty));
                    let size = Mul(bcx, C_uint(ccx, unit_size), len, DebugLoc::None);
                    glue::trans_exchange_free_dyn(bcx,
                                                  dataptr,
                                                  size,
                                                  llalign,
                                                  DebugLoc::None)
                })
            } else {
                bcx
            }
        } else {
            bcx
        }
    })
}

#[derive(Copy)]
pub struct VecTypes<'tcx> {
    pub unit_ty: Ty<'tcx>,
    pub llunit_ty: Type,
    pub llunit_alloc_size: u64
}

impl<'tcx> VecTypes<'tcx> {
    pub fn to_string<'a>(&self, ccx: &CrateContext<'a, 'tcx>) -> String {
        format!("VecTypes {{unit_ty={}, llunit_ty={}, llunit_alloc_size={}}}",
                ty_to_string(ccx.tcx(), self.unit_ty),
                ccx.tn().type_to_string(self.llunit_ty),
                self.llunit_alloc_size)
    }
}

pub fn trans_fixed_vstore<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                      expr: &ast::Expr,
                                      dest: expr::Dest)
                                      -> Block<'blk, 'tcx> {
    //!
    //
    // [...] allocates a fixed-size array and moves it around "by value".
    // In this case, it means that the caller has already given us a location
    // to store the array of the suitable size, so all we have to do is
    // generate the content.

    debug!("trans_fixed_vstore(expr={}, dest={})",
           bcx.expr_to_string(expr), dest.to_string(bcx.ccx()));

    let vt = vec_types_from_expr(bcx, expr);

    return match dest {
        Ignore => write_content(bcx, &vt, expr, expr, dest),
        SaveIn(lldest) => {
            // lldest will have type *[T x N], but we want the type *T,
            // so use GEP to convert:
            let lldest = GEPi(bcx, lldest, &[0, 0]);
            write_content(bcx, &vt, expr, expr, SaveIn(lldest))
        }
    };
}

/// &[...] allocates memory on the stack and writes the values into it, returning the vector (the
/// caller must make the reference).  "..." is similar except that the memory can be statically
/// allocated and we return a reference (strings are always by-ref).
pub fn trans_slice_vec<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                   slice_expr: &ast::Expr,
                                   content_expr: &ast::Expr)
                                   -> DatumBlock<'blk, 'tcx, Expr> {
    let fcx = bcx.fcx;
    let ccx = fcx.ccx;
    let mut bcx = bcx;

    debug!("trans_slice_vec(slice_expr={})",
           bcx.expr_to_string(slice_expr));

    let vec_ty = node_id_type(bcx, slice_expr.id);

    // Handle the "..." case (returns a slice since strings are always unsized):
    if let ast::ExprLit(ref lit) = content_expr.node {
        if let ast::LitStr(ref s, _) = lit.node {
            let scratch = rvalue_scratch_datum(bcx, vec_ty, "");
            bcx = trans_lit_str(bcx,
                                content_expr,
                                s.clone(),
                                SaveIn(scratch.val));
            return DatumBlock::new(bcx, scratch.to_expr_datum());
        }
    }

    // Handle the &[...] case:
    let vt = vec_types_from_expr(bcx, content_expr);
    let count = elements_required(bcx, content_expr);
    debug!("    vt={}, count={}", vt.to_string(ccx), count);
    let llcount = C_uint(ccx, count);

    let fixed_ty = ty::mk_vec(bcx.tcx(),
                              vt.unit_ty,
                              Some(count));
    let llfixed_ty = type_of::type_of(bcx.ccx(), fixed_ty).ptr_to();

    let llfixed = if count == 0 {
        // Just create a zero-sized alloca to preserve
        // the non-null invariant of the inner slice ptr
        let llfixed = base::arrayalloca(bcx, vt.llunit_ty, llcount);
        BitCast(bcx, llfixed, llfixed_ty)
    } else {
        // Make a fixed-length backing array and allocate it on the stack.
        let llfixed = base::arrayalloca(bcx, vt.llunit_ty, llcount);

        // Arrange for the backing array to be cleaned up.
        let llfixed_casted = BitCast(bcx, llfixed, llfixed_ty);
        let cleanup_scope = cleanup::temporary_scope(bcx.tcx(), content_expr.id);
        fcx.schedule_lifetime_end(cleanup_scope, llfixed_casted);
        fcx.schedule_drop_mem(cleanup_scope, llfixed_casted, fixed_ty);

        // Generate the content into the backing array.
        bcx = write_content(bcx, &vt, slice_expr,
                            content_expr, SaveIn(llfixed));

        llfixed_casted
    };

    immediate_rvalue_bcx(bcx, llfixed, vec_ty).to_expr_datumblock()
}

/// Literal strings translate to slices into static memory.  This is different from
/// trans_slice_vstore() above because it doesn't need to copy the content anywhere.
pub fn trans_lit_str<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                 lit_expr: &ast::Expr,
                                 str_lit: InternedString,
                                 dest: Dest)
                                 -> Block<'blk, 'tcx> {
    debug!("trans_lit_str(lit_expr={}, dest={})",
           bcx.expr_to_string(lit_expr),
           dest.to_string(bcx.ccx()));

    match dest {
        Ignore => bcx,
        SaveIn(lldest) => {
            let bytes = str_lit.len();
            let llbytes = C_uint(bcx.ccx(), bytes);
            let llcstr = C_cstr(bcx.ccx(), str_lit, false);
            let llcstr = consts::ptrcast(llcstr, Type::i8p(bcx.ccx()));
            Store(bcx, llcstr, GEPi(bcx, lldest, &[0, abi::FAT_PTR_ADDR]));
            Store(bcx, llbytes, GEPi(bcx, lldest, &[0, abi::FAT_PTR_EXTRA]));
            bcx
        }
    }
}

pub fn write_content<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                 vt: &VecTypes<'tcx>,
                                 vstore_expr: &ast::Expr,
                                 content_expr: &ast::Expr,
                                 dest: Dest)
                                 -> Block<'blk, 'tcx> {
    let _icx = push_ctxt("tvec::write_content");
    let fcx = bcx.fcx;
    let mut bcx = bcx;

    debug!("write_content(vt={}, dest={}, vstore_expr={})",
           vt.to_string(bcx.ccx()),
           dest.to_string(bcx.ccx()),
           bcx.expr_to_string(vstore_expr));

    match content_expr.node {
        ast::ExprLit(ref lit) => {
            match lit.node {
                ast::LitStr(ref s, _) => {
                    match dest {
                        Ignore => return bcx,
                        SaveIn(lldest) => {
                            let bytes = s.len();
                            let llbytes = C_uint(bcx.ccx(), bytes);
                            let llcstr = C_cstr(bcx.ccx(), (*s).clone(), false);
                            base::call_memcpy(bcx,
                                              lldest,
                                              llcstr,
                                              llbytes,
                                              1);
                            return bcx;
                        }
                    }
                }
                _ => {
                    bcx.tcx().sess.span_bug(content_expr.span,
                                            "unexpected evec content");
                }
            }
        }
        ast::ExprVec(ref elements) => {
            match dest {
                Ignore => {
                    for element in elements {
                        bcx = expr::trans_into(bcx, &**element, Ignore);
                    }
                }

                SaveIn(lldest) => {
                    let temp_scope = fcx.push_custom_cleanup_scope();
                    for (i, element) in elements.iter().enumerate() {
                        let lleltptr = GEPi(bcx, lldest, &[i]);
                        debug!("writing index {} with lleltptr={}",
                               i, bcx.val_to_string(lleltptr));
                        bcx = expr::trans_into(bcx, &**element,
                                               SaveIn(lleltptr));
                        let scope = cleanup::CustomScope(temp_scope);
                        fcx.schedule_lifetime_end(scope, lleltptr);
                        fcx.schedule_drop_mem(scope, lleltptr, vt.unit_ty);
                    }
                    fcx.pop_custom_cleanup_scope(temp_scope);
                }
            }
            return bcx;
        }
        ast::ExprRepeat(ref element, ref count_expr) => {
            match dest {
                Ignore => {
                    return expr::trans_into(bcx, &**element, Ignore);
                }
                SaveIn(lldest) => {
                    match ty::eval_repeat_count(bcx.tcx(), &**count_expr) {
                        0 => bcx,
                        1 => expr::trans_into(bcx, &**element, SaveIn(lldest)),
                        count => {
                            let elem = unpack_datum!(bcx, expr::trans(bcx, &**element));
                            let bcx = iter_vec_loop(bcx, lldest, vt,
                                                    C_uint(bcx.ccx(), count),
                                                    |set_bcx, lleltptr, _| {
                                                        elem.shallow_copy(set_bcx, lleltptr)
                                                    });
                            bcx
                        }
                    }
                }
            }
        }
        _ => {
            bcx.tcx().sess.span_bug(content_expr.span,
                                    "unexpected vec content");
        }
    }
}

pub fn vec_types_from_expr<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                                       vec_expr: &ast::Expr)
                                       -> VecTypes<'tcx> {
    let vec_ty = node_id_type(bcx, vec_expr.id);
    vec_types(bcx, ty::sequence_element_type(bcx.tcx(), vec_ty))
}

pub fn vec_types<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
                             unit_ty: Ty<'tcx>)
                             -> VecTypes<'tcx> {
    let ccx = bcx.ccx();
    let llunit_ty = type_of::type_of(ccx, unit_ty);
    let llunit_alloc_size = llsize_of_alloc(ccx, llunit_ty);

    VecTypes {
        unit_ty: unit_ty,
        llunit_ty: llunit_ty,
        llunit_alloc_size: llunit_alloc_size
    }
}

pub fn elements_required(bcx: Block, content_expr: &ast::Expr) -> uint {
    //! Figure out the number of elements we need to store this content

    match content_expr.node {
        ast::ExprLit(ref lit) => {
            match lit.node {
                ast::LitStr(ref s, _) => s.len(),
                _ => {
                    bcx.tcx().sess.span_bug(content_expr.span,
                                            "unexpected evec content")
                }
            }
        },
        ast::ExprVec(ref es) => es.len(),
        ast::ExprRepeat(_, ref count_expr) => {
            ty::eval_repeat_count(bcx.tcx(), &**count_expr)
        }
        _ => bcx.tcx().sess.span_bug(content_expr.span,
                                     "unexpected vec content")
    }
}

/// Converts a fixed-length vector into the slice pair. The vector should be stored in `llval`
/// which should be by ref.
pub fn get_fixed_base_and_len(bcx: Block,
                              llval: ValueRef,
                              vec_length: uint)
                              -> (ValueRef, ValueRef) {
    let ccx = bcx.ccx();

    let base = expr::get_dataptr(bcx, llval);
    let len = C_uint(ccx, vec_length);
    (base, len)
}

fn get_slice_base_and_len(bcx: Block,
                          llval: ValueRef)
                          -> (ValueRef, ValueRef) {
    let base = Load(bcx, GEPi(bcx, llval, &[0, abi::FAT_PTR_ADDR]));
    let len = Load(bcx, GEPi(bcx, llval, &[0, abi::FAT_PTR_EXTRA]));
    (base, len)
}

/// Converts a vector into the slice pair.  The vector should be stored in `llval` which should be
/// by-reference.  If you have a datum, you would probably prefer to call
/// `Datum::get_base_and_len()` which will handle any conversions for you.
pub fn get_base_and_len(bcx: Block,
                        llval: ValueRef,
                        vec_ty: Ty)
                        -> (ValueRef, ValueRef) {
    let ccx = bcx.ccx();

    match vec_ty.sty {
        ty::ty_vec(_, Some(n)) => get_fixed_base_and_len(bcx, llval, n),
        ty::ty_open(ty) => match ty.sty {
            ty::ty_vec(_, None) | ty::ty_str => get_slice_base_and_len(bcx, llval),
            _ => ccx.sess().bug("unexpected type in get_base_and_len")
        },

        // Only used for pattern matching.
        ty::ty_uniq(ty) | ty::ty_rptr(_, ty::mt{ty, ..}) => match ty.sty {
            ty::ty_vec(_, None) | ty::ty_str => get_slice_base_and_len(bcx, llval),
            ty::ty_vec(_, Some(n)) => {
                let base = GEPi(bcx, Load(bcx, llval), &[0, 0]);
                (base, C_uint(ccx, n))
            }
            _ => ccx.sess().bug("unexpected type in get_base_and_len"),
        },
        _ => ccx.sess().bug("unexpected type in get_base_and_len"),
    }
}

pub fn iter_vec_loop<'blk, 'tcx, F>(bcx: Block<'blk, 'tcx>,
                                     data_ptr: ValueRef,
                                     vt: &VecTypes<'tcx>,
                                     count: ValueRef,
                                     f: F)
                                     -> Block<'blk, 'tcx> where
    F: FnOnce(Block<'blk, 'tcx>, ValueRef, Ty<'tcx>) -> Block<'blk, 'tcx>,
{
    let _icx = push_ctxt("tvec::iter_vec_loop");
    let fcx = bcx.fcx;

    let next_bcx = fcx.new_temp_block("expr_repeat: while next");
    let loop_bcx = fcx.new_temp_block("expr_repeat");
    let cond_bcx = fcx.new_temp_block("expr_repeat: loop cond");
    let body_bcx = fcx.new_temp_block("expr_repeat: body: set");
    let inc_bcx = fcx.new_temp_block("expr_repeat: body: inc");
    Br(bcx, loop_bcx.llbb, DebugLoc::None);

    let loop_counter = {
        // i = 0
        let i = alloca(loop_bcx, bcx.ccx().int_type(), "__i");
        Store(loop_bcx, C_uint(bcx.ccx(), 0us), i);

        Br(loop_bcx, cond_bcx.llbb, DebugLoc::None);
        i
    };

    { // i < count
        let lhs = Load(cond_bcx, loop_counter);
        let rhs = count;
        let cond_val = ICmp(cond_bcx, llvm::IntULT, lhs, rhs, DebugLoc::None);

        CondBr(cond_bcx, cond_val, body_bcx.llbb, next_bcx.llbb, DebugLoc::None);
    }

    { // loop body
        let i = Load(body_bcx, loop_counter);
        let lleltptr = if vt.llunit_alloc_size == 0 {
            data_ptr
        } else {
            InBoundsGEP(body_bcx, data_ptr, &[i])
        };
        let body_bcx = f(body_bcx, lleltptr, vt.unit_ty);

        Br(body_bcx, inc_bcx.llbb, DebugLoc::None);
    }

    { // i += 1
        let i = Load(inc_bcx, loop_counter);
        let plusone = Add(inc_bcx, i, C_uint(bcx.ccx(), 1us), DebugLoc::None);
        Store(inc_bcx, plusone, loop_counter);

        Br(inc_bcx, cond_bcx.llbb, DebugLoc::None);
    }

    next_bcx
}

pub fn iter_vec_raw<'blk, 'tcx, F>(bcx: Block<'blk, 'tcx>,
                                   data_ptr: ValueRef,
                                   unit_ty: Ty<'tcx>,
                                   len: ValueRef,
                                   f: F)
                                   -> Block<'blk, 'tcx> where
    F: FnOnce(Block<'blk, 'tcx>, ValueRef, Ty<'tcx>) -> Block<'blk, 'tcx>,
{
    let _icx = push_ctxt("tvec::iter_vec_raw");
    let fcx = bcx.fcx;

    let vt = vec_types(bcx, unit_ty);

    if vt.llunit_alloc_size == 0 {
        // Special-case vectors with elements of size 0  so they don't go out of bounds (#9890)
        iter_vec_loop(bcx, data_ptr, &vt, len, f)
    } else {
        // Calculate the last pointer address we want to handle.
        let data_end_ptr = InBoundsGEP(bcx, data_ptr, &[len]);

        // Now perform the iteration.
        let header_bcx = fcx.new_temp_block("iter_vec_loop_header");
        Br(bcx, header_bcx.llbb, DebugLoc::None);
        let data_ptr =
            Phi(header_bcx, val_ty(data_ptr), &[data_ptr], &[bcx.llbb]);
        let not_yet_at_end =
            ICmp(header_bcx, llvm::IntULT, data_ptr, data_end_ptr, DebugLoc::None);
        let body_bcx = fcx.new_temp_block("iter_vec_loop_body");
        let next_bcx = fcx.new_temp_block("iter_vec_next");
        CondBr(header_bcx, not_yet_at_end, body_bcx.llbb, next_bcx.llbb, DebugLoc::None);
        let body_bcx = f(body_bcx, data_ptr, vt.unit_ty);
        AddIncomingToPhi(data_ptr, InBoundsGEP(body_bcx, data_ptr,
                                               &[C_int(bcx.ccx(), 1)]),
                         body_bcx.llbb);
        Br(body_bcx, header_bcx.llbb, DebugLoc::None);
        next_bcx
    }
}
