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

Module:

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

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

#include <assert.h>

#include <map>
#include <set>

#include <symbol.h>
#include <mp_arith.h>
#include <i2string.h>
#include <arith_tools.h>
#include <replace_expr.h>
#include <string2array.h>
#include <std_types.h>
#include <prefix.h>

#include "boolbv.h"

//#define DEBUG

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

Function: boolbvt::get_map_entry

  Inputs:

 Outputs:

 Purpose:

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

boolbvt::bv_map_entryt &boolbvt::get_map_entry(
  const irep_idt &identifier,
  const typet &type)
{
  std::pair<mappingt::iterator, bool> result=
    mapping.insert(std::pair<irep_idt, bv_map_entryt>(
      identifier, bv_map_entryt()));

  bv_map_entryt &map_entry=result.first->second;

  if(result.second)
  { // actually inserted
    map_entry.type=type;
    get_width(type, map_entry.width);
    map_entry.bvtype=get_bvtype(type);
  }

  return map_entry;
}

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

Function: boolbvt::show_mapping

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::show_mapping() const
{
  for(mappingt::const_iterator it=mapping.begin();
      it!=mapping.end();
      it++)
  {
  }
}

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

Function: boolbvt::get_literal

  Inputs:

 Outputs:

 Purpose:

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

literalt boolbvt::get_literal(
  const irep_idt &identifier,
  const unsigned bit,
  const typet &type)
{
  bv_map_entryt &map_entry=get_map_entry(identifier, type);

  literal_mapt::const_iterator it=
    map_entry.literal_map.find(bit);

  if(it!=map_entry.literal_map.end()) return it->second;

  literalt l=prop.new_literal();

  map_entry.literal_map.insert(
    std::pair<unsigned, literalt>(bit, l));

  #ifdef DEBUG
  std::cout << "NEW: " << identifier << ":" << bit
            << "=" << l << std::endl;
  #endif

  return l;
}

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

Function: boolbvt::literal

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::literal(
  const exprt &expr,
  const unsigned bit,
  literalt &dest) const
{
  if(expr.type().id()=="bool")
  {
    assert(bit==0);
    return prop_convt::literal(expr, dest);
  }
  else
  {
    if(expr.id()=="symbol" ||
       expr.id()=="nondet_symbol")
    {
      const irep_idt &identifier=expr.get("identifier");

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

      if(it_m==mapping.end()) return true;

      const bv_map_entryt &map_entry=it_m->second;
      
      literal_mapt::const_iterator it_l=
        map_entry.literal_map.find(bit);
      if(it_l==map_entry.literal_map.end()) return true;

      dest=it_l->second;
      return false;
    }
    else if(expr.id()=="index")
    {
      if(expr.operands().size()!=2)
        throw "index takes two operands";

      unsigned element_width;

      if(get_width(expr.type(), element_width))
        throw "literal expects a bit-vector type";

      mp_integer index;
      if(to_integer(expr.op1(), index))
        throw "literal expects constant index";

      unsigned offset=integer2long(index*element_width);

      return literal(expr.op0(), bit+offset, dest);
    }
    else if(expr.id()=="member")
    {
      if(expr.operands().size()!=1)
        throw "member takes one operand";

      const irept &components=expr.type().find("components");
      const irep_idt &component_name=expr.get("component_name");

      unsigned offset=0;

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

        if(it->get("name")==component_name)
          return literal(expr.op0(), bit+offset, dest);

        unsigned element_width;
        if(get_width(subtype, element_width))
          throw "literal expects a bit-vector type";

        offset+=element_width;
      }

      throw "failed to find component";
    }
  }

  throw "found no literal for expression";
}

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

Function: boolbvt::set_literal

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::set_literal(
  const irep_idt &identifier,
  const unsigned bit,
  const typet &type,
  literalt literal)
{
  bv_map_entryt &map_entry=get_map_entry(identifier, type);

  literal_mapt::const_iterator it=
    map_entry.literal_map.find(bit);

  if(it!=map_entry.literal_map.end())
  {
    prop.set_equal(it->second, literal);
    return;
  }

  map_entry.literal_map.insert(
    std::pair<unsigned, literalt>(bit, literal));
}

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

Function: boolbvt::bvtype

  Inputs:

 Outputs:

 Purpose:

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

boolbvt::bvtypet boolbvt::get_bvtype(const typet &type)
{
  if(type.id()=="signedbv")
    return IS_SIGNED;
  else if(type.id()=="unsignedbv")
    return IS_UNSIGNED;
  else if(type.id()=="c_enum" ||
          type.id()=="incomplete_c_enum")
    return IS_C_ENUM;
  else if(type.id()=="floatbv")
    return IS_FLOAT;
  else if(type.id()=="fixedbv")
    return IS_FIXED;
  else if(type.id()=="bv")
    return IS_BV;
  else if(type.id()=="verilogbv")
    return IS_VERILOGBV;
  else if(type.id()=="range")
    return IS_RANGE;

  return IS_UNKNOWN;
}

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

Function: boolbvt::convert_bv

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_bv(const exprt &expr, bvt &bv)
{
  // check cache first
  
  {
    bv_cachet::const_iterator cache_result=bv_cache.find(expr);
    if(cache_result!=bv_cache.end())
    {
      //std::cerr << "Cache hit on " << expr << "\n";
      bv=cache_result->second;
      return false;
    }
  }
  
  if(convert_bitvector(expr, bv))
  {
    ignoring(expr);

    // try to make it free bits
    if(make_fresh_variables(expr, bv))
      return true;
  }

  // insert into cache

  bv_cache.insert(std::pair<const exprt, bvt>(expr, bv));

  return false;
}

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

Function: boolbvt::make_fresh_variables

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::make_fresh_variables(const exprt &expr, bvt &bv)
{
  unsigned width;
  if(get_width(expr.type(), width))
    return true;

  bv.resize(width);

  for(unsigned i=0; i<width; i++)
    bv[i]=prop.new_literal();
    
  return false;
}

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

Function: boolbvt::convert_bitvector

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_bitvector(const exprt &expr, bvt &bv)
{
  #ifdef DEBUG
  std::cout << "BV: " << expr.pretty() << std::endl;
  #endif

  if(expr.type().id()=="bool")
  {
    bv.resize(1);
    bv[0]=convert(expr);
    return false;
  }

  if(expr.id()=="index")
    return convert_index(expr, bv);
  else if(expr.id()=="constraint_select_one")
    return convert_constraint_select_one(expr, bv);
  else if(expr.id()=="member")
    return convert_member(expr, bv);
  else if(expr.id()=="with")
    return convert_with(expr, bv);
  else if(expr.id()=="case")
    return convert_case(expr, bv);
  else if(expr.id()=="cond")
    return convert_cond(expr, bv);
  else if(expr.id()=="if")
    return convert_if(expr, bv);
  else if(expr.id()=="constant")
    return convert_constant(expr, bv);
  else if(expr.id()=="typecast")
    return convert_typecast(expr, bv);
  else if(expr.id()=="symbol")
    return convert_symbol(expr, bv);
  else if(expr.id()=="bv_literals")
    return convert_bv_literals(expr, bv);
  else if(expr.id()=="+" || expr.id()=="-" ||
          expr.id()=="no-overflow-plus" ||
          expr.id()=="no-overflow-minus")
    return convert_add_sub(expr, bv);
  else if(expr.id()=="*" ||
          expr.id()=="no-overflow-mult")
    return convert_mult(expr, bv);
  else if(expr.id()=="/")
    return convert_div(expr, bv);
  else if(expr.id()=="mod")
    return convert_mod(expr, bv);
  else if(expr.id()=="shl" || expr.id()=="ashr" || expr.id()=="lshr")
    return convert_shift(expr, bv);
  else if(expr.id()=="concatenation")
    return convert_concatenation(expr, bv);
  else if(expr.id()=="replication")
    return convert_replication(expr, bv);
  else if(expr.id()=="extractbits")
    return convert_extractbits(expr, bv);
  else if(expr.id()=="bitnot" || expr.id()=="bitand" ||
          expr.id()=="bitor" || expr.id()=="bitxor")
    return convert_bitwise(expr, bv);
  else if(expr.id()=="unary-" ||
          expr.id()=="no-overflow-unary-minus")
    return convert_unary_minus(expr, bv);
  else if(has_prefix(expr.id_string(), "byte_extract"))
    return convert_byte_extract(expr, bv);
  else if(expr.id()=="nondet_symbol" ||
          expr.id()=="quant_symbol")
    return convert_symbol(expr, bv);
  else if(expr.id()=="struct")
    return convert_struct(expr, bv);
  else if(expr.id()=="union")
    return convert_union(expr, bv);
  else if(expr.id()=="string-constant")
  {
    exprt tmp;
    string2array(expr, tmp);
    return convert_bitvector(tmp, bv);
  }
  else if(expr.id()=="array")
    return convert_array(expr, bv);
  else if(expr.id()=="lambda")
    return convert_lambda(expr, bv);
  else if(expr.id()=="array_of")
    return convert_array_of(expr, bv);
  
  return true;
}

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

Function: boolbvt::convert_array

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_array(const exprt &expr, bvt &bv)
{
  unsigned width;
  if(get_width(expr.type(), width))
    return true;
    
  bv.resize(width);

  if(expr.type().id()=="array")
  {
    unsigned op_width=width/expr.operands().size();
    unsigned offset=0;
    
    forall_operands(it, expr)
    {
      bvt tmp;

      if(convert_bv(*it, tmp))
        return true;

      if(tmp.size()!=op_width)
        throw "convert_array: unexpected operand width";

      for(unsigned j=0; j<op_width; j++)
        bv[offset+j]=tmp[j];

      offset+=op_width;
    }   

    return false;
  }
  
  return true;
}

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

Function: boolbvt::convert_lambda

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_lambda(const exprt &expr, bvt &bv)
{
  unsigned width;
  if(get_width(expr.type(), width))
    return true;

  if(expr.operands().size()!=2)
    throw "lambda takes two operands";

  if(expr.type().id()!="array") return true;

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

  mp_integer size;

  if(to_integer(array_size, size))
    return true;

  typet counter_type=expr.op0().type();

  for(mp_integer i=0; i<size; ++i)
  {
    exprt counter=from_integer(i, counter_type);

    exprt expr(expr.op1());
    replace_expr(expr.op0(), counter, expr);

    bvt tmp;
    if(convert_bv(expr, tmp))
      return true;

    unsigned offset=integer2long(i*tmp.size());

    if(size*tmp.size()!=width)
      throw "convert_lambda: unexpected operand width";

    for(unsigned j=0; j<tmp.size(); j++)
      bv[offset+j]=tmp[j];
  }

  return false;
}

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

Function: boolbvt::convert_array_of

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_array_of(const exprt &expr, bvt &bv)
{
  if(expr.operands().size()!=1)
    throw "array_of takes one operand";

  if(expr.type().id()!="array") return true;
  
  if(is_unbounded_array(expr.type()))
    return true;

  unsigned width;
  if(get_width(expr.type(), width))
    return true;

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

  mp_integer size;

  if(to_integer(array_size, size))
    return true;

  bvt tmp;
  if(convert_bv(expr.op0(), tmp))
    return true;
    
  bv.resize(width);

  for(mp_integer i=0; i<size; i=i+1)
  {
    unsigned offset=integer2long(i*tmp.size());

    if(size*tmp.size()!=width)
      throw "convert_array_of: unexpected operand width";

    for(unsigned j=0; j<tmp.size(); j++)
      bv[offset+j]=tmp[j];
  }

  return false;
}

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

Function: boolbvt::convert_bv_literals

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_bv_literals(const exprt &expr, bvt &bv)
{
  unsigned width;
  if(get_width(expr.type(), width))
    return true;

  bv.resize(width);

  const irept::subt &bv_sub=expr.find("bv").get_sub();

  if(bv_sub.size()!=width)
    throw "bv_literals with wrong size";

  for(unsigned i=0; i<width; i++)
    bv[i].set(atol(bv_sub[i].id().c_str()));

  return false;
}

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

Function: boolbvt::convert_symbol

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_symbol(const exprt &expr, bvt &bv)
{
  unsigned width;
  if(get_width(expr.type(), width))
    return true;

  bv.resize(width);

  const irep_idt &identifier=expr.get("identifier");

  if(identifier.empty())
    throw "got empty identifier";

  for(unsigned i=0; i<width; i++)
    bv[i]=get_literal(identifier, i, expr.type());

  return false;
}
   
/*******************************************************************\

Function: boolbvt::convert_struct_class

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_struct(const exprt &expr, bvt &bv)
{
  if(expr.type().id()!="struct")
    return true;

  unsigned width;
  if(get_width(expr.type(), width))
    return true;

  const irept &components=expr.type().find("components");

  if(expr.operands().size()!=components.get_sub().size())
    throw "struct: wrong number of arguments";

  bv.resize(width);

  unsigned offset=0, i=0;
  
  forall_irep(it, components.get_sub())
  {
    const typet &subtype=(typet &)it->find("type");
    const exprt &op=expr.operands()[i];

    if(subtype!=op.type())
      throw "struct: component type does not match: "+
        subtype.to_string()+" vs. "+
        op.type().to_string();
        
    unsigned subtype_width;
    assert(!get_width(subtype, subtype_width));

    if(subtype_width!=0)
    {
      bvt op_bv;
      
      if(convert_bv(op, op_bv))
        return true;
    
      assert(offset<width);
      assert(op_bv.size()==subtype_width);
      assert(offset+op_bv.size()<=width);

      for(unsigned j=0; j<op_bv.size(); j++)
        bv[offset+j]=op_bv[j];

      offset+=op_bv.size();
    }

    i++;    
  }
  
  assert(offset==width);

  return false;
}

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

Function: boolbvt::convert_union

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_union(const exprt &expr, bvt &bv)
{
  unsigned width;
  if(get_width(expr.type(), width))
    return true;

  if(expr.operands().size()!=1)
    throw "union: wrong number of arguments";

  bvt op_bv;
  if(convert_bv(expr.op0(), op_bv))
    return true;

  if(width<op_bv.size())
    throw "union: unexpected operand op width";

  bv.resize(width);

  for(unsigned i=0; i<op_bv.size(); i++)
    bv[i]=op_bv[i];

  const irep_idt &component_name=expr.get("component_name");
  const irept &components=expr.type().find("components");
  unsigned number;

  if(to_struct_type(expr.type()).
     component_number(component_name, number))
    throw "union: component not found";

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

  unsigned offset=width-component_bits;

  for(unsigned i=0; i<component_bits; i++)
    bv[offset+i]=prop.constant((number>>i)&1);

  return false;
}

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

Function: boolbvt::convert_constraint_select_one

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::convert_constraint_select_one(const exprt &expr, bvt &bv)
{
  const std::vector<exprt> &operands=expr.operands();

  if(expr.id()!="constraint_select_one")
    throw "expected constraint_select_one expression";

  if(operands.size()<2)
    throw "constraint_select_one takes at least two operands";

  if(expr.type()!=expr.op0().type())
    throw "constraint_select_one expects matching types";
    
  if(prop.has_set_to())
  {
    std::vector<bvt> op_bv;
    op_bv.resize(expr.operands().size());

    unsigned i=0;
    forall_operands(it, expr)
      if(convert_bv(*it, op_bv[i++]))
        return true;

    bv=op_bv[0];

    // add constraints

    bvt equal_bv;
    equal_bv.resize(bv.size());

    bvt b;
    b.reserve(op_bv.size()-1);

    for(unsigned i=1; i<op_bv.size(); i++)
    {
      if(op_bv[i].size()!=bv.size())
        throw "constraint_select_one expects matching width";

      for(unsigned j=0; j<bv.size(); j++)
        equal_bv[j]=prop.lequal(bv[j], op_bv[i][j]);

      b.push_back(prop.land(equal_bv));
    }

    prop.l_set_to_true(prop.lor(b));
  }
  else
    assert(false);

  return false;
}

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

Function: boolbvt::convert_rest

  Inputs:

 Outputs:

 Purpose:

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

literalt boolbvt::convert_rest(const exprt &expr)
{
  if(expr.type().id()!="bool")
  {
    std::cerr << expr << std::endl;
    throw "boolbvt::convert_rest got non-boolean operand";
  }

  const exprt::operandst &operands=expr.operands();

  if(expr.id()=="typecast")
    return convert_typecast(expr);
  else if(expr.id()=="=" || expr.id()=="notequal")
    return convert_equality(expr);
  else if(expr.id()=="<=" || expr.id()==">=" ||
          expr.id()=="<"  || expr.id()==">")
    return convert_bv_rel(expr);
  else if(expr.id()=="extractbit")
    return convert_extractbit(expr);
  else if(expr.id()=="forall")
    return convert_quantifier(expr);
  else if(expr.id()=="exists")
    return convert_quantifier(expr);
  else if(expr.id()=="index")
  {
    bvt bv;
    if(!convert_index(expr, bv))
    {
      if(bv.size()!=1)
        throw "convert_index returned non-bool bitvector";

      return bv[0];
    }
  }
  else if(expr.id()=="member")
  {
    bvt bv;
    if(!convert_member(expr, bv))
    {
      if(bv.size()!=1)
        throw "convert_member returned non-bool bitvector";

      return bv[0];
    }
  }
  else if(expr.id()=="case")
  {
    bvt bv;
    if(!convert_case(expr, bv))
    {
      if(bv.size()!=1)
        throw "convert_case returned non-bool bitvector";

      return bv[0];
    }
  }
  else if(expr.id()=="cond")
  {
    bvt bv;
    if(!convert_cond(expr, bv))
    {
      if(bv.size()!=1)
        throw "convert_cond returned non-bool bitvector";

      return bv[0];
    }
  }
  else if(expr.id()=="sign")
  {
    if(expr.operands().size()!=1)
      throw "sign expects one operand";

    bvt bv;

    if(!convert_bv(operands[0], bv))
    {
      if(bv.size()<1)
        throw "sign operator takes one non-empty operand";

      if(operands[0].type().id()=="signedbv")
        return bv[bv.size()-1];
      else if(operands[0].type().id()=="unsignedbv")
        return prop.constant(false);
      else if(operands[0].type().id()=="fixedbv")
        return bv[bv.size()-1];
    }
  }
  else if(expr.id()=="reduction_or"  || expr.id()=="reduction_and"  ||
          expr.id()=="reduction_nor" || expr.id()=="reduction_nand" ||
          expr.id()=="reduction_xor" || expr.id()=="reduction_xnor")
    return convert_reduction(expr);
  else if(has_prefix(expr.id_string(), "overflow-"))
    return convert_overflow(expr);

  return SUB::convert_rest(expr);
}

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

Function: boolbvt::boolbv_set_equality_to_true

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::boolbv_set_equality_to_true(const exprt &expr)
{
  const exprt::operandst &operands=expr.operands();

  if(operands.size()==2)
  {
    if(operands[0].id()=="symbol" &&
       operands[0].type()==operands[1].type() &&
       operands[0].type().id()!="bool")
    {
      // see if it is an unbounded array
      if(is_unbounded_array(operands[0].type()))
        return true;

      bvt bv1;
      if(!convert_bv(operands[1], bv1))
      {
        const irep_idt &identifier=
          operands[0].get("identifier");

        const typet &type=operands[0].type();

        for(unsigned i=0; i<bv1.size(); i++)
          set_literal(identifier, i, type, bv1[i]);

        return false;
      }
    }
  }

  return true;
}

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

Function: boolbvt::set_to

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::set_to(const exprt &expr, bool value)
{
  if(expr.type().id()!="bool")
  {
    std::cerr << expr << std::endl;
    throw "boolbvt::set_to got non-boolean operand";
  }

  if(value)
  {
    if(expr.id()=="=")
    {
      if(!boolbv_set_equality_to_true(expr))
        return;
    }
  }

  return SUB::set_to(expr, value);
}

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

Function: boolbvt::make_bv_expr

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::make_bv_expr(const typet &type, const bvt &bv, exprt &dest)
{
  dest=exprt("bv_literals", type);
  irept::subt &bv_sub=dest.add("bv").get_sub();

  bv_sub.resize(bv.size());

  for(unsigned i=0; i<bv.size(); i++)
    bv_sub[i].id(i2string(bv[i].get()));
}

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

Function: boolbvt::make_bv_expr

  Inputs:

 Outputs:

 Purpose:

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

void boolbvt::make_free_bv_expr(const typet &type, exprt &dest)
{
  unsigned width;
  if(get_width(type, width))
    throw "failed to get width of "+type.to_string();

  bvt bv;
  bv.resize(width);

  // make result free variables
  for(unsigned i=0; i<bv.size(); i++)
    bv[i]=prop.new_literal();

  make_bv_expr(type, bv, dest);
}

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

Function: boolbvt::is_unbounded_array

  Inputs:

 Outputs:

 Purpose:

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

bool boolbvt::is_unbounded_array(const typet &type) const
{
  if(type.id()!="array") return false;
  
  if(unbounded_array==U_ALL) return true;
  
  const exprt &size=(exprt &)type.find("size");
  
  mp_integer s;
  if(to_integer(size, s)) return true;

  if(unbounded_array==U_AUTO)
    if(s>1000) // magic number!
      return true;

  return false;
}
