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

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 <ansi-c/c_types.h>

#include "goto_convert_class.h"

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

Function: goto_convertt::convert_functioncall

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_functioncall(
  codet &code,
  goto_programt &dest)
{
  exprt lhs((const exprt &)nil_rep);
  
  do_functioncall(lhs, code, dest);
}

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

Function: goto_convertt::do_functioncall

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::do_functioncall(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  // make it all side effect free

  if(!lhs.is_nil())
    remove_sideeffects(lhs, dest);

  Forall_operands(it, rhs)
    remove_sideeffects(*it, dest);

  if(rhs.operands().size()==0)
  {
    err_location(rhs);
    throw "functioncall takes at least one operand";
  }

  exprt &function=rhs.op0();
  
  if(function.id()=="dereference" ||
     function.id()=="implicit_dereference")  
  {
    do_functioncall_dereference(lhs, rhs, dest);
  }
  else if(function.id()=="if")
  {
    if(function.operands().size()!=3)
    {
      err_location(function);
      throw "if expects three operands";
    }
    
    // replicate lhs -- it's destroyed
    exprt lhs1(lhs), lhs2(lhs);
  
    // case split

    goto_programt sideeffects;
    remove_sideeffects(function.op0(), sideeffects);

    //    c?f():g()
    //--------------------
    // v: if(!c) goto y;
    // w: f();
    // x: goto z;
    // y: g();
    // z: ;

    // do the v label
    goto_programt tmp_v;
    goto_programt::targett v=tmp_v.add_instruction();

    // do the x label
    goto_programt tmp_x;
    goto_programt::targett x=tmp_x.add_instruction();

    // do the z label
    goto_programt tmp_z;
    goto_programt::targett z=tmp_z.add_instruction();
    z->make_skip();

    // y: g();
    goto_programt tmp_y;
    goto_programt::targett y;

    {
      exprt tmp_rhs(rhs);
      tmp_rhs.op0()=function.op2();
      do_functioncall(lhs2, tmp_rhs, tmp_y);
    }

    y=tmp_y.instructions.begin();

    // v: if(!c) goto y;
    v->make_goto(y);
    v->guard=function.op0();
    v->guard.make_not();
    set_location(function.op0(), *v);

    // w: f();
    goto_programt tmp_w;

    {
      exprt tmp_rhs(rhs);
      tmp_rhs.op0()=function.op1();
      do_functioncall(lhs1, tmp_rhs, tmp_w);
    }

    // x: goto z;
    x->make_goto(z);

    dest.destructive_append(sideeffects);  
    dest.destructive_append(tmp_v);
    dest.destructive_append(tmp_w);
    dest.destructive_append(tmp_x);
    dest.destructive_append(tmp_y);
    dest.destructive_append(tmp_z);
  }
  else if(function.id()=="symbol")
  {
    do_functioncall_symbol(lhs, rhs, dest);
  }
  else
  {
    err_location(function);
    throw "unexpected function argument: "+function.id_string();
  }
}

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

Function: goto_convertt::do_functioncall_dereference

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::do_functioncall_dereference(
  exprt &lhs,
  exprt &rhs,
  goto_programt &dest)
{
  // don't know what to do with it
  goto_programt::targett t=dest.add_instruction(OTHER);
  set_location(rhs, *t);

  exprt statement("code");
  statement.set("statement", "function_pointer_call");
  statement.move_to_operands(lhs, rhs);
  t->code.swap(statement);
}
