#include "Session.h"
#include "HostProcess.h"
#include "PeerProcess.h"
#include "Log.h"
#include "ace/OS_NS_strings.h"
#include "ace/SString.h"
#include "ace/OS_NS_sys_stat.h"

long
Session::tao_version_ = 220;

AltAddresses
Session::alt_addrs_;


Session::Session (void)
  : split_details_ (false)
{
  ACE_CString n ("localhost");
  ACE_CString v ("127.0.0.1");
  this->alt_addrs_.bind (n,v);
}

Session::~Session (void)
{
  for (Processes::iterator i = processes_.begin();
       i != processes_.end();
       i++)
    {
      delete i->item();
    }
}

bool
Session::set_tao_version (ACE_TCHAR *str)
{
  if (ACE_OS::strncmp(str, ACE_TEXT("1.5"), 3)== 0)
    tao_version_ = 150;
  else if (ACE_OS::strncmp (str, ACE_TEXT("1.6"), 3) == 0)
    tao_version_ = 160;
  else if (ACE_OS::strncmp (str, ACE_TEXT("1.7"), 3) == 0)
    tao_version_ = 170;
  else if (ACE_OS::strncmp (str, ACE_TEXT("1.8"), 3) == 0)
    tao_version_ = 180;
  else if (ACE_OS::strncmp (str, ACE_TEXT("2.0"), 3) == 0)
    tao_version_ = 200;
  else if (ACE_OS::strncmp (str, ACE_TEXT("2.2"), 3) == 0)
    tao_version_ = 220;
  else
    return false;
  return true;
}

long
Session::tao_version (void)
{
  return tao_version_;
}

void
Session::add_process (HostProcess *proc)
{
  if (processes_.bind(proc->pid(),proc) != 0)
    ACE_ERROR ((LM_ERROR,
                "Session::add_process could not bind pid %d\n",
                proc->pid()));
  if (procs_by_name_.bind(proc->proc_name(), proc) != 0)
    ACE_ERROR ((LM_ERROR,
                "Session::add_process could not bind procname %s\n",
                proc->proc_name().c_str()));
}

void
Session::alternate_address (const char *addrspec)
{
  const char *equal = ACE_OS::strchr(addrspec,'=');
  if (equal == 0)
   return;
  ACE_CString name (addrspec,(equal - addrspec));
  ACE_CString value (equal+1);
  Session::alt_addrs_.bind(name,value);
}

bool
Session::is_equivalent (const ACE_CString &primary,
                        const ACE_CString &alternate)
{
  ACE_CString test(primary);
  ACE_CString alt;
  if (Session::alt_addrs_.find(test,alt) == 0)
    {
      return alt == alternate;
    }
  return false;
}

void
Session::default_service (const char *addrspec)
{
   const char *equal = ACE_OS::strchr(addrspec,'=');
  if (equal == 0)
    return;
  ACE_CString name (addrspec,(equal - addrspec));
  Endpoint ep (equal+1);

  static long next_def_pid = 0;
  --next_def_pid;
  HostProcess *hp = new HostProcess ("defaulted",next_def_pid);
  hp->proc_name(name);
  hp->add_listen_endpoint (ep);
  this->processes_.bind(next_def_pid,hp);
  this->procs_by_name_.bind(name,hp);
}

HostProcess *
Session::find_process (long pid)
{
  Processes::ENTRY *entry = 0;
  if (this->processes_.find(pid,entry) == 0)
    return entry->item();
  else
    return 0;
}

HostProcess *
Session::find_host_i (const Endpoint &endpoint, bool server)
{
  for (Processes::ITERATOR i (this->processes_); !i.done(); i.advance())
    {
      Processes::ENTRY *entry;
      if (i.next(entry) == 0)
        break;
      if (entry->item()->has_endpoint(endpoint, server))
        return entry->item();
    }
  return 0;
}

HostProcess *
Session::find_host (const Endpoint &endpoint, bool server)
{
  HostProcess *result = find_host_i (endpoint, server);
  if (result != 0)
    return result;

  Endpoint test (endpoint);
  ACE_CString alternate;
  if (Session::alt_addrs_.find(endpoint.host_,alternate) == 0)
    {
      test.host_ = alternate;
      return find_host_i (test, server);
    }
  return 0;
}

void
Session::make_dir (const char *dirname, bool split)
{
  this->base_dir_ = dirname;
  this->split_details_ = split;
}

void
Session::outfile (const char *o)
{
  this->outfile_ = o;
}

bool
Session::has_dir (void)
{
  return this->base_dir_.length() > 0;
}

bool
Session::has_outfile (void)
{
  return this->outfile_.length() > 0;
}

ostream *
Session::stream_for ( ostream *oldstream, HostProcess *hp, const char *sub, const char *detail)
{
  if (this->has_dir())
    {
      ACE_CString outname = this->base_dir_;

      if (oldstream == 0 && hp == 0)
        {
          if (ACE_OS::mkdir(outname.c_str()) != 0 && errno != EEXIST)
            ACE_ERROR ((LM_ERROR,
                        "Session::stream_for unable to make dir %C, %p\n",
                        outname.c_str(), "mkdir"));
        }
      delete oldstream;
      outname += ACE_DIRECTORY_SEPARATOR_CHAR;
      if (hp != 0)
        {
          outname += hp->proc_name();
          if (ACE_OS::mkdir(outname.c_str()) != 0 && errno != EEXIST)
            ACE_ERROR ((LM_ERROR,
                        "Session::stream_for unable to make dir %C, %p\n",
                        outname.c_str(), "mkdir"));

          outname += ACE_DIRECTORY_SEPARATOR_CHAR;
        }
      outname += (sub == 0) ? "summary.txt" : sub;
      if (detail != 0)
        {
          if (ACE_OS::mkdir(outname.c_str()) != 0 && errno != EEXIST)
            ACE_ERROR ((LM_ERROR,
                        "Session::stream_for unable to make dir %C, %p\n",
                        outname.c_str(), "mkdir"));

          outname += ACE_DIRECTORY_SEPARATOR_CHAR;
          outname += detail;
        }
      return new ofstream (outname.c_str());
    }

  if (oldstream != 0)
    return oldstream;
  if (this->has_outfile())
    return new ofstream(this->outfile_.c_str());
  else
    return &cout;
}

void
Session::dump ()
{
  bool single = !this->has_dir();
  ostream *strm = this->stream_for(0);
  // report session metrics

  if (single)
    {
      this->split_details_ = false;
      *strm << "Session summary report: "
            << this->processes_.current_size() << " Processes detected." << endl;
    }
  for (Procs_By_Name::ITERATOR i (this->procs_by_name_); !i.done(); i.advance())
    {
      Procs_By_Name::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      entry->item()->dump_summary (*strm);
    }

  if (single)
    *strm << "\n\n\nSession detail threads report: " << endl;
  for (Procs_By_Name::ITERATOR i (this->procs_by_name_); !i.done(); i.advance())
    {
      Procs_By_Name::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      strm = this->split_details_ ?
        stream_for (strm,entry->item(),"threads", "summary.txt"):
        stream_for (strm,entry->item(),"threads.txt");
      entry->item()->dump_thread_summary (*strm);
    }

  if (single)
    *strm << "\n\n\nSession detail peer process report: " << endl;
  for (Procs_By_Name::ITERATOR i (this->procs_by_name_); !i.done(); i.advance())
    {
      Procs_By_Name::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      strm = this->split_details_ ?
        stream_for (strm,entry->item(),"peers", "summary.txt") :
        stream_for (strm,entry->item(),"peer_processes.txt");
      entry->item()->dump_peer_summary (*strm);
    }

  if (single)
    *strm << "\n\n\nSession detail object report: " << endl;
  for (Procs_By_Name::ITERATOR i (this->procs_by_name_); !i.done(); i.advance())
    {
      Procs_By_Name::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      strm = stream_for (strm,entry->item(),"objects.txt");
      entry->item()->dump_object_detail (*strm);
    }

  if (single)
    *strm << "\n\n\nSession detail invocation by peer process report: " << endl;
  for (Procs_By_Name::ITERATOR i (this->procs_by_name_); !i.done(); i.advance())
    {
      Procs_By_Name::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      if (this->split_details_)
        {
          entry->item()->split_peer_invocations (this);
        }
      else
        {
          strm = stream_for (strm,entry->item(),"invocation_by_peer.txt");
          entry->item()->dump_invocation_detail (*strm);
        }
    }

  if (single)
    *strm << "\n\n\nSession detail invocation by thread report: " << endl;

  ACE_Time_Value start = ACE_Time_Value::zero;
  for (Procs_By_Name::ITERATOR i (this->procs_by_name_); !i.done(); i.advance())
    {
      Procs_By_Name::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      if (start == ACE_Time_Value::zero ||
          entry->item()->start_time().msec() < start.msec())
        {
          start = entry->item()->start_time();
        }
    }

  for (Procs_By_Name::ITERATOR i (this->procs_by_name_); !i.done(); i.advance())
    {
      Procs_By_Name::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      if (this->split_details_)
        {
          entry->item()->split_thread_invocations (this, start);
        }
      else
        {
          strm = stream_for (strm,entry->item(),"invocation_by_thread.txt");
          entry->item()->dump_thread_invocations (*strm, start);
        }
    }
  if (this->has_outfile() || this->has_dir())
    delete strm;
}


// iterate over the collection of host processes to associate peer
// processes via endpoints.
void
Session::reconcile (void)
{
  for (Processes::ITERATOR i (this->processes_); !i.done(); i.advance())
    {
      Processes::ENTRY *entry;
      if (i.next(entry) == 0)
        continue;
      entry->item()->reconcile_peers(this);
    }

}
