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

Module: Program Transformation

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

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

#include <expr_util.h>
#include <rename.h>
#include <cprover_prefix.h>
#include <i2string.h>

#include <ansi-c/c_types.h>

#include "goto_convert_class.h"

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

Function: goto_convertt::read

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::read(exprt &expr, goto_programt &dest)
{
  if(expr.is_constant())
    return;

  if(expr.id()=="symbol")
  {
    // see if we already renamed it

  }

  symbolt &new_symbol=new_tmp_symbol(expr.type());

  codet assignment("assign");
  assignment.reserve_operands(2);
  assignment.copy_to_operands(symbol_expr(new_symbol));
  assignment.move_to_operands(expr);

  goto_programt tmp_program;
  convert(assignment, tmp_program);
  dest.destructive_append(tmp_program);

  expr=symbol_expr(new_symbol);  
}

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

Function: goto_convertt::has_sideeffect

  Inputs:

 Outputs:

 Purpose:

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

bool goto_convertt::has_sideeffect(const exprt &expr)
{
  forall_operands(it, expr)
    if(has_sideeffect(*it))
      return true;

  if(expr.id()=="sideeffect")
    return true;

  return false;
}

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

Function: goto_convertt::has_functioncall

  Inputs:

 Outputs:

 Purpose:

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

bool goto_convertt::has_functioncall(const exprt &expr)
{
  forall_operands(it, expr)
    if(has_functioncall(*it))
      return true;

  if(expr.id()=="sideeffect" &&
     expr.get("statement")=="functioncall")
    return true;

  return false;
}

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

Function: goto_convertt::remove_sideeffects

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_sideeffects(
  exprt &expr,
  goto_programt &dest,
  bool result_is_used)
{
  guardt guard;
  remove_sideeffects(expr, guard, dest, result_is_used);
}

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

Function: goto_convertt::remove_sideeffects

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_sideeffects(
  exprt &expr,
  guardt &guard,
  goto_programt &dest,
  bool result_is_used)
{
  if(!has_sideeffect(expr))
    return;

  if(expr.id()=="and" || expr.id()=="or")
  {
    if(!expr.is_boolean())
      throw expr.id_string()+" must be Boolean, but got "+
            expr.pretty();

    std::vector<bool> s;
    s.resize(expr.operands().size());
    unsigned last=0;

    for(unsigned i=0; i<expr.operands().size(); i++)
    {
      s[i]=has_sideeffect(expr.operands()[i]);
      if(s[i]) last=i;
    }

    unsigned old_guards=guard.size();

    for(unsigned i=0; i<=last; i++)
    {
      exprt &op=expr.operands()[i];

      if(!op.is_boolean())
        throw expr.id_string()+" takes Boolean operands only, but got "+
              op.pretty();

      if(s[i])
      {
        // the side effect might modify the previous conditions
        for(unsigned j=0; j<i; j++)
          read(expr.operands()[j], dest);

        remove_sideeffects(op, guard, dest);
      }

      if(expr.id()=="or")
      {
        exprt tmp(op);
        tmp.make_not();
        guard.add(tmp);
      }
      else
        guard.add(op);
    }

    guard.resize(old_guards);

    return;
  }
  else if(expr.id()=="if")
  {
    if(expr.operands().size()!=3)
      throw "if takes three arguments";

    if(!expr.op0().is_boolean())
    {
      std::string msg=
        "first argument of if must be boolean, but got "
        +expr.op0().to_string();
      throw msg;
    }

    remove_sideeffects(expr.op0(), guard, dest);

    bool o1=has_sideeffect(expr.op1());
    bool o2=has_sideeffect(expr.op2());

    if(o1 || o2)
      read(expr.op0(), dest);

    if(o1)
    {
      unsigned old_guard=guard.size();
      guard.add(expr.op0());
      remove_sideeffects(expr.op1(), guard, dest);
      guard.resize(old_guard);
    }

    if(o2)
    {
      unsigned old_guard=guard.size();
      exprt tmp(expr.op0());
      tmp.make_not();
      guard.add(tmp);
      remove_sideeffects(expr.op2(), guard, dest);
      guard.resize(old_guard);
    }

    return;
  }
  else if(expr.id()=="comma")
  {
    exprt result;
  
    Forall_operands(it, expr)
    {
      bool last=(it==--expr.operands().end());
      
      if(last)
      {
        result.swap(*it);
        remove_sideeffects(result, guard, dest, result_is_used);
      }
      else
        remove_sideeffects(*it, guard, dest, false);
    }
    
    expr.swap(result);
    
    return;
  }

  // TODO: evaluation order

  Forall_operands(it, expr)
    remove_sideeffects(*it, guard, dest);

  if(expr.id()=="sideeffect")
  {
    const irep_idt &statement=expr.get("statement");

    if(statement=="functioncall") // might do anything
      remove_functioncall(expr, guard, dest, result_is_used);
    else if(statement=="assign" ||
            statement=="assign+" ||
            statement=="assign-" ||
            statement=="assign*" ||
            statement=="assign/" ||
            statement=="assign_bitor" ||
            statement=="assign_bitxor" ||
            statement=="assign_bitand" ||
            statement=="assign_lshr" ||
            statement=="assign_ashr" ||
            statement=="assign_shl")
      remove_assignment(expr, guard, dest);
    else if(statement=="postincrement" ||
            statement=="postdecrement")
      remove_post(expr, guard, dest, result_is_used);
    else if(statement=="preincrement" ||
            statement=="predecrement")
      remove_pre(expr, guard, dest);
    else if(statement=="cpp_new" ||
            statement=="cpp_new[]")
      remove_cpp_new(expr, guard, dest, result_is_used);
    else if(statement=="temporary_object")
      remove_temporary_object(expr, guard, dest, result_is_used);
    else if(statement=="statement_expression")
      remove_statement_expression(expr, guard, dest, result_is_used);
    else
    {
      err << "cannot remove side effect (" << statement << ")";
      throw 0;
    }
  }
}

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

Function: goto_convertt::remove_assignment

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_assignment(
  exprt &expr,
  guardt &guard,
  goto_programt &dest)
{
  codet assignment_statement("expression");
  assignment_statement.copy_to_operands(expr);

  if(expr.operands().size()!=2)
    throw "assignment must have two operands";

  exprt lhs;
  lhs.swap(expr.op0());
  expr.swap(lhs);

  exprt guard_expr;
  guard.expr(guard_expr);

  code_ifthenelset if_statement;
  if_statement.move_to_operands(guard_expr, assignment_statement);

  goto_programt tmp_program;
  convert(if_statement, tmp_program);
  dest.destructive_append(tmp_program);
}

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

Function: goto_convertt::remove_pre

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_pre(
  exprt &expr,
  guardt &guard,
  goto_programt &dest)
{
  codet pre_statement("expression");
  pre_statement.copy_to_operands(expr);

  if(expr.operands().size()!=1)
    throw "preincrement/predecrement must have one operand";

  exprt op;
  op.swap(expr.op0());
  expr.swap(op);

  exprt guard_expr;
  guard.expr(guard_expr);

  code_ifthenelset if_statement;
  if_statement.move_to_operands(guard_expr, pre_statement);

  goto_programt tmp_program;
  convert(if_statement, tmp_program);
  dest.destructive_append(tmp_program);
}

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

Function: goto_convertt::remove_post

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_post(
  exprt &expr,
  guardt &guard,
  goto_programt &dest,
  bool result_is_used)
{
  // we have ...(op++)...
  codet post_statement("expression");
  post_statement.copy_to_operands(expr);

  if(expr.operands().size()!=1)
    throw "postincrement/postdecrement must have one operand";

  if(result_is_used)
  {
    exprt tmp;
    tmp.swap(expr.op0());
    read(tmp, dest);
    expr.swap(tmp);
  }
  else
  {
    expr.make_nil();
  }

  exprt guard_expr;
  guard.expr(guard_expr);

  code_ifthenelset if_statement;
  if_statement.move_to_operands(guard_expr);
  if_statement.move_to_operands(post_statement);

  goto_programt tmp_program;
  convert(if_statement, tmp_program);
  dest.destructive_append(tmp_program);
}

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

Function: goto_convertt::remove_functioncall

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_functioncall(
  exprt &expr,
  guardt &guard,
  goto_programt &dest,
  bool result_is_used)
{
  codet call;

  if(result_is_used)
  {
    symbolt new_symbol;

    new_symbol.base_name="return_value";
    new_symbol.lvalue=true;
    new_symbol.type=expr.type();

    // get name of function, if available

    if(expr.id()!="sideeffect" ||
       expr.get("statement")!="functioncall")
      throw "expected function call";

    if(expr.operands().empty())
      throw "functioncall expects at least one operand";

    if(expr.op0().id()=="symbol")
    {
      const irep_idt &identifier=expr.op0().get("identifier");
      const symbolt &symbol=lookup(identifier);
      
      std::string new_base_name=id2string(new_symbol.base_name);
      
      new_base_name+="_";
      new_base_name+=id2string(symbol.base_name);
      new_base_name+="$"+i2string(++temporary_counter);
      
      new_symbol.base_name=new_base_name;
      new_symbol.mode=symbol.mode;
    }

    new_symbol.name=tmp_symbol_prefix+id2string(new_symbol.base_name);

    new_name(new_symbol);
    
    tmp_symbols.push_back(new_symbol.name);

    call=code_assignt();
    call.copy_to_operands(symbol_expr(new_symbol), expr);

    expr=symbol_expr(new_symbol);
  }
  else
  {
    call=codet("expression");
    call.move_to_operands(expr);
  }

  exprt guard_expr;
  guard.expr(guard_expr);

  code_ifthenelset if_statement;
  if_statement.move_to_operands(guard_expr, call);

  goto_programt tmp_program;
  convert(if_statement, tmp_program);
  dest.destructive_append(tmp_program);
}

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

Function: goto_convertt::replace_new_object

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::replace_new_object(
  const exprt &object,
  exprt &dest)
{
  if(dest.id()=="new_object")
    dest=object;
  else
    Forall_operands(it, dest)
      replace_new_object(object, *it);
}

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

Function: goto_convertt::remove_cpp_new

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_cpp_new(
  exprt &expr,
  guardt &guard,
  goto_programt &dest,
  bool result_is_used)
{
  codet call;

  if(result_is_used)
  {
    symbolt new_symbol;

    new_symbol.base_name="new_value$"+i2string(++temporary_counter);
    new_symbol.lvalue=true;
    new_symbol.type=expr.type();

    new_symbol.name=tmp_symbol_prefix+id2string(new_symbol.base_name);

    new_name(new_symbol);

    call=code_assignt();
    call.copy_to_operands(symbol_expr(new_symbol), expr);

    expr=symbol_expr(new_symbol);
  }
  else
  {
    call=codet("expression");
    call.move_to_operands(expr);
  }

  exprt guard_expr;
  guard.expr(guard_expr);

  code_ifthenelset if_statement;
  if_statement.move_to_operands(guard_expr, call);

  goto_programt tmp_program;
  convert(if_statement, tmp_program);
  dest.destructive_append(tmp_program);
}

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

Function: goto_convertt::remove_temporary_object

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_temporary_object(
  exprt &expr,
  guardt &guard,
  goto_programt &dest,
  bool result_is_used)
{
  if(expr.operands().size()!=1 &&
     expr.operands().size()!=0)
    throw "temporary_object takes 0 or 1 operands";

  symbolt &new_symbol=new_tmp_symbol(expr.type());

  new_symbol.mode=expr.get("mode");
  
  if(expr.operands().size()==1)
  {
    codet assignment("assign");
    assignment.reserve_operands(2);
    assignment.copy_to_operands(symbol_expr(new_symbol));
    assignment.move_to_operands(expr.op0());

    goto_programt tmp_program;
    convert(assignment, tmp_program);
    dest.destructive_append(tmp_program);
  }

  if(expr.find("initializer").is_not_nil())
  {
    assert(expr.operands().empty());
    exprt initializer=static_cast<const exprt &>(expr.find("initializer"));
    replace_new_object(symbol_expr(new_symbol), initializer);

    goto_programt tmp_program;
    convert(to_code(initializer), tmp_program);
    dest.destructive_append(tmp_program);
  }

  expr=symbol_expr(new_symbol);  
}

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

Function: goto_convertt::remove_statement_expression

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::remove_statement_expression(
  exprt &expr,
  guardt &guard,
  goto_programt &dest,
  bool result_is_used)
{
  if(expr.operands().size()!=1)
    throw "statement_expression takes 1 operand";

  if(expr.op0().id()!="code")
    throw "statement_expression takes code as operand";

  codet &code=to_code(expr.op0());

  exprt last;
  last.make_nil();

  if(result_is_used)
  {
    // get last statement from block
    if(code.get_statement()!="block")
      throw "statement_expression expects block";

    if(code.operands().empty())
      throw "statement_expression expects non-empty block";

    last.swap(code.operands().back());
    code.operands().pop_back();
  }

  {
    exprt guard_expr;
    guard.expr(guard_expr);

    code_ifthenelset if_statement;
    if_statement.move_to_operands(guard_expr, code);

    goto_programt tmp;
    convert(if_statement, tmp);

    dest.destructive_append(tmp);
  }

  if(result_is_used)
  {
    goto_programt tmp;
    remove_sideeffects(last, guard, tmp, true);

    if(last.get("statement")=="expression" &&
       last.operands().size()==1)
      expr=last.op0();
    else
      throw "statement_expression expects expression as last statement";
  }
}
