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

Module: Symbolic Execution of ANSI-C

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

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

#include <sstream>
#include <fstream>

#include <i2string.h>
#include <location.h>
#include <time_stopping.h>
#include <error_handler.h>

#include <propsolve/satcheck.h>

#include <langapi/mode.h>
#include <langapi/languages.h>
#include <langapi/language_util.h>

#include <goto-symex/goto_trace.h>
#include <goto-symex/build_goto_trace.h>
#include <goto-symex/slice.h>
#include <goto-symex/slice_by_trace.h>
#include <goto-symex/xml_goto_trace.h>

#ifdef HAVE_BV_REFINEMENT
#include <bv_refinement/bv_refinement.h>
#endif

#include "bmc.h"
#include "bv_cbmc.h"
#include "counterexample_beautification_greedy.h"
#include "counterexample_beautification_pbs.h"
#include "document_subgoals.h"

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

Function: bmc_baset::do_unwind_module

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::do_unwind_module(
  decision_proceduret &decision_procedure)
{
}

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

Function: bmc_baset::do_cbmc

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::do_cbmc(prop_convt &solver)
{
  solver.set_message_handler(message_handler);

  status("Passing to decision procedure");

  equation.convert(solver);

  forall_expr_list(it, bmc_constraints)
    solver.set_to_true(*it);
}

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

Function: bmc_baset::error_trace

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::error_trace(const prop_convt &prop_conv)
{
  status("Building error trace");

  goto_tracet goto_trace;
  build_goto_trace(equation, prop_conv, goto_trace);
  
  switch(ui)
  {
  case ui_message_handlert::PLAIN:
    std::cout << std::endl << "Counterexample:" << std::endl;
    show_goto_trace(std::cout, symex.ns, goto_trace);
    break;
  
  case ui_message_handlert::OLD_GUI:
    show_goto_trace_gui(std::cout, symex.ns, goto_trace);
    break;
  
  case ui_message_handlert::XML_UI:
    {
      xmlt xml;
      convert(symex.ns, goto_trace, xml);
      std::cout << xml << std::endl;
    }
    break;
  
  default:
    assert(false);
  }
}

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

Function: bmc_baset::decide

  Inputs:

 Outputs:

 Purpose:

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

bool bmc_baset::decide()
{
  bool result=true;

  if(options.get_bool_option("beautify-pbs") ||
     options.get_bool_option("beautify-greedy"))
  {
    // we need it again later for beautification
    cnf_clause_list_assignmentt cnf;

    bv_cbmct bv_cbmc(cnf);
    bv_cbmc.set_message_handler(message_handler);
    bv_cbmc.set_verbosity(get_verbosity());

    do_unwind_module(bv_cbmc);
    do_cbmc(bv_cbmc);
    
    bv_cbmc.post_process();

    satcheckt satcheck;    
    satcheck.set_message_handler(message_handler);
    satcheck.set_verbosity(get_verbosity());
    cnf.copy_to(satcheck);
    
    status("Running decision procedure");

    // stop the time
    fine_timet sat_start=current_time();
    propt::resultt prop_result=satcheck.prop_solve();
    fine_timet sat_stop=current_time();

    switch(prop_result)
    {
    case propt::P_UNSATISFIABLE:
      result=false;

      report_success();
      break;

    case propt::P_SATISFIABLE:
      cnf.copy_assignment_from(satcheck);
    
      if(options.get_bool_option("beautify-pbs") ||
         options.get_bool_option("beautify-greedy"))
        counterexample_beautification(cnf, bv_cbmc);
        
      error_trace(bv_cbmc);

      report_failure();
      break;

    default:
      error("SAT solver failed");
    }

    // output SAT runtime

    {
      std::ostringstream str;

      str << "Runtime SAT: ";
      output_time(sat_stop-sat_start, str);
      str << "s";
      print(9, str.str());
    }
  }
  else
  {
    satcheckt satcheck;
    satcheck.set_message_handler(message_handler);
    satcheck.set_verbosity(get_verbosity());
  
    bv_cbmct bv_cbmc(satcheck);
    
    return run_decision_procedure(bv_cbmc);
  }

  return result;
}

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

Function: bmc_baset::run_decision_procedure

  Inputs:

 Outputs:

 Purpose:

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

bool bmc_baset::run_decision_procedure(prop_convt &prop_conv)
{
  status("Passing problem to "+prop_conv.decision_procedure_text());

  prop_conv.set_message_handler(message_handler);
  prop_conv.set_verbosity(get_verbosity());

  do_unwind_module(prop_conv);
  do_cbmc(prop_conv);

  status("Running "+prop_conv.decision_procedure_text());

  // stop the time
  fine_timet sat_start=current_time();
  bool result=true;
  decision_proceduret::resultt dec_result=prop_conv.dec_solve();
  fine_timet sat_stop=current_time();

  switch(dec_result)
  {
  case decision_proceduret::D_UNSATISFIABLE:
    result=false;
    report_success();
    break;

  case decision_proceduret::D_SATISFIABLE:
    error_trace(prop_conv);
    report_failure();
    break;

  default:
    error("decision procedure failed");
  }

  // output runtime

  {
    std::ostringstream str;

    str << "Runtime decision procedure: ";
    output_time(sat_stop-sat_start, str);
    str << "s";
    print(9, str.str());
  }

  return result;
}

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

Function: bmc_baset::report_success

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::report_success()
{
  status("VERIFICATION SUCCESSFUL");

  switch(ui)
  {
  case ui_message_handlert::OLD_GUI:
    std::cout << "SUCCESS" << std::endl
              << "Verification successful" << std::endl
              << ""     << std::endl
              << ""     << std::endl
              << ""     << std::endl
              << ""     << std::endl;
    break;
    
  case ui_message_handlert::PLAIN:
    break;
    
  case ui_message_handlert::XML_UI:
    {
      xmlt xml("cprover-status");
      xml.data="SUCCESS";
      std::cout << xml;
      std::cout << std::endl;
    }
    break;
    
  default:
    assert(false);
  }
}

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

Function: bmc_baset::report_failure

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::report_failure()
{
  status("VERIFICATION FAILED");

  switch(ui)
  {
  case ui_message_handlert::OLD_GUI:
    break;
    
  case ui_message_handlert::PLAIN:
    break;
    
  case ui_message_handlert::XML_UI:
    {
      xmlt xml("cprover-status");
      xml.data="FAILURE";
      std::cout << xml;
      std::cout << std::endl;
    }
    break;
    
  default:
    assert(false);
  }
}

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

Function: bmc_baset::counterexample_beautification

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::counterexample_beautification(
  cnf_clause_listt &cnf,
  bv_cbmct &bv_cbmc)
{
  if(options.get_bool_option("beautify-greedy"))
  {
    #ifdef SATCHECK_ZCHAFF
    counterexample_beautification_greedyt counterexample_beautification_greedy;
    counterexample_beautification_greedy.counterexample_beautification(
      cnf, bv_cbmc, equation, symex.ns);
    #else
    error("Greedy beautification requires ZChaff");
    #endif
  }
  else
  {
    counterexample_beautification_pbst counterexample_beautification_pbs;
    counterexample_beautification_pbs.counterexample_beautification(
      cnf, bv_cbmc, equation, symex.ns);
  }
}

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

Function: bmc_baset::show_vcc

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::show_vcc(std::ostream &out)
{
  switch(ui)
  {
  case ui_message_handlert::OLD_GUI:
  case ui_message_handlert::XML_UI:
    error("not supported");
    return;
    
  case ui_message_handlert::PLAIN:
    break;
    
  default:
    assert(false);
  }   
    
  out << std::endl << "VERIFICATION CONDITIONS:" << std::endl << std::endl;

  languagest languages(symex.ns, MODE_C);

  for(symex_target_equationt::statest::iterator
      it=equation.states.begin();
      it!=equation.states.end(); it++)
  {
    if(!it->is_assert()) continue;

    if(it->pc->location.is_not_nil())
      out << it->pc->location << std::endl;
    
    if(it->comment!="")
      out << it->comment << std::endl;
      
    symex_target_equationt::statest::const_iterator
      p_it=equation.states.begin();
      
    for(unsigned count=1; p_it!=it; p_it++)
      if(p_it->is_assume() || p_it->is_assignment())
        if(!p_it->ignore)
        {
          std::string string_value;
          languages.from_expr(p_it->cond, string_value);
          out << "{-" << count << "} " << string_value << std::endl;
          count++;
        }

    out << "|--------------------------" << std::endl;

    std::string string_value;
    languages.from_expr(it->cond, string_value);
    out << "{" << 1 << "} " << string_value << std::endl;
    
    out << std::endl;
  }
}

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

Function: bmc_baset::show_vcc

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::show_vcc()
{
  const std::string &filename=options.get_option("outfile");
  
  if(filename.empty() || filename=="-")
    show_vcc(std::cout);
  else
  {
    std::ofstream out(filename.c_str());
    if(!out)
      std::cerr << "failed to open " << filename << std::endl;
    else
      show_vcc(out);
  }
}

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

Function: bmc_baset::show_program

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::show_program()
{
  unsigned count=1;

  languagest languages(symex.ns, MODE_C);
  
  std::cout << std::endl << "Program constraints:" << std::endl;

  for(symex_target_equationt::statest::const_iterator
      it=equation.states.begin();
      it!=equation.states.end(); it++)
  {
    if(it->is_assignment())
    {
      std::string string_value;
      languages.from_expr(it->cond, string_value);
      std::cout << "(" << count << ") " << string_value << std::endl;
      count++;
    }
  }
}

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

Function: bmc_baset::run

  Inputs:

 Outputs:

 Purpose:

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

bool bmc_baset::run(const goto_functionst &goto_functions)
{
  //symex.total_claims=0;
  symex.set_message_handler(message_handler);
  symex.set_verbosity(get_verbosity());
  symex.options=options;

  status("Starting Bounded Model Checking");

  symex.last_location.make_nil();

  try
  {
    // get unwinding info
    setup_unwind();

    symex.symex_start(goto_functions);
  }

  catch(std::string &error_str)
  {
    error_handlert error_handler(*get_message_handler());
    error_handler.err_location(symex.last_location);
    error_handler.error(error_str);
    return true;
  }

  catch(const char *error_str)
  {
    error_handlert error_handler(*get_message_handler());
    error_handler.err_location(symex.last_location);
    error_handler.error(error_str);
    return true;
  }

  print(8, "size of program expression: "+
           i2string(equation.states.size())+
           " assignments");

  try
  {
    if(options.get_option("slice-by-trace")!="")
    {
      symex_slice_by_tracet symex_slice_by_trace;

      symex_slice_by_trace.slice_by_trace
	(options.get_option("slice-by-trace"), equation, symex.ns);
    }
  }

  catch(std::string &error_str)
  {
    error(error_str);
    return true;
  }
  catch(const char *error_str)
  {
    error(error_str);
    return true;
  }

  try
  {
    if(options.get_bool_option("slice"))
    {
      slice(equation);
      print(8, "removed "+
        i2string(equation.count_ignored_states())+" assignments");
    }
  }

  catch(std::string &error_str)
  {
    error(error_str);
    return true;
  }
  catch(const char *error_str)
  {
    error(error_str);
    return true;
  }

  if(options.get_bool_option("program-only"))
  {
    show_program();
    return false;
  }

  {
    char msg[100];
    snprintf(msg, 100, "Generated %u claims, %u remaining",
             symex.total_claims, symex.remaining_claims);
    print(8, msg);
  }

  try
  {
    if(options.get_bool_option("document-subgoals"))
    {
      document_subgoals(equation, std::cout);
      return false;
    }

    if(options.get_bool_option("show-vcc"))
    {
      show_vcc();
      return false;
    }

    if(symex.remaining_claims==0)
    {
      report_success();
      return false;
    }

    if(options.get_bool_option("dimacs"))
      return write_dimacs();
    else if(options.get_bool_option("cvc"))
      return cvc();
    else if(options.get_bool_option("smt"))
      return smt();
    else if(options.get_bool_option("bv-refine"))
    {
      #ifdef HAVE_BV_REFINEMENT
      bv_refinementt bv_refinement;
      return run_decision_procedure(bv_refinement);
      #else
      throw "bv refinement not linked in";
      #endif
    }
    else
      return decide();
  }

  catch(std::string &error_str)
  {
    error(error_str);
    return true;
  }
  catch(const char *error_str)
  {
    error(error_str);
    return true;
  }
}

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

Function: bmc_baset::setup_unwind

  Inputs:

 Outputs:

 Purpose:

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

void bmc_baset::setup_unwind()
{
  const std::string &set = options.get_option("unwindset");
  unsigned int length = set.length();

  for(unsigned int idx = 0; idx < length; idx++)
  {
    std::string::size_type next = set.find(",", idx);
    std::string val = set.substr(idx, next - idx);
    std::string id = val.substr(0, val.find(":", 0));
    unsigned long uw = atol(val.substr(val.find(":", 0) + 1).c_str());
    symex.unwind_set[id] = uw;
    if(next == std::string::npos) break;
    idx = next;
  }

  symex.max_unwind=atol(options.get_option("unwind").c_str());
}
