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

Module: Invariant Set

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

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

#include <context.h>
#include <namespace.h>
#include <expr_util.h>
#include <bitvector.h>
#include <arith_tools.h>
#include <std_expr.h>
#include <simplify_expr.h>
#include <base_type.h>

#include <ansi-c/c_types.h>
#include <langapi/language_util.h>

#include "invariant_set.h"

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

Function: inv_object_storet::output

  Inputs:

 Outputs:

 Purpose:

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

void inv_object_storet::output(std::ostream &out) const
{
  for(unsigned i=0; i<entries.size(); i++)
    out << "STORE " << i << ": " << to_string(i, "") << std::endl;
}

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

Function: inv_object_storet::get

  Inputs:

 Outputs:

 Purpose:

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

bool inv_object_storet::get(const exprt &expr, unsigned &n)
{
  std::string s=build_string(expr);
  if(s=="") return true;
  
  // if it's a constant, we add it in any case
  if(is_constant(expr))
  {
    n=map.number(s);
    
    if(n>=entries.size())
    {
      entries.resize(n+1);
      entries[n].expr=expr;
      entries[n].is_constant=true;
    }
    
    return false;
  }

  return map.get_number(s, n);
}
  
/*******************************************************************\

Function: inv_object_storet::add

  Inputs:

 Outputs:

 Purpose:

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

unsigned inv_object_storet::add(const exprt &expr)
{
  std::string s=build_string(expr);
  
  assert(s!="");

  unsigned n=map.number(s);
  
  if(n>=entries.size())
  {
    entries.resize(n+1);
    entries[n].expr=expr;
    entries[n].is_constant=is_constant(expr);
  }
  
  return n;
}
  
/*******************************************************************\

Function: inv_object_storet::is_constant

  Inputs:

 Outputs:

 Purpose:

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

bool inv_object_storet::is_constant(unsigned n) const
{
  assert(n<entries.size());
  return entries[n].is_constant;
}  

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

Function: inv_object_storet::is_constant

  Inputs:

 Outputs:

 Purpose:

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

bool inv_object_storet::is_constant(const exprt &expr) const
{
  return expr.is_constant() ||
         is_constant_address(expr);
}  

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

Function: inv_object_storet::build_string

  Inputs:

 Outputs:

 Purpose:

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

std::string inv_object_storet::build_string(const exprt &expr) const
{
  // we ignore some casts
  if(expr.id()=="typecast")
  {
    assert(expr.operands().size()==1);
    
    if(expr.type().id()=="signedbv" ||
       expr.type().id()=="unsignedbv")
    {
      if(expr.op0().type().id()=="signedbv" ||
         expr.op0().type().id()=="unsignedbv")
      {
        if(bv_width(expr.type())>=bv_width(expr.op0().type()))
          return build_string(expr.op0());
      }
      else if(expr.op0().type().id()=="bool")
      {
        return build_string(expr.op0());
      }
    }
  }
  
  // we always track constants, but make sure they are uniquely
  // represented
  if(expr.is_constant())
  {
    // NULL?
    if(expr.type().id()=="pointer")
      if(expr.get("value")=="NULL")
        return "0";
  
    mp_integer i;

    if(!to_integer(expr, i))
      return integer2string(i);
  }
  
  // we also like "address_of" and "reference_to"
  // if the object is constant
  if(is_constant_address(expr))
    return from_expr(ns, "", expr);
  
  if(expr.id()=="member")
  {
    assert(expr.operands().size()==1);
    return build_string(expr.op0())+"."+expr.get_string("component_name");
  }
  
  if(expr.id()=="symbol")
    return expr.get_string("identifier");

  return "";
}

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

Function: invariant_sett::get_object

  Inputs:

 Outputs:

 Purpose:

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

bool invariant_sett::get_object(
  const exprt &expr,
  unsigned &n) const
{
  assert(object_store!=NULL);
  return object_store->get(expr, n);
}

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

Function: inv_object_storet::is_constant_address

  Inputs:

 Outputs:

 Purpose:

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

bool inv_object_storet::is_constant_address(const exprt &expr)
{
  if(expr.id()=="address_of" ||
     expr.id()=="implicit_address_of" ||
     expr.id()=="reference_to")
    if(expr.operands().size()==1)
      return is_constant_address_rec(expr.op0());
  
  return false;
}

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

Function: inv_object_storet::is_constant_address_rec

  Inputs:

 Outputs:

 Purpose:

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

bool inv_object_storet::is_constant_address_rec(const exprt &expr)
{
  if(expr.id()=="symbol")
    return true;
  else if(expr.id()=="member")
  {
    assert(expr.operands().size()==1);
    return is_constant_address_rec(expr.op0());
  }
  else if(expr.id()=="index")
  {
    assert(expr.operands().size()==2);
    if(expr.op1().is_constant())
      return is_constant_address_rec(expr.op0());
  }
  
  return false;
}

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

Function: invariant_sett::add

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::add(
  const std::pair<unsigned, unsigned> &p,
  ineq_sett &dest)
{
  eq_set.check_index(p.first);
  eq_set.check_index(p.second);

  // add all. Quadratic.
  unsigned f_r=eq_set.find(p.first);
  unsigned s_r=eq_set.find(p.second);
  
  for(unsigned f=0; f<eq_set.size(); f++)
    if(eq_set.find(f)==f_r)
      for(unsigned s=0; s<eq_set.size(); s++)
        if(eq_set.find(s)==s_r)
          dest.insert(std::pair<unsigned, unsigned>(f, s));
}

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

Function: invariant_sett::add_eq

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::add_eq(const std::pair<unsigned, unsigned> &p)
{
  eq_set.make_union(p.first, p.second);

  // check if there is a contradiction with two constants
  unsigned r=eq_set.find(p.first);
  
  bool constant_seen=false;
  mp_integer c;
  
  for(unsigned i=0; i<eq_set.size(); i++)
    if(eq_set.find(i)==r)
      if(object_store->is_constant(i))
      {
        if(constant_seen)
        {
          // contradiction
          make_false();
          return;
        }
        else 
          constant_seen=true;
      }

  // replicate <= and != constraints
  
  for(ineq_sett::const_iterator it=le_set.begin();
      it!=le_set.end();
      it++)
    add_eq(le_set, p, *it);

  for(ineq_sett::const_iterator it=ne_set.begin();
      it!=ne_set.end();
      it++)
    add_eq(ne_set, p, *it);
}

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

Function: invariant_sett::add_eq

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::add_eq(
  ineq_sett &dest,
  const std::pair<unsigned, unsigned> &eq,
  const std::pair<unsigned, unsigned> &ineq)
{
  std::pair<unsigned, unsigned> n;

  // uhuh. Need to try all pairs

  if(eq.first==ineq.first)
  {
    n=ineq;
    n.first=eq.second; 
    dest.insert(n);
  }
  
  if(eq.first==ineq.second)
  {
    n=ineq;
    n.second=eq.second;
    dest.insert(n);
  }
  
  if(eq.second==ineq.first)
  {
    n=ineq;
    n.first=eq.first; 
    dest.insert(n);
  }
  
  if(eq.second==ineq.second)
  {
    n=ineq;
    n.second=eq.first;
    dest.insert(n);
  }
}

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

Function: invariant_sett::is_eq

  Inputs:

 Outputs:

 Purpose:

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

tvt invariant_sett::is_eq(std::pair<unsigned, unsigned> p) const
{
  std::pair<unsigned, unsigned> s=p;
  std::swap(s.first, s.second);
  
  if(has_eq(p))
    return tvt(true);
    
  if(has_ne(p) || has_ne(s))
    return tvt(false);

  return tvt(tvt::TV_UNKNOWN);
}
  
/*******************************************************************\

Function: invariant_sett::is_le

  Inputs:

 Outputs:

 Purpose:

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

tvt invariant_sett::is_le(std::pair<unsigned, unsigned> p) const
{
  std::pair<unsigned, unsigned> s=p;
  std::swap(s.first, s.second);
  
  if(has_eq(p))
    return tvt(true);
  
  if(has_le(p))
    return tvt(true);
    
  if(has_le(s))
    if(has_ne(s) || has_ne(p))
      return tvt(false);
    
  return tvt(tvt::TV_UNKNOWN);
}

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

Function: invariant_sett::output

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::output(
  const irep_idt &identifier,
  std::ostream &out) const
{
  #if 0
  for(unsigned i=0; i<active.size(); i++)
    out << "ACTIVE: " << to_string(i, identifier) << " " << is_active(i) << std::endl;
  #endif

  if(is_false)
  {
    out << "FALSE" << std::endl;
    return;
  }

  assert(object_store!=NULL);

  for(unsigned i=0; i<eq_set.size(); i++)
    if(eq_set.is_root(i) &&
       eq_set.count(i)>=2)
    {
      bool first=true;
      for(unsigned j=0; j<eq_set.size(); j++)
        if(eq_set.find(j)==i)
        {
          if(first)
            first=false;
          else
            out << " = ";

          out << to_string(j, identifier);
        }
        
      out << std::endl;
    }

  for(ineq_sett::const_iterator it=le_set.begin();
      it!=le_set.end();
      it++)
  {
    out << to_string(it->first, identifier)
        << " <= " << to_string(it->second, identifier)
        << std::endl;
  }

  for(ineq_sett::const_iterator it=ne_set.begin();
      it!=ne_set.end();
      it++)
  {
    out << to_string(it->first, identifier)
        << " != " << to_string(it->second, identifier)
        << std::endl;
  }
}

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

Function: invariant_sett::strengthen

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::strengthen(
  const exprt &expr,
  bool negate)
{
  if(expr.type().id()!="bool")
    throw "non-Boolean argument to strengthen()";

  #if 0
  std::cout << "S: " << from_expr(*ns, "", expr) << std::endl;
  #endif
    
  if(is_false)
  {
    // can't get any stronger
    return;
  }
  
  if(expr.is_true())
  {
    if(negate)
      make_false();

    // do nothing, it's useless
  }
  else if(expr.is_false())
  {
    if(negate) return;
    // wow, that's strong
    make_false();
  }
  else if(expr.id()=="typecast")
  {
    assert(expr.operands().size()==1);

    if(expr.op0().type().id()=="unsignedbv" ||
       expr.op0().type().id()=="signedbv")
    {
      exprt tmp("=", typet("bool"));
      tmp.copy_to_operands(expr.op0(), gen_zero(expr.op0().type()));
      strengthen(tmp, negate);
    }
  }
  else if(expr.id()=="not")
  {
    assert(expr.operands().size()==1);
    strengthen(expr.op0(), !negate);
  }
  else if(expr.id()=="and")
  {
    if(negate) return;

    forall_operands(it, expr)
      strengthen(*it);
  }
  else if(expr.id()=="<=" ||
          expr.id()==">=" ||
          expr.id()=="<" ||
          expr.id()==">" ||
          expr.id()=="=" ||
          expr.id()=="notequal")
  {
    if(negate)
    {
      exprt tmp(expr);

      if(expr.id()=="<=")
        tmp.id(">");
      else if(expr.id()==">=")
        tmp.id("<");
      else if(expr.id()=="<")
        tmp.id(">=");
      else if(expr.id()==">")
        tmp.id("<=");
      else if(expr.id()=="=")
        tmp.id("notequal");
      else if(expr.id()=="notequal")
        tmp.id("=");
      else
        assert(false);

      strengthen(tmp);
      return;
    }

    assert(expr.operands().size()==2);
    
    const typet &op_type=ns->follow(expr.op0().type());
    
    if(op_type.id()=="struct")
    {
      if(expr.id()!="=") return;
    
      const struct_typet &struct_type=to_struct_type(op_type);
  
      const struct_typet::componentst &c=struct_type.components();
  
      exprt lhs_member_expr("member");
      exprt rhs_member_expr("member");
      lhs_member_expr.copy_to_operands(expr.op0());
      rhs_member_expr.copy_to_operands(expr.op1());
  
      for(struct_typet::componentst::const_iterator
          it=c.begin();
          it!=c.end();
          it++)
      {
        const irep_idt &component_name=it->get("name");

        lhs_member_expr.set("component_name", component_name);
        rhs_member_expr.set("component_name", component_name);
        lhs_member_expr.type()=it->type();
        rhs_member_expr.type()=it->type();

        expr_equalityt equality;
        equality.lhs()=lhs_member_expr;
        equality.rhs()=rhs_member_expr;
        
        // recursive call
        strengthen(equality);
      }
      
      return;
    }

    std::pair<unsigned, unsigned> p, s;
    
    if(get_object(expr.op0(), p.first) ||
       get_object(expr.op1(), p.second))
      return;
      
    s=p;
    std::swap(s.first, s.second);

    if(expr.id()=="<=")
    {
      add_le(p);
    }
    else if(expr.id()==">=")
    {
      add_le(s);
    }
    else if(expr.id()=="<")
    {
      add_le(p);
      add_ne(p);
    }
    else if(expr.id()==">")
    {
      add_le(s);
      add_ne(s);
    }
    else if(expr.id()=="=")
    {
      // contradiction?
      if(has_ne(p) || has_ne(s))
      {
        make_false();
      }
      else if(!has_eq(p))
      {
        add_eq(p);

        // check if there is a contradiction with two constants
        unsigned r=eq_set.find(p.first);
        
        bool constant_seen=false;
        mp_integer c;
        
        for(unsigned i=0; i<eq_set.size(); i++)
          if(eq_set.find(i)==r)
            if(object_store->is_constant(i))
            {
              if(constant_seen)
              {
                make_false();
                break;
              }
              else 
                constant_seen=true;
            }
      }
    }
    else if(expr.id()=="notequal")
    {
      // check if this is a contradiction
      if(has_eq(p))
        make_false();
      else
        add_ne(p);
    }
    else
      assert(false);
  }
}

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

Function: invariant_sett::implies

  Inputs:

 Outputs:

 Purpose:

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

tvt invariant_sett::implies(
  const exprt &expr,
  bool negate) const
{
  if(expr.type().id()!="bool")
    throw "implies: non-Boolean expression";
    
  #if 0
  std::cout << "I: " << from_expr(*ns, "", expr) << std::endl;
  #endif
  
  if(is_false)
  {
    // can't get any stronger
    return tvt(true);
  }

  if(expr.is_true())
  {
    if(!negate)
      return tvt(true);
  }
  else if(expr.id()=="not")
  {
    assert(expr.operands().size()==1);
    return implies(expr.op0(), !negate);
  }
  else if(expr.id()=="and")
  {
    if(!negate)
    {
      forall_operands(it, expr)
        if(implies(*it)!=tvt(true))
          return tvt(tvt::TV_UNKNOWN);
      
      return tvt(true);
    }
  }
  else if(expr.id()=="<=" ||
          expr.id()==">=" ||
          expr.id()=="<" ||
          expr.id()==">" ||
          expr.id()=="=" ||
          expr.id()=="notequal")
  {
    if(negate)
    {
      exprt tmp(expr);

      if(expr.id()=="<=")
        tmp.id(">");
      else if(expr.id()==">=")
        tmp.id("<");
      else if(expr.id()=="<")
        tmp.id(">=");
      else if(expr.id()==">")
        tmp.id("<=");
      else if(expr.id()=="=")
        tmp.id("notequal");
      else if(expr.id()=="notequal")
        tmp.id("=");
      else
        assert(false);
        
      return implies(tmp);
    }

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

    std::pair<unsigned, unsigned> p;
    
    if(get_object(expr.op0(), p.first) ||
       get_object(expr.op1(), p.second))
      return tvt(tvt::TV_UNKNOWN);
    
    if(expr.id()=="<=")
      return is_le(p);
    else if(expr.id()==">=")
      return is_ge(p);
    else if(expr.id()=="<")
      return is_lt(p);
    else if(expr.id()==">")
      return is_gt(p);
    else if(expr.id()=="=")
      return is_eq(p);
    else if(expr.id()=="notequal")
      return is_ne(p);
    else
      assert(false);
  }

  return tvt(tvt::TV_UNKNOWN);
}

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

Function: invariant_sett::simplify

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::simplify(
  exprt &expr) const
{
  if(expr.id()=="address_of" ||
     expr.id()=="implicit_address_of" ||
     expr.id()=="reference_to")
    return;

  Forall_operands(it, expr)
    simplify(*it);

  if(expr.id()=="symbol" ||
     expr.id()=="member")
  {
    exprt tmp=get_constant(expr);
    if(tmp.is_not_nil())
      expr.swap(tmp);
  }
}

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

Function: invariant_sett::get_constant

  Inputs:

 Outputs:

 Purpose:

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

exprt invariant_sett::get_constant(const exprt &expr) const
{
  unsigned a;

  if(!get_object(expr, a))
  {  
    unsigned r=eq_set.find(a);

    // is it a constant?
    for(unsigned i=0; i<eq_set.size(); i++)
      if(eq_set.find(i)==r)
      {
        const exprt &e=object_store->get_expr(i);
        
        if(e.is_constant())
        {
          mp_integer value;
          assert(!to_integer(e, value));
          
          if(expr.type().id()=="pointer")
          {
            if(value==0)
            {
              exprt tmp("constant", expr.type());
              tmp.set("value", "NULL");
              return tmp;
            }
          }
          else
            return from_integer(value, expr.type());
        }
        else if(object_store->is_constant_address(e))
        {
          if(e.type()==expr.type())
            return e;
            
          exprt tmp("typecast", expr.type());
          tmp.copy_to_operands(e);
          return tmp;
        }
      }
  }
  
  return static_cast<const exprt &>(nil_rep);
}

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

Function: inv_object_storet::to_string

  Inputs:

 Outputs:

 Purpose:

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

std::string inv_object_storet::to_string(
  unsigned a,
  const irep_idt &identifier) const
{
  //return from_expr(ns, "", get_expr(a));
  return id2string(map[a]);
}

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

Function: invariant_sett::to_string

  Inputs:

 Outputs:

 Purpose:

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

std::string invariant_sett::to_string(
  unsigned a,
  const irep_idt &identifier) const
{
  assert(object_store!=NULL);
  return object_store->to_string(a, identifier);
}

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

Function: invariant_sett::make_intersection

  Inputs:

 Outputs:

 Purpose:

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

bool invariant_sett::make_intersection(
  const invariant_sett &other)
{
  if(other.threaded &&
     !threaded)
  {
    make_threaded();
    return true; // change
  }
  
  if(threaded)
    return false; // no change

  if(other.is_false)
    return false; // no change
    
  if(is_false)
  {
    // copy
    is_false=false;
    eq_set=other.eq_set;
    le_set=other.le_set;
    ne_set=other.ne_set;
    
    #if 0
    filter_active();
    #endif
    
    return true; // change
  }
  
  // equalities first
  unsigned old_eq_roots=eq_set.count_roots();
  
  eq_set.intersection(other.eq_set);

  // inequalities
  unsigned old_ne_set=ne_set.size();
  unsigned old_le_set=le_set.size();
  
  intersection(ne_set, other.ne_set);
  intersection(le_set, other.le_set);

  #if 0
  filter_active();
  #endif
  
  if(old_eq_roots!=eq_set.count_roots()) return true;
  if(old_ne_set!=ne_set.size()) return true;
  if(old_le_set!=le_set.size()) return true;
  
  return false; // no change
}

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

Function: invariant_sett::modifies

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::modifies(
  const exprt &lhs)
{
  if(lhs.id()=="symbol" ||
     lhs.id()=="member")
  {
    unsigned a;
    if(!get_object(lhs, a))
    {
      // ==
      eq_set.isolate(a);
    
      // !=
      remove(ne_set, a);

      // <=
      remove(le_set, a);
    }
  }
  else if(lhs.id()=="index")
  {
    // we don't track arrays
  }
  else if(lhs.id()=="dereference")
  {
    // be very, very conservative for now
    make_true();
  }
  else if(lhs.id()=="object_value")
  {
    // be very, very conservative for now
    make_true();
  }
  else if(lhs.id()=="if")
  {
    // we just assume both are changed
    assert(lhs.operands().size()==3);
    modifies(lhs.op1());
    modifies(lhs.op2());
  }
  else if(lhs.id()=="typecast")
  {
    // just go down
    assert(lhs.operands().size()==1);
    modifies(lhs.op0());
  }
  else
    throw "modifies: unexpected lhs: "+lhs.id_string();
}

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

Function: invariant_sett::filter_active

  Inputs:

 Outputs:

 Purpose:

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

#if 0
void invariant_sett::filter_active()
{
  for(unsigned i=0; i<eq_set.size(); i++)
    if(!is_active(i))
      eq_set.isolate(i);

  filter_active(le_set);
  filter_active(ne_set);
}
#endif

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

Function: invariant_sett::filter_active

  Inputs:

 Outputs:

 Purpose:

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

#if 0
void invariant_sett::filter_active(ineq_sett &dest)
{  
  for(ineq_sett::iterator it=dest.begin();
      it!=dest.end();
      ) // no it++
  {
    ineq_sett::iterator next(it);
    next++;

    if(!is_active(it->first) ||
       !is_active(it->second))
      dest.erase(it);

    it=next;
  }
}
#endif

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

Function: invariant_sett::assignment

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::assignment(
  const exprt &lhs,
  const exprt &rhs)
{
  expr_equalityt equality;
  equality.lhs()=lhs;
  equality.rhs()=rhs;
  
  // first evaluate RHS
  simplify(equality.rhs());
  ::simplify(equality.rhs());
  
  // now kill LHS
  modifies(lhs);
  
  // and put it back
  strengthen(equality);
}

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

Function: invariant_sett::apply_code

  Inputs:

 Outputs:

 Purpose:

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

void invariant_sett::apply_code(const codet &code)
{
  const irep_idt &statement=code.get("statement");

  if(statement=="block")
  {
    forall_operands(it, code)
      apply_code(to_code(*it));
  }
  else if(statement=="assign" ||
          statement=="init")
  {
    if(code.operands().size()!=2)
      throw "assignment expected to have two operands";

    assignment(code.op0(), code.op1());
  }
  else if(statement=="decl")
  {
    if(code.operands().size()==2)
      assignment(code.op0(), code.op1());
    else
      modifies(code.op0());
  }
  else if(statement=="expression")
  {
    // this never modifies anything
  }
  else if(statement=="function_pointer_call" ||
          statement=="functioncall")
  {
    // may be a desaster
    make_true();
  }
  else if(statement=="cpp_delete" ||
          statement=="cpp_delete[]")
  {
    // does nothing
  }
  else if(statement=="printf")
  {
    // does nothing
  }
  else if(statement=="lock" || statement=="unlock")
  {
    // ignore for now
  }
  else
  {
    std::cerr << code.pretty() << std::endl;
    throw "invariant_sett: unexpected statement: "+id2string(statement);
  }
}
