
// General tests combining features of SystemC 2.1 and 1666


#include <systemc>
using sc_core::sc_interface;
using sc_core::sc_object;
using sc_core::sc_port;
using sc_core::sc_export;
using sc_core::sc_module;
using sc_core::sc_module_name;
using sc_core::sc_in;
using sc_core::sc_in_clk;
using sc_core::sc_signal;
using sc_core::sc_prim_channel;
using sc_core::sc_event;
using sc_core::sc_delta_count;
using sc_core::sc_is_running;
using sc_core::sc_get_top_level_objects;
using sc_core::sc_find_object;
using sc_core::sc_report;
using sc_core::sc_report_handler;
using sc_core::sc_actions;
using sc_core::SC_INFO;
using sc_core::SC_UNSPECIFIED;
using sc_core::SC_THROW;
using sc_core::sc_spawn;
using sc_core::sc_process_handle;
using sc_core::sc_get_current_process_handle;
using sc_core::sc_time;
using sc_core::sc_time_stamp;
using sc_core::SC_ZERO_TIME;
using sc_core::SC_PS;
using sc_core::SC_NS;
using sc_core::SC_US;
using sc_core::sc_start_of_simulation_invoked;
using sc_core::sc_end_of_simulation_invoked;
using sc_core::sc_start;
using sc_core::sc_stop;
using sc_core::sc_copyright;
using sc_core::sc_version;
using sc_core::sc_release;
using sc_dt::sc_abs;
using sc_dt::sc_max;
using sc_dt::sc_min;
using sc_dt::sc_int;
using sc_dt::sc_bigint;
using std::cout;
using std::endl;

#if defined(IEEE_1666_CPLUSPLUS) && IEEE_1666_CPLUSPLUS >= 201103L
// dynamic process macros have been moved to functions on C++11
using sc_core::sc_bind;
using sc_core::sc_ref;
using sc_core::sc_cref;
#endif // IEEE_1666_CPLUSPLUS

struct i_f: virtual sc_interface
{
  virtual void write() = 0;
};

void check_form_of_suffix(std::string s)
{
  std::string charset = "0123456789";
  while (!s.empty())
  {
    sc_assert(s[0] == '_');
  s = s.substr(1);
  sc_assert(!s.empty());
  do
  {
    sc_assert(charset.find(s[0]) < charset.size());
    s = s.substr(1);
  } while (!s.empty() && (s[0] != '_'));
  }
}

struct Chan: i_f, sc_object
{
  virtual void write()
  {
    sc_assert(std::string(name()).substr(0,17) == "top.m_dest.object");
    check_form_of_suffix(std::string(name()).substr(17));
  }
};

struct M_src: sc_core::sc_behavior
{
  sc_port<i_f>* p;

  M_src(sc_module_name _name)
  {
    p = new sc_port<i_f>;

    SC_THREAD(T);
  }
  void T()
  {
    (*p)->write();
  }
};

struct M_dest: sc_core::sc_channel
{
  sc_export<i_f>* xp;

  M_dest(sc_module_name _name)
  {
    xp = new sc_export<i_f>;
    Chan* ch = new Chan;
    xp->bind(*ch);
  }
};

void f()
{
  ::sc_core::wait(33, SC_PS);
}

struct Prim: sc_prim_channel
{
  Prim() : cref_arg(1) {}

  void write() { request_update(); }

  void update()
  {
    sc_spawn(sc_bind(&Prim::proc, this));
  }

  void proc()
  {
    ev.notify(SC_ZERO_TIME);
    wait(ev);
    cout << "Prim::Proc spawned and resumed" << endl;
    SC_FORK
      sc_spawn(sc_bind(&Prim::Th, this, 1)),
      sc_spawn(sc_bind(&Prim::Th, this, 2))
    SC_JOIN
    sc_spawn(sc_bind(&Prim::Th_ref, this, sc_ref(ref_arg)));
    sc_spawn(sc_bind(&Prim::Th_cref, this, sc_cref(cref_arg)));
    wait(1, SC_NS);
    sc_assert(ref_arg == 99);
  }
  sc_event ev;
  void Th(int i)
  {
    cout << "Th::i=" << i << endl;
  }
  void Th_ref(int& arg)
  {
    arg = 99;
    sc_time t(sc_time_stamp());
    sc_process_handle h = sc_spawn(&f);
    wait(h.terminated_event());
    sc_assert(sc_time_stamp() == t + sc_time(33, SC_PS));
  }
  void Th_cref(const int& arg)
  {
  }
  int ref_arg;
  const int cref_arg;
};

SC_MODULE(M)
{
  Prim prim;
  SC_CTOR(M)
  {
    SC_THREAD(T);
  }

  void T()
  {
    wait(10, SC_NS);
    try {
      sc_report_handler::set_actions("msg_type", SC_INFO, SC_THROW);
      SC_REPORT_INFO("msg_type", "msg");
    }
    catch (sc_report& rpt) {
      cout << "Caught rpt " << rpt.what() << endl;
      sc_assert(rpt.get_severity() == SC_INFO);
      sc_assert(std::string(rpt.get_msg_type()) == "msg_type");
      sc_assert(std::string(rpt.get_msg()) == "msg");
      sc_assert(rpt.get_time() == sc_time(10, SC_NS));
      sc_assert(std::string(rpt.get_process_name()) == "top.m.T");

      sc_assert(sc_report_handler::get_count(SC_INFO) == 1);
      sc_report rpt2 = rpt; // Copy constructor

      prim.write();

      sc_spawn(sc_bind(&M::proc, this, &rpt2));
      wait(100, SC_NS);
    }
  }
  void proc(sc_report* rpt)
  {
    sc_assert(rpt->get_severity() == SC_INFO);
    sc_assert(std::string(rpt->get_msg_type()) == "msg_type");
    sc_assert(std::string(rpt->get_msg()) == "msg");
    sc_assert(rpt->get_time() == sc_time(10, SC_NS));
    sc_assert(std::string(rpt->get_process_name()) == "top.m.T");
    rpt->get_file_name();
    rpt->get_line_number();
  }
};

struct CM: sc_module
{
  sc_in_clk clk;
  sc_in<bool> reset;


  CM(sc_module_name _name)
  : first(true)
  {
    SC_CTHREAD(CT, clk.pos());
      reset_signal_is(reset, true);

    sc_assert(sc_start_of_simulation_invoked() == false);
    sc_assert(sc_end_of_simulation_invoked() == false);
    sc_assert(sc_is_running() == false);
  }

  bool first;

  void CT()
  {
    if (first)
      sc_assert(sc_time_stamp() == sc_time(5, SC_US));
    else
      sc_assert(sc_time_stamp() == sc_time(30, SC_US));
    first = false;

    sc_assert(sc_start_of_simulation_invoked() == true);
    sc_assert(sc_end_of_simulation_invoked() == false);
    sc_assert(sc_is_running() == true);

    while(1)
    {
      wait();
      sc_assert(sc_time_stamp() == sc_time(15, SC_US));
      wait();
      sc_assert(sc_time_stamp() == sc_time(25, SC_US));
      wait();
      sc_assert(false);
    }
  }

  void start_of_simulation()
  {
    sc_assert(sc_start_of_simulation_invoked() == false);
    sc_assert(sc_end_of_simulation_invoked() == false);
    sc_assert(sc_is_running() == false);
  }

  void end_of_simulation()
  {
    sc_assert(sc_start_of_simulation_invoked() == true);
    sc_assert(sc_end_of_simulation_invoked() == false);
    sc_assert(sc_is_running() == false);
  }
};

SC_MODULE(Top)
{
  M *m;
  CM *cm;

  M_src m_src;
  M_dest m_dest;

  sc_signal<bool> clk, reset;

  SC_CTOR(Top)
  : m_src("m_src"), m_dest("m_dest")
  {
    m = new M("m");
    cm = new CM("cm");
    cm->clk(clk);
    cm->reset(reset);

    m_src.p->bind( *(m_dest.xp) );  // Port-export binding

    SC_THREAD(T);
    SC_THREAD(T2);
  }
  void T()
  {
    clk = false;
    reset = true;
    wait(5, SC_US);
    clk = true;
    wait(5, SC_US);
    clk = false;
    reset = false;
    wait(5, SC_US);
    clk = true;
    wait(5, SC_US);
    clk = false;
    wait(5, SC_US);
    clk = true;
    wait(5, SC_US);
    clk = false;
    reset = true;
    wait(10, SC_US);
  }
  void T2()
  {
    sc_assert(sc_min(-1,1) == -1);
    sc_assert(sc_max(-1,1) == 1);
    sc_assert(sc_abs(-1) == 1);

    sc_assert(sc_min(sc_int<8>(-1),sc_int<8>(1)) == sc_int<8>(-1));
    sc_assert(sc_max(sc_int<8>(-1),sc_int<8>(1)) == sc_int<8>(1));
    sc_assert(sc_abs(sc_int<8>(-1)) == sc_int<8>(1));

    sc_assert(sc_min(sc_bigint<256>(-1),sc_bigint<256>(1)) == sc_bigint<256>(-1));
    sc_assert(sc_max(sc_bigint<256>(-1),sc_bigint<256>(1)) == sc_bigint<256>(1));
    sc_assert(sc_abs(sc_bigint<256>(-1)) == sc_bigint<256>(1));
  }
};

int sc_main(int argc, char* argv[])
{
  sc_assert(sc_delta_count() == 0);
  sc_assert(sc_is_running() == 0);
  sc_assert(sc_time_stamp() == sc_time(SC_ZERO_TIME));
  sc_assert(sc_report_handler::get_count(SC_INFO) == 0);
  sc_assert(sc_report_handler::get_count("foo") == 0);

  sc_actions act1 = sc_report_handler::get_new_action_id();
  sc_actions act2 = sc_report_handler::get_new_action_id();
  while (act2 != SC_UNSPECIFIED)
  {
    sc_assert(act2 != act1);
    act2 = sc_report_handler::get_new_action_id();
  }
  sc_assert(sc_report_handler::get_log_file_name() == 0);
  sc_assert(sc_get_top_level_objects().size() == 0);
  sc_assert(sc_find_object("foo") == 0);
  sc_assert(!sc_get_current_process_handle().valid());

  sc_copyright();
  sc_version();
  std::string release = sc_release();
  int n = release.find('.');
  std::string major = release.substr(0,n);
  release = release.substr(n+1,release.size()-1);
  n = release.find('.');
  std::string minor = release.substr(0,n);
  release = release.substr(n+1,release.size()-1);
  n = release.find('-');
  std::string patch = release.substr(0,n);
  std::string originator = release.substr(n+1,release.size());

  std::string charset =
  "abcdefghijklmnopqrstuvwxyzABSCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";

  for (unsigned int i = 0; i < major.size(); i++)
    sc_assert(charset.find(major[i]) < charset.size());
  for (unsigned int i = 0; i < minor.size(); i++)
    sc_assert(charset.find(minor[i]) < charset.size());
  for (unsigned int i = 0; i < patch.size(); i++)
    sc_assert(charset.find(patch[i]) < charset.size());
  for (unsigned int i = 0; i < originator.size(); i++)
    sc_assert(charset.find(originator[i]) < charset.size());

  Top top("top");
  sc_start();

  sc_stop();
  sc_assert(sc_end_of_simulation_invoked() == true);

  cout << endl << "Success" << endl;
  return 0;
}
