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

Module:

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

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

//#define DEBUG

#include <assert.h>

#include "arrays.h"

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

Function: arrayst::record_array_equality

  Inputs:

 Outputs:

 Purpose:

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

literalt arrayst::record_array_equality(
  const exprt &equality)
{
  if(equality.operands().size()!=2)
    throw "function equality expected to have two operands";
    
  // check types
  if(equality.op0().type()!=equality.op1().type())
  {
    std::cout << equality.pretty() << std::endl;
    throw "record_array_equality got equality without matching types";
  }

  array_equalities.push_back(array_equalityt());
  
  array_equalities.back().f1=equality.op0();
  array_equalities.back().f2=equality.op1();
  array_equalities.back().l=SUB::equality(equality.op0(), equality.op1());

  arrays.make_union(equality.op0(), equality.op1());
  collect_arrays(equality.op0());
  collect_arrays(equality.op1());

  return array_equalities.back().l;
}

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

Function: arrayst::collect_arrays

  Inputs:

 Outputs:

 Purpose:

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

void arrayst::collect_arrays(const exprt &a)
{
  if(a.id()=="with")
  {
    if(a.operands().size()!=3)
      throw "with expected to have three operands";

    // check types
    if(a.type()!=a.op0().type())
    {
      std::cout << a.pretty() << std::endl;
      throw "collect_arrays got with without matching types";
    }
      
    arrays.make_union(a, a.op0());
    collect_arrays(a.op0());
    
    // make sure this shows as an application
    exprt index_expr("index", a.type().subtype());
    index_expr.copy_to_operands(a.op0(), a.op1());
    record_function_application(index_expr);
  }
  else if(a.id()=="if")
  {
    if(a.operands().size()!=3)
      throw "if expected to have three operands";

    // check types
    if(a.type()!=a.op1().type())
    {
      std::cout << a.pretty() << std::endl;
      throw "collect_arrays got if without matching types";
    }
      
    // check types
    if(a.type()!=a.op2().type())
    {
      std::cout << a.pretty() << std::endl;
      throw "collect_arrays got if without matching types";
    }

    arrays.make_union(a, a.op1());
    arrays.make_union(a, a.op2());
    collect_arrays(a.op1());
    collect_arrays(a.op2());
  }
  else if(a.id()=="symbol")
  {
  }
  else if(a.id()=="nondet_symbol")
  {
  }
  else if(a.id()=="constant" || a.id()=="array")
  {
  }
  else if(a.id()=="array_of")
  {
  }
  else
    throw "unexpected array expression: "+a.id_string();
}

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

Function: arrayst::add_array_constraints

  Inputs:

 Outputs:

 Purpose:

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

void arrayst::add_array_constraints()
{
  // first get index map
  get_index_map();

  // add constraints for if and with
  for(unsigned i=0; i<arrays.size(); i++)
    add_array_constraints(arrays[i]);

  // add constraints for equalities
  for(array_equalitiest::const_iterator it=
      array_equalities.begin();
      it!=array_equalities.end();
      it++)
    add_array_constraints(*it);
}

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

Function: arrayst::get_index_map

  Inputs:

 Outputs:

 Purpose:

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

//#include <cpp/expr2cpp.h>

void arrayst::get_index_map()
{
  // start fresh
  index_map.clear();
  
  for(unsigned i=0; i<arrays.size(); i++)
  {
    #if 0
    {
      contextt context;
      namespacet ns(context);
      std::string code;
      expr2cpp(arrays[i], ns, code);
      std::cout << "N " << i << " " << code << std::endl;
      std::cout << "ROOT " << arrays.is_root(i) << std::endl;
    }
    #endif

    const exprt &root=arrays.find(arrays[i]);
    
    function_mapt::const_iterator it=function_map.find(arrays[i]);
    
    if(it==function_map.end()) continue;

    index_sett &index_set=index_map[root];
    
    for(applicationst::const_iterator
        a_it=it->second.applications.begin();
        a_it!=it->second.applications.end();
        a_it++)
    {
      // we expect to see an index expression here
      if(a_it->id()!="index")
        throw "array logic expected index expression "
              "as function, but got "+a_it->id_string();
        
      if(a_it->operands().size()!=2)
        throw "index takes two operands";
        
      index_set.insert(a_it->op1());
    }
  }
}

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

Function: arrayst::add_array_constraints

  Inputs:

 Outputs:

 Purpose:

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

void arrayst::add_array_constraints(
  const array_equalityt &array_equality)
{
  // we got x=y

  // get previous function applications
  
  const index_sett &index_set=
    index_map[arrays.find(array_equality.f1)];

  // both f1 and f2 are in the same set anyways

  for(index_sett::const_iterator
      it=index_set.begin();
      it!=index_set.end();
      it++)
  {
    exprt index_expr1("index", array_equality.f1.type().subtype());
    index_expr1.copy_to_operands(array_equality.f1, *it);

    exprt index_expr2("index", array_equality.f2.type().subtype());
    index_expr2.copy_to_operands(array_equality.f2, *it);
    
    assert(index_expr1.type()==index_expr2.type());

    exprt equality_expr("=", typet("bool"));
    equality_expr.reserve_operands(2);
    equality_expr.move_to_operands(index_expr1, index_expr2);
    
    // add constraint

    bvt bv;
    bv.push_back(prop.lnot(array_equality.l));
    bv.push_back(convert(equality_expr));
    prop.lcnf(bv);    
  }
}  

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

Function: arrayst::add_array_constraints

  Inputs:

 Outputs:

 Purpose:

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

void arrayst::add_array_constraints(const exprt &expr)
{
  // we got x=y
  
  if(expr.id()=="with")
    add_array_constraints_with(expr);
  else if(expr.id()=="if")
    add_array_constraints_if(expr);
  else if(expr.id()=="symbol")
  {
  }
  else if(expr.id()=="nondet_symbol")
  {
  }
  else if(expr.id()=="constant" || expr.id()=="array")
  {
  }
  else if(expr.id()=="array_of")
    add_array_constraints_array_of(expr);
  else
    throw "unexpected array expression: "+expr.id_string();
}

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

Function: arrayst::add_array_constraints_with

  Inputs:

 Outputs:

 Purpose:

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

void arrayst::add_array_constraints_with(const exprt &expr)
{
  // we got x=(y with [i:=v])
  
  assert(expr.id()=="with");

  assert(expr.operands().size()==3);

  // add constaint x[i]=v
  
  const exprt &index=expr.op1();
  const exprt &value=expr.op2();

  {  
    exprt index_expr("index", expr.type().subtype());
    index_expr.copy_to_operands(expr, index);

    assert(index_expr.type()==value.type());

    exprt equality_expr("=", typet("bool"));
    equality_expr.reserve_operands(2);
    equality_expr.move_to_operands(index_expr);
    equality_expr.copy_to_operands(value);

    set_to_true(equality_expr);    
  }

  // get previous function applications for "else" case

  const index_sett &index_set=
    index_map[arrays.find(expr)];

  for(index_sett::const_iterator
      it=index_set.begin();
      it!=index_set.end();
      it++)
  {
    exprt other_index(*it);

    if(other_index!=index)
    {
      // we first build the guard
      
      if(other_index.type()!=index.type())
        other_index.make_typecast(index.type());

      exprt guard_expr("=", typet("bool"));
      guard_expr.copy_to_operands(index, other_index);
      
      literalt guard_lit=convert(guard_expr);

      if(guard_lit!=prop.constant(true))
      {
        exprt index_expr1("index", expr.type().subtype());
        index_expr1.copy_to_operands(expr, other_index);

        exprt index_expr2("index", expr.type().subtype());
        index_expr2.copy_to_operands(expr.op0(), other_index);

        assert(index_expr1.type()==index_expr2.type());

        exprt equality_expr("=", typet("bool"));
        equality_expr.reserve_operands(2);
        equality_expr.move_to_operands(index_expr1, index_expr2);
        
        literalt equality_lit=convert(equality_expr);

        // add constraint
        bvt bv;
        bv.reserve(2);
        bv.push_back(equality_lit);
        bv.push_back(guard_lit);
        prop.lcnf(bv);
      }
    }
  }
}

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

Function: arrayst::add_array_constraints_array_of

  Inputs:

 Outputs:

 Purpose:

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

void arrayst::add_array_constraints_array_of(const exprt &expr)
{
  // we got x=array_of[v]
  
  assert(expr.id()=="array_of");

  assert(expr.operands().size()==1);

  // get other function applications

  const index_sett &index_set=
    index_map[arrays.find(expr)];

  for(index_sett::const_iterator
      it=index_set.begin();
      it!=index_set.end();
      it++)
  {
    exprt index_expr("index", expr.type().subtype());
    index_expr.copy_to_operands(expr, *it);

    assert(index_expr.type()==expr.op0().type());

    exprt equality_expr("=", typet("bool"));
    equality_expr.reserve_operands(2);
    equality_expr.move_to_operands(index_expr);
    equality_expr.copy_to_operands(expr.op0());
    
    // add constraint
    set_to_true(equality_expr);
  }
}

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

Function: arrayst::add_array_constraints_if

  Inputs:

 Outputs:

 Purpose:

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

void arrayst::add_array_constraints_if(const exprt &expr)
{
  // we got x=(c?a:b)
  
  assert(expr.id()=="if");

  assert(expr.operands().size()==3);

  literalt cond_lit=convert(expr.op0());

  // get previous function applications

  const index_sett &index_set=
    index_map[arrays.find(expr)];

  // first do true case

  for(index_sett::const_iterator
      it=index_set.begin();
      it!=index_set.end();
      it++)
  {
    exprt index_expr1("index", expr.type().subtype());
    index_expr1.copy_to_operands(expr, *it);

    exprt index_expr2("index", expr.type().subtype());
    index_expr2.copy_to_operands(expr.op1(), *it);

    assert(index_expr1.type()==index_expr2.type());

    exprt equality_expr("=", typet("bool"));
    equality_expr.reserve_operands(2);
    equality_expr.move_to_operands(index_expr1, index_expr2);
    
    // add constraint

    bvt bv;
    bv.push_back(prop.lnot(cond_lit));
    bv.push_back(convert(equality_expr));
    prop.lcnf(bv);
  }

  // now the false case

  for(index_sett::const_iterator
      it=index_set.begin();
      it!=index_set.end();
      it++)
  {
    exprt index_expr1("index", expr.type().subtype());
    index_expr1.copy_to_operands(expr, *it);

    exprt index_expr2("index", expr.type().subtype());
    index_expr2.copy_to_operands(expr.op2(), *it);

    assert(index_expr1.type()==index_expr2.type());

    exprt equality_expr("=", typet("bool"));
    equality_expr.reserve_operands(2);
    equality_expr.move_to_operands(index_expr1, index_expr2);
    
    // add constraint

    bvt bv;
    bv.push_back(cond_lit);
    bv.push_back(convert(equality_expr));
    prop.lcnf(bv);
  }
}
