/*******************************************************************\

Module: Program Transformation

Author: Daniel Kroening, kroening@cs.cmu.edu

\*******************************************************************/

#include <assert.h>

#include <i2string.h>
#include <replace_expr.h>
#include <expr_util.h>
#include <location.h>
#include <cprover_prefix.h>
#include <prefix.h>
#include <arith_tools.h>
#include <simplify_expr.h>

#include <ansi-c/c_types.h>

#include "goto_convert_class.h"

/*******************************************************************\

Function: get_alloc_type_rec

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

static void get_alloc_type_rec(
  const exprt &src,
  typet &type,
  exprt &size)
{
  const irept &sizeof_type=src.find("#c_sizeof_type");

  if(!sizeof_type.is_nil())
  {
    type=(typet &)sizeof_type;
  }
  else if(src.id()=="*")
  {
    forall_operands(it, src)
      get_alloc_type_rec(*it, type, size);
  }
  else
  {
    size.copy_to_operands(src);
  }
}

/*******************************************************************\

Function: get_alloc_type

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

static void get_alloc_type(
  const exprt &src,
  typet &type,
  exprt &size)
{
  type.make_nil();
  size.make_nil();

  get_alloc_type_rec(src, type, size);

  if(type.is_nil())
    type=char_type();

  if(size.has_operands())
  {
    if(size.operands().size()==1)
    {
      exprt tmp;
      tmp.swap(size.op0());
      size.swap(tmp);
    }
    else
    {
      size.id("*");
      size.type()=size.op0().type();
    }
  }
}

/*******************************************************************\

Function: goto_convertt::do_create_thread

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_create_thread(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(rhs.operands().size()!=5)
  {
    err_location(lhs);
    throw "thread_create takes four arguments";
  }

  codet functioncall("functioncall");
  functioncall.move_to_operands(rhs.operands()[3],
                                rhs.operands()[4]);

  if(functioncall.op0().type().id()!="pointer")
  {
    err_location(functioncall.op0());
    throw "create_thread expects function pointer as third argument";
  }

  // do dereferencing  
  if(functioncall.op0().id()=="implicit_address_of" ||
     functioncall.op0().id()=="address_of")
  {
    exprt tmp;
    assert(functioncall.op0().operands().size()==1);
    tmp.swap(functioncall.op0().op0());
    functioncall.op0().swap(tmp);
  }
  else
  {
    exprt tmp("dereference", functioncall.op0().type().subtype());
    tmp.move_to_operands(functioncall.op0());
    functioncall.op0().swap(tmp);
  }
  
  codet start_thread("start_thread");
  start_thread.move_to_operands(functioncall);
  
  convert(start_thread, dest);
}

/*******************************************************************\

Function: goto_convertt::do_printf

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_printf(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  exprt printf_code("sideeffect", rhs.type());
  printf_code.set("statement", "printf");

  for(unsigned i=1; i<rhs.operands().size(); i++)
    printf_code.move_to_operands(rhs.operands()[i]);

  printf_code.location()=rhs.location();

  if(lhs.is_not_nil())
  {
    code_assignt assignment;
    assignment.move_to_operands(lhs, printf_code);
    assignment.location()=rhs.location();
    move(assignment, dest);
  }
  else
  {
    printf_code.id("code");
    printf_code.type()=typet("code");
    move(to_code(printf_code), dest);
  }
}

/*******************************************************************\

Function: goto_convertt::do_atomic_begin

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_atomic_begin(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(lhs.is_not_nil())
  {
    err_location(lhs);
    throw "atomic_begin does not expect an LHS";
  }

  if(rhs.operands().size()!=1)
  {
    err_location(rhs);
    throw "atomic_begin takes no arguments";
  }

  goto_programt::targett t=dest.add_instruction(ATOMIC_BEGIN);
  set_location(rhs, *t);
}

/*******************************************************************\

Function: goto_convertt::do_atomic_end

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_atomic_end(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(lhs.is_not_nil())
  {
    err_location(lhs);
    throw "atomic_end does not expect an LHS";
  }

  if(rhs.operands().size()!=1)
  {
    err_location(rhs);
    throw "atomic_end takes no arguments";
  }

  goto_programt::targett t=dest.add_instruction(ATOMIC_END);
  set_location(rhs, *t);
}

/*******************************************************************\

Function: goto_convertt::do_event

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_event(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(rhs.operands().size()!=2)
  {
    err_location(rhs);
    throw "event expected to have one operand";
  }

  goto_programt::targett t=dest.add_instruction(SYNC);
  t->event=get_string_constant(rhs.op1());
  set_location(rhs, *t);

  if(lhs.is_not_nil())
  {
    err_location(rhs);
    throw "event expected not to have LHS";
  }
}

/*******************************************************************\

Function: goto_convertt::do_malloc

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_malloc(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(rhs.operands().size()!=2)
  {
    err_location(rhs);
    throw "malloc expected to have one operand";
  }

  if(lhs.is_nil())
    return; // does nothing
    
  locationt location=rhs.location();
    
  // get alloc type and size
  typet alloc_type;
  exprt alloc_size;
  
  get_alloc_type(rhs.op1(), alloc_type, alloc_size);
  
  if(alloc_size.is_nil())
    alloc_size=from_integer(1, uint_type());
    
  if(alloc_type.is_nil())
    alloc_type=char_type();

  if(alloc_size.type()!=uint_type())
  {
    alloc_size.make_typecast(uint_type());
    simplify(alloc_size);
  }

  // produce new object

  exprt new_expr("sideeffect", lhs.type());
  new_expr.set("statement", "malloc");
  new_expr.move_to_operands(rhs.op1());
  new_expr.set("#size", alloc_size);
  new_expr.set("#type", alloc_type);
  new_expr.location()=location;

  goto_programt::targett t_n=dest.add_instruction(OTHER);
  
  t_n->code=code_assignt(lhs, new_expr);
  t_n->location=location;

  // set up some expressions
  exprt valid_expr("valid_object", typet("bool"));
  valid_expr.copy_to_operands(lhs);
  valid_expr.location()=location;
  exprt neg_valid_expr=gen_not(valid_expr);
  
  exprt is_dynamic_expr("is_dynamic_object", typet("bool"));
  is_dynamic_expr.location()=location;
  is_dynamic_expr.copy_to_operands(lhs);
  
  exprt pointer_offset_expr("pointer_offset", int_type());
  pointer_offset_expr.location()=location;
  pointer_offset_expr.copy_to_operands(lhs);
  
  exprt offset_is_zero_expr("=", typet("bool"));
  offset_is_zero_expr.reserve_operands(2);
  offset_is_zero_expr.copy_to_operands(pointer_offset_expr);
  offset_is_zero_expr.copy_to_operands(gen_zero(int_type()));
  
  // first assume that it's available and that it's a dynamic object
  goto_programt::targett t_a=dest.add_instruction(ASSUME);

  t_a->guard=gen_and(neg_valid_expr, is_dynamic_expr, offset_is_zero_expr);
                
  // set size
  exprt dynamic_size("dynamic_size", uint_type());
  dynamic_size.copy_to_operands(lhs);
  dynamic_size.location()=location;
  goto_programt::targett t_s_s=dest.add_instruction(OTHER);
  t_s_s->code=code_assignt(dynamic_size, alloc_size);
  t_s_s->location=location;
                
  // now set alloc bit
  goto_programt::targett t_s_a=dest.add_instruction(OTHER);
  t_s_a->code=code_assignt(valid_expr, gen_true());
  t_s_a->location=location;
}

/*******************************************************************\

Function: goto_convertt::do_cpp_new

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_cpp_new(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(lhs.is_nil())
  {
    // TODO
    assert(0);
  }
  
  // grab initializer
  goto_programt tmp_initializer;
  cpp_new_initializer(lhs, rhs, tmp_initializer);

  // produce new object
  goto_programt::targett t_n=dest.add_instruction(OTHER);
  
  t_n->code=code_assignt(lhs, rhs);
  t_n->location=rhs.find_location();

  // set up some expressions
  exprt valid_expr("valid_object", typet("bool"));
  valid_expr.copy_to_operands(lhs);
  
  // first assume that it's available
  goto_programt::targett t_a=dest.add_instruction(ASSUME);

  t_a->guard=valid_expr;
  t_a->guard.make_not();
  
  exprt alloc_size;
  
  if(rhs.get("statement")=="cpp_new[]")
  {
    alloc_size=static_cast<const exprt &>(rhs.find("size"));
    if(alloc_size.type()!=uint_type())
      alloc_size.make_typecast(uint_type());
  }
  else
    alloc_size=from_integer(1, uint_type());
                
  // set size
  exprt dynamic_size("dynamic_size", uint_type());
  dynamic_size.copy_to_operands(lhs);
  dynamic_size.location()=rhs.find_location();
  goto_programt::targett t_s_s=dest.add_instruction(OTHER);
  t_s_s->code=code_assignt(dynamic_size, alloc_size);
  t_s_s->location=rhs.find_location();
                
  // now set alloc bit
  goto_programt::targett t_s_a=dest.add_instruction(OTHER);
  t_s_a->code=code_assignt(valid_expr, gen_true());
  t_s_a->location=rhs.find_location();
  
  // run initializer
  dest.destructive_append(tmp_initializer);
}

/*******************************************************************\

Function: goto_convertt::cpp_new_initializer

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::cpp_new_initializer(
  const exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  // grab initializer
  exprt initializer;
  
  if(rhs.find("initializer").is_nil())
    initializer.make_nil();
  else
  {
    initializer.make_nil();
    rhs.add("initializer").swap(initializer);
  }

  if(initializer.is_not_nil())
  {
    if(rhs.id()=="cpp_new[]")
    {
      // build loop
    }
    else // cpp_new
    {
      exprt deref_new("dereference", rhs.type().subtype());
      deref_new.copy_to_operands(lhs);
      replace_new_object(deref_new, initializer);
      convert(to_code(initializer), dest);
    }
  }
}

/*******************************************************************\

Function: goto_convertt::do_exit

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_exit(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(rhs.operands().size()!=2)
  {
    err_location(rhs);
    throw "exit expected to have one operand";
  }

  // same as assume(false)

  goto_programt::targett t_a=dest.add_instruction(ASSUME);
  t_a->guard=gen_false();
  t_a->location=rhs.find_location();
}

/*******************************************************************\

Function: goto_convertt::do_array_set

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_array_set(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(rhs.operands().size()!=3)
    throw "array_set expects two arguments";
  
  const exprt &array_ptr=rhs.op1();
  const exprt &value=rhs.op2();

  if(array_ptr.id()!="implicit_address_of")
    throw "array_set expects array-pointer as first argument";

  if(array_ptr.op0().type().id()!="array")
    throw "array_set expects array as first argument";
    
  const exprt &array=array_ptr.op0();

  exprt assignment_rhs("array_of", array.type());
  assignment_rhs.copy_to_operands(value);
    
  codet assignment("assign");

  assignment.reserve_operands(2);
  assignment.copy_to_operands(array);
  assignment.move_to_operands(assignment_rhs);

  convert(assignment, dest);
}

/*******************************************************************\

Function: goto_convertt::do_free

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void goto_convertt::do_free(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  if(rhs.operands().size()!=2)
  {
    err_location(rhs);
    throw "free expected to have one operand";
  }

  if(lhs.is_not_nil())
  {
    err_location(rhs);
    throw "free is expected not to have LHS";
  }

  exprt valid_expr("valid_object", typet("bool"));
  valid_expr.copy_to_operands(rhs.op1());
  
  exprt dynamic_expr("is_dynamic_object", typet("bool"));
  dynamic_expr.copy_to_operands(rhs.op1());
  
  // first assert that it's not yet freed
  
  if(options.get_bool_option("pointer-check"))
  {
    goto_programt::targett t_a=dest.add_instruction(ASSERT);

    t_a->guard=gen_and(dynamic_expr, valid_expr);
    t_a->location=rhs.find_location();
    t_a->location.set("comment", "free() of invalid object");
    t_a->location.set("property", "pointer");
  }
  
  // TODO: maybe assert that offset is zero
                
  // now clear alloc bit

  goto_programt::targett t_c=dest.add_instruction(OTHER);

  t_c->code=code_assignt(valid_expr, gen_false());
}

/*******************************************************************\

Function: goto_convertt::do_functioncall_symbol

  Inputs:

 Outputs:

 Purpose: add function calls to function queue for later
          processing

\*******************************************************************/

void goto_convertt::do_functioncall_symbol(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  exprt &function=rhs.op0();
  
  if(function.get_bool("#invalid_object"))
    return; // ignore

  // lookup symbol
  const irep_idt &identifier=function.get("identifier");

  const symbolt *symbol;
  if(ns.lookup(identifier, symbol))
  {
    err_location(function);
    throw "error: function `"+id2string(identifier)+"' not found";
  }

  if(symbol->type.id()!="code")
  {
    err_location(function);
    throw "error: function `"+id2string(identifier)+"' type mismatch: expected code";
  }
  
  bool is_assume=identifier==CPROVER_PREFIX "assume" ||
                 identifier=="specc::__CPROVER_assume";

  bool is_assert=identifier=="c::assert" ||
                 identifier=="specc::assert";

  if(is_assume || is_assert)
  {
    if(rhs.operands().size()!=2)
    {
      err_location(rhs);
      throw "`"+id2string(identifier)+"' expected to have one operand";
    }

    if(!options.get_bool_option("assertions"))
      return;

    goto_programt::targett t=dest.add_instruction(
      is_assume?ASSUME:ASSERT);
    t->guard.swap(rhs.op1());
    set_location(rhs, *t);

    t->location.set("user-provided", true);

    if(is_assert) t->location.set("property", "assertion");
    
    if(lhs.is_not_nil())
    {
      err_location(rhs);
      throw id2string(identifier)+" expected not to have LHS";
    }

    return;
  }
  else if(identifier==CPROVER_PREFIX "assert")
  {
    if(rhs.operands().size()!=3)
    {
      err_location(rhs);
      throw "`"+id2string(identifier)+"' expected to have two operands";
    }
    
    const std::string &description=get_string_constant(rhs.op2());

    if(!options.get_bool_option("assertions"))
      return;

    goto_programt::targett t=dest.add_instruction(ASSERT);
    t->guard.swap(rhs.op1());
    set_location(rhs, *t);

    t->location.set("user-provided", true);
    t->location.set("property", "assertion");
    t->location.set("comment", description);
    
    if(lhs.is_not_nil())
    {
      err_location(rhs);
      throw id2string(identifier)+" expected not to have LHS";
    }

    return;          
  }
  else if(identifier==CPROVER_PREFIX "event")
  {
    do_event(lhs, rhs, dest);
    return;          
  }
  else if(identifier==CPROVER_PREFIX "create_thread")
  {
    do_create_thread(lhs, rhs, dest);
    return;
  }
  else if(identifier==CPROVER_PREFIX "printf")
  {
    do_printf(lhs, rhs, dest);
    return;
  }
  else if(identifier==CPROVER_PREFIX "atomic_begin")
  {
    do_atomic_begin(lhs, rhs, dest);
    return;
  }
  else if(identifier==CPROVER_PREFIX "atomic_end")
  {
    do_atomic_end(lhs, rhs, dest);
    return;
  }
  else if(has_prefix(id2string(identifier), "c::nondet_") ||
          has_prefix(id2string(identifier), "cpp::nondet_"))
  {
    // make it a side effect
    locationt l=rhs.find_location();
    rhs=exprt("sideeffect", rhs.type());
    rhs.set("statement", "nondet");
    rhs.location()=l;
  }
  else if(has_prefix(id2string(identifier), CPROVER_PREFIX "array_set"))
  {
    do_array_set(lhs, rhs, dest);
    return;
  }
  else if(identifier=="c::exit")
  {
    do_exit(lhs, rhs, dest);
    return;
  }
  else if(identifier=="c::malloc")
  {
    do_malloc(lhs, rhs, dest);
    return;
  }
  else if(identifier=="c::free")
  {
    do_free(lhs, rhs, dest);
    return;          
  }
  else if(identifier==CPROVER_PREFIX "POINTER_OFFSET")
  {
    if(rhs.operands().size()!=2)
      throw "POINTER_OFFSET expects one argument";
    
    exprt tmp;
    tmp.swap(rhs.op1());
    
    rhs=exprt("pointer_offset", rhs.type());
    rhs.move_to_operands(tmp);
  }
  else if(identifier==CPROVER_PREFIX "POINTER_OBJECT")
  {
    if(rhs.operands().size()!=2)
      throw "POINTER_OBJECT expects one argument";
    
    exprt tmp;
    tmp.swap(rhs.op1());
    
    rhs=exprt("pointer_object", rhs.type());
    rhs.move_to_operands(tmp);
  }
  else
  {
    do_functioncall_symbol(*symbol);
  }

  // insert function call
  if(lhs.is_nil())
  {
    codet tmp;
    tmp.swap(rhs);
    tmp.id("code");
    move(tmp, dest);
  }
  else
  {
    // check if it's a constructor call with LHS
    if(symbol->type.find("return_type").id()=="constructor")
    {
      assert(rhs.operands().size()>=2);
      assert(rhs.op1().id()=="address_of");
      assert(rhs.op1().operands().size()==1);

      code_assignt assignment;
      assignment.copy_to_operands(lhs, rhs.op1().op0());

      assert(lhs.type()==rhs.op1().op0().type());

      codet tmp;
      tmp.swap(rhs);
      tmp.id("code");
      move(tmp, dest);
      move(assignment, dest);    
    }
    else
    {
      code_assignt assignment;
      assignment.move_to_operands(lhs, rhs);
      move(assignment, dest);
    }
  }
}
