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

Module: Goto Program Template

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

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

#ifndef CPROVER_GOTO_PROGRAM_TEMPLATE_H
#define CPROVER_GOTO_PROGRAM_TEMPLATE_H

#include <assert.h>

#include <iostream>
#include <set>

#include <expr.h>
#include <namespace.h>

typedef enum { GOTO, ASSUME, ASSERT, OTHER, SYNC, SKIP,
               START_THREAD, END_THREAD, LOCATION, END_FUNCTION,
               ATOMIC_BEGIN, ATOMIC_END, RETURN }
  goto_program_instruction_typet;

template <class T>
class goto_program_templatet
{
public:
  // DO NOT COPY ME! I HAVE POINTERS IN ME!
  goto_program_templatet(const goto_program_templatet &)
  {
    assert(instructions.empty());
  }

  // DO NOT COPY ME! I HAVE POINTERS IN ME!
  goto_program_templatet &operator=(const goto_program_templatet &p)
  {
    assert(p.instructions.empty());
    instructions.clear();
    update();
    return *this;
  }

  // local variables
  typedef std::set<irep_idt> local_variablest;
  
  class instructiont
  {
  public:
    T code;
    
    // keep track of the location in the source file
    locationt location;

    // what kind of instruction?
    goto_program_instruction_typet type;

    // for gotos, assume, assert, lock, unlock
    exprt guard;
    
    // for sync
    irep_idt event;

    // for gotos
    typedef typename std::list<class instructiont>::iterator targett;
    typedef typename std::list<class instructiont>::const_iterator const_targett;
    typedef std::list<targett> targetst;
    typedef std::list<const_targett> const_targetst;

    targetst targets;
    
    typedef std::list<irep_idt> labelst;
    labelst labels;

    std::set<targett> incoming_edges;
    
    void make_goto()
    {
      type=GOTO;
      targets.clear();
      guard.make_true();
      event="";
      code.make_nil();
    }
     
    void make_return()
    {
      type=RETURN;
      targets.clear();
      guard.make_true();
      event="";
      code.make_nil();
    }
     
    void make_skip()
    {
      type=SKIP;
      targets.clear();
      guard.make_true();
      event="";
      code.make_nil();
    }
     
    void make_assertion()
    {
      type=ASSERT;
      targets.clear();
      guard.make_true();
      event="";
      code.make_nil();
    }
     
    void make_goto(typename std::list<class instructiont>::iterator _target)
    {
      make_goto();
      targets.push_back(_target);
    }
    
    void make_other()
    {
      type=OTHER;
      targets.clear();
      guard.make_true();
      event="";
      code.make_nil();
    }
    
    // valid local variables at this point
    local_variablest local_variables;
    
    void add_local_variables(
      const local_variablest &locals)
    {
      local_variables.insert(locals.begin(), locals.end());
    }   
    
    void add_local_variables(
      const std::list<irep_idt> &locals)
    {
      for(typename std::list<irep_idt>::const_iterator
          it=locals.begin();
          it!=locals.end();
          it++)
        local_variables.insert(*it);
    }   
    
    inline bool is_goto        () const { return type==GOTO;         }
    inline bool is_return      () const { return type==RETURN;       }
    inline bool is_skip        () const { return type==SKIP;         }
    inline bool is_location    () const { return type==LOCATION;     }
    inline bool is_other       () const { return type==OTHER;        }
    inline bool is_assume      () const { return type==ASSUME;       }
    inline bool is_assert      () const { return type==ASSERT;       }
    inline bool is_sync        () const { return type==SYNC;         }
    inline bool is_atomic_begin() const { return type==ATOMIC_BEGIN; }
    inline bool is_atomic_end  () const { return type==ATOMIC_END;   }
    inline bool is_start_thread() const { return type==START_THREAD; }
    inline bool is_end_thread  () const { return type==END_THREAD;   }
    inline bool is_end_function() const { return type==END_FUNCTION; }

    instructiont():
      location((const locationt &)nil_rep),
      type(OTHER),
      ordering(0)
    {
      guard.make_true();
    }

    instructiont(goto_program_instruction_typet _type):
      location((const locationt &)nil_rep),
      type(_type),
      ordering(0)
    {
      guard.make_true();
    }
    
    void swap(instructiont &instruction)
    {
      instruction.code.swap(code);
      instruction.location.swap(location);
      std::swap(instruction.type, type);
      instruction.guard.swap(guard);
      instruction.event.swap(event);
      instruction.targets.swap(targets);
      instruction.local_variables.swap(local_variables);
    }
    
    // an ordering
    unsigned ordering;
    
    bool is_backwards_goto() const
    {
      if(!is_goto()) return false;

      for(typename targetst::const_iterator
          it=targets.begin();
          it!=targets.end();
          it++)
        if((*it)->ordering<ordering)
          return true;
          
      return false;
    }
  };

  typedef std::list<class instructiont> instructionst;
  
  typedef typename instructionst::iterator targett;
  typedef typename instructionst::const_iterator const_targett;
  typedef typename std::list<targett> targetst;
  typedef typename std::list<const_targett> const_targetst;

  instructionst instructions;
  
  bool has_local_variable(
    class instructiont &instruction,
    const irep_idt &identifier)
  {
    return instruction.local_variables.count(identifier)!=0;
  }

  void get_successors(
    targett target,
    targetst &successors)
  {
    successors.clear();
    if(target==instructions.end()) return;

    targett next=target;
    next++;
    
    const instructiont &i=*target;
  
    if(i.is_goto())
    {
      for(typename targetst::const_iterator
          t_it=i.targets.begin();
          t_it!=i.targets.end();
          t_it++)
        successors.push_back(*t_it);
  
      if(!i.guard.is_true())
        successors.push_back(next);
    }
    else if(i.is_start_thread())
    {
      for(typename targetst::const_iterator
          t_it=i.targets.begin();
          t_it!=i.targets.end();
          t_it++)
        successors.push_back(*t_it);
  
      successors.push_back(next);
    }
    else if(i.is_end_thread())
    {
      // no successors
    }
    else if(i.is_return())
    {
      // the successor is the end_function at the end
      successors.push_back(--instructions.end());
    }
    else
      successors.push_back(next);
  }

  void get_successors(
    const_targett target,
    const_targetst &successors) const
  {
    successors.clear();
    if(target==instructions.end()) return;

    const_targett next=target;
    next++;
    
    const instructiont &i=*target;
  
    if(i.is_goto())
    {
      for(typename targetst::const_iterator
          t_it=i.targets.begin();
          t_it!=i.targets.end();
          t_it++)
        successors.push_back(*t_it);
  
      if(!i.guard.is_true())
        successors.push_back(next);
    }
    else if(i.is_start_thread())
    {
      for(typename targetst::const_iterator
          t_it=i.targets.begin();
          t_it!=i.targets.end();
          t_it++)
        successors.push_back(*t_it);
  
      successors.push_back(next);
    }
    else if(i.is_end_thread())
    {
      // no successors
    }
    else if(i.is_return())
    {
      // the successor is the end_function at the end
      successors.push_back(--instructions.end());
    }
    else
      successors.push_back(next);
  }

  void compute_incoming_edges();

  // insertion that preserves targets
  void insert_swap(targett target, instructiont &instruction)
  {
    targett next=target;
    next++;
    instructions.insert(next, instructiont())->swap(*target);
    target->swap(instruction);
  }
  
  targett insert(targett target)
  {
    return instructions.insert(target, instructiont());
  }
  
  inline void destructive_append(goto_program_templatet<T> &p)
  {
    instructions.splice(instructions.end(),
                        p.instructions);
  }

  targett add_instruction()
  {
    instructions.push_back(instructiont());
    return --instructions.end();
  }

  targett add_instruction(goto_program_instruction_typet type)
  {
    instructions.push_back(instructiont(type));
    return --instructions.end();
  }

  // these assume that the targets are computed and numbered
  std::ostream& output(
    const class namespacet &ns,
    const irep_idt &identifier,
    std::ostream &out) const;
  
  std::ostream& output_instruction(
    const class namespacet &ns,
    const irep_idt &identifier,
    std::ostream &out,
    typename instructionst::const_iterator it) const;

  virtual std::ostream& output_other_instruction(
    const class namespacet &ns,
    const irep_idt &identifier,
    std::ostream &out,
    const instructiont &instruction) const=0;

  // keep a list of the targets
  typedef typename std::map<const_targett, unsigned> target_numberst;
  target_numberst target_numbers;
  
  // compute the list
  void compute_targets();

  // number them
  void number_targets();
  
  // compute ordering
  void compute_ordering()
  {
    unsigned o=0;
    for(typename instructionst::iterator it=instructions.begin();
        it!=instructions.end();
        it++)
      it->ordering=o++;
  }
  
  void update()
  {
    compute_targets();
    number_targets();
    compute_ordering();
  }
  
  // empty program?
  bool empty() const
  {
    return instructions.empty();
  }

  // constructor/destructor
  goto_program_templatet()
  {
  }

  virtual ~goto_program_templatet()
  {
  }
   
  void swap(goto_program_templatet<T> &program)
  {
    program.instructions.swap(instructions);
    program.target_numbers.swap(target_numbers);
  }
  
  void clear()
  {
    instructions.clear();
    target_numbers.clear();
  }
  
  void copy_from(const goto_program_templatet<T> &src);
  
  bool has_assertion() const;
}; 

#include <langapi/language_util.h>
#include <iomanip>

template <class T>
std::ostream& goto_program_templatet<T>::output(
  const namespacet &ns,
  const irep_idt &identifier,
  std::ostream& out) const
{
  // output program

  for(typename instructionst::const_iterator
      it=instructions.begin();
      it!=instructions.end();
      it++)
    output_instruction(ns, identifier, out, it);

  return out;  
}

template <class T>
std::ostream& goto_program_templatet<T>::output_instruction(
  const class namespacet &ns,
  const irep_idt &identifier,
  std::ostream& out,
  typename instructionst::const_iterator it) const
{
  typename target_numberst::const_iterator t_it=target_numbers.find(it);

  if(!it->location.is_nil())
    out << "        // " << it->location.as_string() << "\n";
  else
    out << "        // no location\n";

  if(!it->local_variables.empty())
  {
    out << "        // Variables:";
    for(local_variablest::const_iterator
        l_it=it->local_variables.begin();
        l_it!=it->local_variables.end();
        l_it++)
      out << " " << *l_it;
    
    out << std::endl;
  }

  if(!it->labels.empty())
  {
    out << "        // Labels:";
    for(typename instructiont::labelst::const_iterator
        l_it=it->labels.begin();
        l_it!=it->labels.end();
        l_it++)
    {
      out << " " << *l_it;
    }
    
    out << std::endl;
  }

  if(t_it==target_numbers.end())
    out << "        ";
  else
    out << std::setw(6) << t_it->second << ": ";

  switch(it->type)
  {
  case GOTO:
    if(!it->guard.is_true())
    {
      out << "IF "
          << from_expr(ns, identifier, it->guard)
          << " THEN ";
    }

    out << "GOTO ";
    
    for(typename instructiont::targetst::const_iterator
        gt_it=it->targets.begin();
        gt_it!=it->targets.end();
        gt_it++)
    {
      if(gt_it!=it->targets.begin()) out << ", ";
    
      typename target_numberst::const_iterator t_it2=
        target_numbers.find(*gt_it);

      if(t_it2==target_numbers.end())
        out << "unknown";
      else
        out << t_it2->second;
    }
      
    out << std::endl;
    break;
    
  case RETURN:
    output_other_instruction(ns, identifier, out, *it);
    out << std::endl;
    break;
    
  case ASSUME:
  case ASSERT:
    if(it->is_assume())
      out << "ASSUME ";
    else
      out << "ASSERT ";
      
    {
      out << from_expr(ns, identifier, it->guard);
    
      const irep_idt &comment=it->location.get("comment");
      if(comment!="") out << " // " << comment;
    }
      
    out << std::endl;
    break;
    
  case SKIP:
    out << "SKIP" << std::endl;
    break;
    
  case END_FUNCTION:
    out << "END_FUNCTION" << std::endl;
    break;
    
  case LOCATION:
    out << "LOCATION" << std::endl;
    break;
    
  case SYNC:
    out << "SYNC " << it->event << std::endl;
    break;
    
  case ATOMIC_BEGIN:
    out << "ATOMIC_BEGIN" << std::endl;
    break;
    
  case ATOMIC_END:
    out << "ATOMIC_END" << std::endl;
    break;
    
  case OTHER:
    output_other_instruction(ns, identifier, out, *it);
    out << std::endl;
    break;
    
  case START_THREAD:
    out << "START THREAD ";

    if(it->targets.size()==1)
    {
      typename target_numberst::const_iterator t_it2=
        target_numbers.find(it->targets.front());

      if(t_it2==target_numbers.end())
        out << "unknown";
      else
        out << t_it2->second;
    }
    
    out << std::endl;
    break;
    
  case END_THREAD:
    out << "END THREAD" << std::endl;
    break;
    
  default:
    throw "unknown statement";
  }

  return out;  
}

template <class T>
void goto_program_templatet<T>::compute_targets()
{
  target_numbers.clear();
 
  // get the targets
  for(typename instructionst::const_iterator
      i_it=instructions.begin();
      i_it!=instructions.end();
      i_it++)
    if(i_it->is_goto() || i_it->is_start_thread())
      for(typename instructiont::targetst::const_iterator
          t_it=i_it->targets.begin();
          t_it!=i_it->targets.end();
          t_it++)
        target_numbers.insert(
          std::pair<targett, unsigned>
          (*t_it, 0));
}

// number them
template <class T>
void goto_program_templatet<T>::number_targets()
{
  // number the targets
  unsigned cnt=0;

  for(typename instructionst::const_iterator
      it=instructions.begin();
      it!=instructions.end();
      it++)
  {
    typename target_numberst::iterator t_it=
      target_numbers.find(it);

    if(t_it!=target_numbers.end())
      t_it->second=++cnt;
  }
}

template <class T>
void goto_program_templatet<T>::copy_from(const goto_program_templatet<T> &src)
{
  // Definitions for mapping between the two programs
  typedef std::map<const_targett, targett> targets_mappingt;
  targets_mappingt targets_mapping;

  clear();

  // Loop over program - 1st time collects targets and copy

  for(typename instructionst::const_iterator it=src.instructions.begin();
      it!=src.instructions.end();
      it++)
  {
    targett new_instruction=add_instruction();
    targets_mapping[it]=new_instruction;
    *new_instruction=*it;
  }

  // Loop over program - 2nd time updates goto targets
  
  for(typename instructionst::iterator it=instructions.begin();
      it!=instructions.end();
      it++)
  {
    if(it->is_goto() || it->is_start_thread())
    {
      for(typename instructiont::targetst::iterator
          t_it=it->targets.begin();
          t_it!=it->targets.end();
          t_it++)
      {
        typename targets_mappingt::iterator
          m_target_it=targets_mapping.find(*t_it);

        if(m_target_it==targets_mapping.end())
          throw "copy_from: goto target not found";

        *t_it=m_target_it->second;
      }
    }
  }

  compute_targets();
  number_targets();
}

// number them
template <class T>
bool goto_program_templatet<T>::has_assertion() const
{
  for(typename instructionst::const_iterator
      it=instructions.begin();
      it!=instructions.end();
      it++)
    if(it->is_assert() && !it->guard.is_true())
      return true;

  return false;
}

template <class T>
void goto_program_templatet<T>::compute_incoming_edges()
{
  for(typename instructionst::iterator
      it=instructions.begin();
      it!=instructions.end();
      it++)
  {
    targetst successors;
  
    get_successors(it, successors);
    
    for(typename targetst::const_iterator
        s_it=successors.begin();
        s_it!=successors.end();
        s_it++)
    {
      targett t=*s_it;

      if(t!=instructions.end())
        t->incoming_edges.insert(it);
    }
  }
}

template <class T>
bool operator<(const typename goto_program_templatet<T>::const_targett i1,
               const typename goto_program_templatet<T>::const_targett i2);

#endif
