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

Module:

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

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

#include <assert.h>

#include <namespace.h>
#include <expr_util.h>
#include <arith_tools.h>
#include <std_code.h>
#include <config.h>

#include "c_types.h"
#include "c_main.h"

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

Function: global_init

  Inputs:

 Outputs:

 Purpose:

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

void global_init(
  const contextt &context,
  codet &dest)
{
  dest=code_blockt();
  
  // do assignments based on "value"

  forall_symbols(it, context.symbols)
    if(it->second.static_lifetime)
    {
      const exprt &value=it->second.value;
      
      if(value.is_not_nil())
      {
        assert(value.type().id()!="code");
        
        exprt symbol("symbol", it->second.type);
        symbol.set("identifier", it->second.name);
 
        exprt code("code", typet("code"));
        code.set("statement", "init");
        code.copy_to_operands(symbol, it->second.value);
        code.location()=it->second.location;

        dest.move_to_operands(code);
      }
    }

  // call designated "initialization" functions

  forall_symbols(it, context.symbols)
  {
    if(it->second.type.get_bool("initialization") &&
       it->second.type.id()=="code")
    {
      code_functioncallt function_call;      
      function_call.copy_to_operands(symbol_expr(it->second));
      dest.move_to_operands(function_call);
    }
  }  
}

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

Function: c_main

  Inputs:

 Outputs:

 Purpose:

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

bool c_main(
  contextt &context,
  const std::string &default_prefix,
  const std::string &standard_main,
  message_handlert &message_handler)
{
  irep_idt main_symbol;
  
  // find main symbol
  if(config.main!="")
  {
    std::list<irep_idt> matches;
  
    forall_symbol_base_map(it, context.symbol_base_map, config.main)
    {
      // look it up
      symbolst::const_iterator s_it=context.symbols.find(it->second);
      
      if(s_it==context.symbols.end()) continue;
    
      if(s_it->second.type.id()=="code")
        matches.push_back(it->second);
    }
    
    if(matches.empty())
    {
      messaget message(message_handler);
      message.error("main symbol `"+config.main+"' not found");
      return true; // give up
    }
    
    if(matches.size()>=2)
    {
      messaget message(message_handler);
      message.error("main symbol `"+config.main+"' is ambiguous");
      return true;
    }

    main_symbol=matches.front();
  }
  else
    main_symbol=standard_main;
    
  // look it up
  symbolst::const_iterator s_it=context.symbols.find(main_symbol);
  
  if(s_it==context.symbols.end())
    return false; // give up, no main
    
  const symbolt &symbol=s_it->second;
  
  // check if it has a body
  if(symbol.value.is_nil())
    return false; // give up

  codet init_code;
  
  global_init(context, init_code);
  
  init_code.make_block();

  // build call to function
  
  exprt call("sideeffect", (typet &)symbol.type.find("return_type"));
  call.set("statement", "functioncall");
  call.copy_to_operands(symbol_expr(symbol)); 

  const irept &arguments=symbol.type.find("arguments");

  if(symbol.name==standard_main)
  {
    if(arguments.get_sub().size()==0)
    {
      // ok
    }
    else if(arguments.get_sub().size()==2)
    {
      namespacet ns(context);

      const symbolt &argc_symbol=ns.lookup("c::argc'");
      const symbolt &argv_symbol=ns.lookup("c::argv'");
      
      {
        // assume argc is at least one
        exprt one=from_integer(1, argc_symbol.type);
        
        exprt ge(">=", typet("bool"));
        ge.copy_to_operands(symbol_expr(argc_symbol), one);
        
        exprt assumption("code", typet("code"));
        assumption.set("statement", "assume");
        assumption.move_to_operands(ge);
        init_code.move_to_operands(assumption);
      }
      
      {
        // assume argc is at most MAX-1
        mp_integer max;
        
        if(argc_symbol.type.id()=="signedbv")
          max=power(2, atoi(argc_symbol.type.get("width").c_str())-1)-1;
        else if(argc_symbol.type.id()=="unsignedbv")
          max=power(2, atoi(argc_symbol.type.get("width").c_str()))-1;
        else
          assert(false);
        
        exprt max_minus_one=from_integer(max-1, argc_symbol.type);
        
        exprt ge("<=", typet("bool"));
        ge.copy_to_operands(symbol_expr(argc_symbol), max_minus_one);
        
        exprt assumption("code", typet("code"));
        assumption.set("statement", "assume");
        assumption.move_to_operands(ge);
        init_code.move_to_operands(assumption);
      }
      
      {
        // assume argv[argc] is NULL
        exprt null("constant", argv_symbol.type.subtype());
        null.set("value", "NULL");
        
        exprt index_expr("index", argv_symbol.type.subtype());
        index_expr.copy_to_operands(
          symbol_expr(argv_symbol),
          symbol_expr(argc_symbol));
          
        // disable bounds check on that one
        index_expr.set("bounds_check", false);
        
        exprt is_null("=", typet("bool"));
        is_null.copy_to_operands(index_expr, null);
        
        exprt assumption2("code", typet("code"));
        assumption2.set("statement", "assume");
        assumption2.move_to_operands(is_null);
        init_code.move_to_operands(assumption2);
      }
      
      {
        // build call to function

        exprt::operandst &operands=call.operands();
        operands.resize(3);
        //exprt &op0=operands.front();
        exprt &op1=*(++operands.begin());
        exprt &op2=operands.back();
        const exprt &arg1=(exprt &)*(++arguments.get_sub().begin());

        op1=symbol_expr(argc_symbol);
        
        exprt index_expr("index", arg1.type().subtype());
        index_expr.copy_to_operands(symbol_expr(argv_symbol), gen_zero(index_type()));

        // disable bounds check on that one
        index_expr.set("bounds_check", false);
        
        op2=exprt("address_of", arg1.type());
        op2.move_to_operands(index_expr);
      }
    }
    else
    {
      assert(false);
    }
  }
  else
  {
    forall_irep(it, arguments.get_sub())
      call.copy_to_operands((exprt &)nil_rep);
  }

  exprt call_code=exprt("code", typet("code"));
  call_code.set("statement", "expression");
  call_code.move_to_operands(call);
  init_code.move_to_operands(call_code);
  
  // add "main"
  symbolt new_symbol;

  code_typet main_type;
  main_type.return_type()=empty_typet();
  
  new_symbol.name="main";
  new_symbol.type.swap(main_type);
  new_symbol.value.swap(init_code);
  
  if(context.move(new_symbol))
  {
    messaget message;
    message.set_message_handler(&message_handler);
    message.error("main already defined by another language module");
    return true;
  }
  
  return false;
}
