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

Module: ANSI-C Linking

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

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

#include <location.h>
#include <namespace.h>
#include <typecheck.h>
#include <base_type.h>

#include "c_link_type_eq.h"
#include "expr2c.h"
#include "c_link.h"

class c_linkt:public typecheckt
{
public:
  c_linkt(
    contextt &_context,
    contextt &_new_context,
    const std::string &_module,
    message_handlert &_message_handler):
    typecheckt(_message_handler),
    context(_context),
    new_context(_new_context),
    module(_module),
    ns(_context, _new_context)
  { }
   
  virtual void typecheck();
 
protected:
  void duplicate(symbolt &in_context, symbolt &new_symbol);
  void duplicate_type(symbolt &in_context, symbolt &new_symbol);
  void duplicate_symbol(symbolt &in_context, symbolt &new_symbol);
  void move(symbolt &new_symbol);

  // overload to use language specific syntax
  virtual std::string to_string(const exprt &expr);
  virtual std::string to_string(const typet &type);

  contextt &context;
  contextt &new_context;
  std::string module;
  namespacet ns;
};

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

Function: c_linkt::to_string

  Inputs:

 Outputs:

 Purpose:

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

std::string c_linkt::to_string(const exprt &expr)
{ 
  return expr2c(expr, ns);
}

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

Function: c_linkt::to_string

  Inputs:

 Outputs:

 Purpose:

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

std::string c_linkt::to_string(const typet &type)
{ 
  return type2c(type, ns);
}

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

Function: c_linkt::duplicate

  Inputs:

 Outputs:

 Purpose:

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

void c_linkt::duplicate(
  symbolt &in_context,
  symbolt &new_symbol)
{
  if(new_symbol.is_type!=in_context.is_type)
  {
    err << "class conflict on symbol `" << in_context.name
        << "'";
    throw 0;
  }

  if(new_symbol.is_type)
    duplicate_type(in_context, new_symbol);
  else
    duplicate_symbol(in_context, new_symbol);
}

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

Function: c_linkt::duplicate_type

  Inputs:

 Outputs:

 Purpose:

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

void c_linkt::duplicate_type(
  symbolt &in_context,
  symbolt &new_symbol)
{
  // check if it is the same -- use base_type_eq, not c_link_type_eq
  if(!base_type_eq(in_context.type, new_symbol.type, ns))
  {
    if(in_context.type.id()=="incomplete_struct" &&
       new_symbol.type.id()=="struct")
    {
      // replace old symbol
      in_context.type=new_symbol.type;
    }
    else if(in_context.type.id()=="struct" &&
            new_symbol.type.id()=="incomplete_struct")
    {
      // ignore
    }
    else
    {
      err_location(new_symbol.location);
      err << "error: conflicting definition for type `"
          << in_context.display_name()
          << "'" << std::endl;
      err << "old definition: " << to_string(in_context.type)
          << std::endl;
      err << "Module: " << in_context.module << std::endl;
      err << "new definition: " << to_string(new_symbol.type)
          << std::endl;
      err << "Module: " << new_symbol.module;
      throw 0;
    }
  }
}

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

Function: c_linkt::duplicate_symbol

  Inputs:

 Outputs:

 Purpose:

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

void c_linkt::duplicate_symbol(
  symbolt &in_context,
  symbolt &new_symbol)
{
  // see if it is a function or a variable

  bool is_code_in_context=in_context.type.id()=="code";
  bool is_code_new_symbol=new_symbol.type.id()=="code";

  if(is_code_in_context!=is_code_new_symbol)
  {
    err_location(new_symbol.location);
    err << "error: conflicting definition for symbol \""
        << in_context.display_name()
        << "\"" << std::endl;
    err << "old definition: " << to_string(in_context.type)
        << std::endl;
    err << "Module: " << in_context.module << std::endl;
    err << "new definition: " << to_string(new_symbol.type)
        << std::endl;
    err << "Module: " << new_symbol.module;
    throw 0;
  }

  if(is_code_in_context)
  {
    // both are functions

    // we don't compare the types, they will be too different

    // care about code

    if(!new_symbol.value.is_nil())
    {
      if(in_context.value.is_nil())
      {
        // the one with body wins!
        in_context.value.swap(new_symbol.value);
        in_context.type.swap(new_symbol.type); // for argument identifiers
      }
      else if(in_context.type.get_bool("#inlined"))
      {
        // ok
      }
      else
      {
        err_location(new_symbol.value);
        err << "error: duplicate definition of function `"
            << in_context.name
            << "'" << std::endl;
        err << "In module `" << in_context.module
            << "' and module `" << new_symbol.module << "'";
        throw 0;
      }
    }
  }
  else
  {
    // both are variables

    if(!base_type_eq(in_context.type, new_symbol.type, ns))
    {
      if(in_context.type.id()=="incomplete_array" &&
         new_symbol.type.id()=="array")
      {
        // store new type
        in_context.type=new_symbol.type;
      }
      else if(in_context.type.id()=="array" &&
              new_symbol.type.id()=="incomplete_array")
      {
        // ignore
      }
      else if(in_context.type.id()=="incomplete_struct" &&
              new_symbol.type.id()=="struct")
      {
        // store new type
        in_context.type=new_symbol.type;
      }
      else if(in_context.type.id()=="struct" &&
              new_symbol.type.id()=="incomplete_struct")
      {
        // ignore
      }
      else
      {
        err_location(new_symbol.location);
        err << "error: conflicting definition for variable `"
            << in_context.name
            << "'" << std::endl;
        err << "old definition: " << to_string(in_context.type)
            << std::endl;
        err << "Module: " << in_context.module << std::endl;
        err << "new definition: " << to_string(new_symbol.type)
            << std::endl;
        err << "Module: " << new_symbol.module;
        throw 0;
      }
    }

    // care about initializers    

    if(!new_symbol.value.is_nil() &&
       !new_symbol.value.get_bool("#zero_initializer"))
    {
      if(in_context.value.is_nil() ||
         in_context.value.get_bool("#zero_initializer"))
      {
        in_context.value.swap(new_symbol.value);
      }
      else if(!base_type_eq(in_context.value, new_symbol.value, ns))
      {
        err_location(new_symbol.value);
        err << "error: conflicting initializers for variable `"
            << in_context.name
            << "'" << std::endl;
        err << "old value: " << to_string(in_context.value)
            << std::endl;
        err << "Module: " << in_context.module << std::endl;
        err << "new value: " << to_string(new_symbol.value)
            << std::endl;
        err << "Module: " << new_symbol.module;
        throw 0;
      }
    }
  }
}

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

Function: c_linkt::typecheck

  Inputs:

 Outputs:

 Purpose:

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

void c_linkt::typecheck()
{
  Forall_symbols(it, new_context.symbols)
    move(it->second);
}

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

Function: c_linkt::move

  Inputs:

 Outputs:

 Purpose:

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

void c_linkt::move(symbolt &new_symbol)
{
  // try to add it

  symbolt *new_symbol_ptr;
  if(context.move(new_symbol, new_symbol_ptr))
    duplicate(*new_symbol_ptr, new_symbol);
}

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

Function: convert_c

  Inputs:

 Outputs:

 Purpose:

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

bool c_link(
  contextt &context,
  contextt &new_context,
  message_handlert &message_handler,
  const std::string &module)
{
  c_linkt c_link(context, new_context, module, message_handler);
  return c_link.typecheck_main();
}
