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

Module:

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

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

#include <assert.h>

#include <arith_tools.h>

#include "boolbv.h"

//#define DEBUG

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

Function: boolbvt::get

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::get(const exprt &expr, exprt &dest) const
{
  if(expr.id()=="symbol" ||
     expr.id()=="nondet_symbol")
  {
    const irep_idt &identifier=expr.get("identifier");

    mappingt::const_iterator it=mapping.find(identifier);

    if(it!=mapping.end())
    {
      const bv_map_entryt &map_entry=it->second;
    
      if(is_unbounded_array(it->second.type))
        bv_get_unbounded_array(identifier, map_entry.type, dest);
      else
      {
        std::vector<bool> unknown;
        bvt bv;
        unsigned width=map_entry.width;

        bv.resize(width);
        unknown.resize(width);

        for(unsigned bit_nr=0; bit_nr<width; bit_nr++)
        {
          literal_mapt::const_iterator l_it=
            map_entry.literal_map.find(bit_nr);

          if(it!=mapping.end())
          {
            unknown[bit_nr]=false;
            bv[bit_nr]=l_it->second;
          }
          else
          {
            unknown[bit_nr]=true;
            bv[bit_nr].clear();
          }
        }

        bv_get_rec(bv, unknown, 0, map_entry.type, dest);
      }

      return;
    }
  }
  else if(expr.id()=="constant")
  {
    dest=expr;
    return;
  }

  return SUB::get(expr, dest);
}

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

Function: boolbvt::bv_get_rec

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::bv_get_rec(
  const bvt &bv,
  const std::vector<bool> &unknown,
  unsigned offset,
  const typet &type,
  exprt &dest) const
{
  dest.make_nil();

  unsigned width;
  if(get_width(type, width))
    return;

  assert(bv.size()==unknown.size());
  assert(bv.size()>=offset+width);

  if(type.id()=="bool")
  {
    if(!unknown[offset])
    {
      switch(prop.l_get(bv[offset]).get_value())
      {
      case tvt::TV_FALSE: dest.make_false(); break;
      case tvt::TV_TRUE:  dest.make_true();  break;
      default: dest.make_false(); break; // default
      }
    }

    return;
  }

  bvtypet bvtype=get_bvtype(type);

  if(bvtype==IS_UNKNOWN)
  {
    if(type.id()=="array")
    {
      unsigned sub_width;
      const typet &subtype=type.subtype();

      if(!get_width(subtype, sub_width))
      {
        exprt::operandst op;
        op.reserve(width/sub_width);

        for(unsigned new_offset=0;
            new_offset<width;
            new_offset+=sub_width)
        {
          op.push_back((exprt &)nil_rep);
          bv_get_rec(bv, unknown, offset+new_offset, subtype, op.back());
        }

        dest=exprt("array", type);
        dest.operands().swap(op);
        return;
      }
    }
    else if(type.id()=="struct")
    {
      const irept &components=type.find("components");
      unsigned new_offset=0;
      exprt::operandst op;
      op.reserve(components.get_sub().size());

      forall_irep(it, components.get_sub())
      {
        const typet &subtype=(typet &)it->find("type");
        op.push_back((exprt &)nil_rep);

        unsigned sub_width;
        if(!get_width(subtype, sub_width))
        {
          bv_get_rec(bv, unknown, offset+new_offset, subtype, op.back());
          new_offset+=sub_width;
        }
      }

      dest=exprt(type.id(), type);
      dest.operands().swap(op);
      return;
    }
    else if(type.id()=="union")
    {
      const irept &components=type.find("components");

      unsigned component_bits=
        integer2long(address_bits(components.get_sub().size()));
      unsigned component_nr=0;

      for(unsigned i=0; i<component_bits; i++)
      {
        unsigned bit_nr=width-component_bits+i+offset;
        if(unknown[bit_nr]) return;

        switch(prop.l_get(bv[bit_nr]).get_value())
        {
         case tvt::TV_FALSE: break;
         case tvt::TV_TRUE:  component_nr|=(1<<i); break;
         case tvt::TV_UNKNOWN: break; // default
         default: return;
        }
      }
        
      if(component_nr>=components.get_sub().size())
        return;

      exprt value("union", type);
      value.operands().resize(1);

      value.set("component_name",
                components.get_sub()[component_nr].get("name"));
      
      const typet &subtype=
        (typet &)components.get_sub()[component_nr].find("type");

      bv_get_rec(bv, unknown, offset, subtype, value.op0());

      dest.swap(value);
      return;
    }
  }

  std::string value;

  for(unsigned bit_nr=offset; bit_nr<offset+width; bit_nr++)
  {
    char ch;
    if(unknown[bit_nr])
      ch='0';
    else
      switch(prop.l_get(bv[bit_nr]).get_value())
      {
       case tvt::TV_FALSE: ch='0'; break;
       case tvt::TV_TRUE:  ch='1'; break;
       case tvt::TV_UNKNOWN: ch='0'; break;
       default: assert(false);
      }

    value=ch+value;
  }

  switch(bvtype)
  {
  case IS_C_ENUM:
    dest.type()=type;
    dest.id("constant");
    dest.set("value", integer2string(binary2integer(value, true)));
    break;
  
  case IS_UNKNOWN:
    break;
    
  case IS_RANGE:
    {
      mp_integer int_value=binary2integer(value, false);
      mp_integer from=string2integer(type.get_string("from"));

      dest.type()=type;
      dest.id("constant");
      dest.set("value", integer2string(int_value+from));
    }
    break;
    
  default:
    dest.type()=type;
    dest.id("constant");
    dest.set("value", value);
  }
}

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

Function: boolbvt::bv_get

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::bv_get(
  const bvt &bv,
  const typet &type,
  exprt &dest) const
{
  std::vector<bool> unknown;
  unknown.resize(bv.size(), false);
  bv_get_rec(bv, unknown, 0, type, dest);
}

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

Function: boolbvt::bv_get_cache

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::bv_get_cache(const exprt &expr, exprt &dest) const
{
  if(expr.type().id()=="bool") // boolean?
  {
    get(expr, dest);
    return;
  }

  // look up literals in cache
  bv_cachet::const_iterator it=bv_cache.find(expr);
  if(it==bv_cache.end())
  {
    dest.make_nil();
    return;
  }

  bv_get(it->second, expr.type(), dest);
}

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

Function: boolbvt::bv_get_unbounded_array

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::bv_get_unbounded_array(
  const irep_idt &identifier,
  const typet &type,
  exprt &dest) const
{
  dest.make_nil();

  // first, try to get size

  const exprt &size_expr=(exprt &)type.find("size");
  exprt size;

  get(size_expr, size);

  // no size, give up
  if(size.is_nil()) return;

  // get the numeric value
  mp_integer size_mpint;
  if(to_integer(size, size_mpint))
    return;

  // simple check
  if(size_mpint<0)
    return;

  // search uninterpreted functions

  typedef std::map<mp_integer, exprt> valuest;
  valuest values;

  {
    exprt array_expr("symbol", type);
    array_expr.set("identifier", identifier);

    function_mapt::const_iterator f_it=function_map.find(array_expr);
    
    if(f_it==function_map.end())
      return;

    for(std::set<exprt>::const_iterator it1=f_it->second.applications.begin();
        it1!=f_it->second.applications.end();
        it1++)
      if(it1->id()=="index")
      {
        if(it1->operands().size()!=2)
          throw "index expected to have two operands";

        if(it1->op0()!=array_expr)
          throw "unexpected function application in function map";

        exprt value;
        bv_get_cache(*it1, value);

        exprt index_expr;
        get(it1->op1(), index_expr);

        if(!value.is_nil() && !index_expr.is_nil())
        {
          mp_integer index_mpint;
          if(!to_integer(index_expr, index_mpint))
          {
            if(index_mpint>=0 && index_mpint<size_mpint)
              values[index_mpint].swap(value);
          }
        }
      }
  }

  if(size_mpint>100)
  {
    dest=exprt("array-list", type);
    dest.type().set("size", integer2string(size_mpint));

    dest.operands().reserve(values.size()*2);

    for(valuest::iterator it=values.begin();
        it!=values.end();
        it++)
    {
      exprt index=from_integer(it->first, size.type());
      dest.move_to_operands(index);
      dest.move_to_operands(it->second);
    }
  }
  else
  {
    // set the size
    dest=exprt("array", type);
    dest.type().set("size", size);

    unsigned long size_int=integer2long(size_mpint);

    // allocate operands
    dest.operands().resize(size_int);

    for(unsigned i=0; i<size_int; i++)
      dest.operands()[i]=exprt("unknown");

    // search uninterpreted functions

    for(valuest::iterator it=values.begin();
        it!=values.end();
        it++)
      dest.operands()[integer2long(it->first)].swap(it->second);
  }
}
