//**************************************************************************************************
//                                           PrcBase.cpp                                           *
//                                          -------------                                          *
// Started     : 2004-01-29                                                                        *
// Last Update : 2018-10-25                                                                        *
// Copyright   : (C) 2004-2017 MSWaters                                                            *
//**************************************************************************************************

//**************************************************************************************************
//                                                                                                 *
//      This program is free software; you can redistribute it and/or modify it under the          *
//      terms of the GNU General Public License as published by the Free Software Foundation;      *
//      either version 3 of the License, or (at your option) any later version.                    *
//                                                                                                 *
//**************************************************************************************************

#include "PrcBase.hpp"

//**************************************************************************************************
// Constructor.

PrcBase::PrcBase( int iFlags ) : wxProcess( iFlags )
{
  m_oNameLog  = DEF_LOG_FILE;  // Set the default log file path and name
  m_osErrMsg  = wxT("");       // Clear the error message
  m_iFlags    = 0;             // Clear the execute flags
#if wxCHECK_VERSION( 3,0,0 )
  m_oEnv.cwd = wxGetCwd( );    // Initialize the process working directory
  wxGetEnvMap( &m_oEnv.env );  // Initialize the process environment variables
#endif
  m_iPid      = -1;            // Clear the process ID number
  m_iExitCode = 0;             // Clear the process exit code
}

//**************************************************************************************************
// Destructor.

PrcBase::~PrcBase( )
{
  // Delete the process log file
  bDelLogFile( );
}

//**************************************************************************************************
// Find a binary using the user's PATH environment variable given the binary name.
//
// Note : This function will fail for (bash) shell builtin commands like "cd" since these commands
//        do not have a associated binary file. Builtin commands can be listed by entering "help" or
//        for a simple list of command names "compgen -b".
//
// Argument List :
//   ofnBin - The binary name without the path
//
// Return Values :
//   true  - Success (path prepended to oFnBinary)
//   false - Failure

bool  PrcBase::bFindBinFile( wxFileName & roFileBin )
{
  wxPathList  opl1;
  wxFileName  ofn1;
  wxString    os1, os2, os3;

  m_osErrMsg = wxT("");

  // Initial checks
  if( ! roFileBin.IsOk( ) ) return( false );

  // Search environment variable PATH for the first occurrence of the binary
  opl1.AddEnvList( wxT("PATH") );
  ofn1 = opl1.FindAbsoluteValidPath( roFileBin.GetFullName( ) );

  // Check whether the binary was successfully found
  if( !ofn1.IsOk( ) || !ofn1.FileExists( ) )
  {
    os1 << wxT("Can't find the binary : ") << m_oNameBin.GetFullName( );
    wxGetEnv( wxT("PATH"), &os2 );
    os2.Prepend( wxT("$PATH = \n") );

    // Set the object error message
    os3 = os1 + wxT("\n\n") + os2;
    SetErrMsg( os3 );

    // If debug mode is enabled send the error message to the console
    if( g_bDebug )
    {
      os3 = os1 + wxT(" (") + os2 + wxT(").");
      std::cerr << "DEBUG : " << rosStrToLine( os3 ).mb_str( ) << "\n\n";
    }

    return( false );
  }

  m_oNameBin = ofn1;

  return( true );
}

//**************************************************************************************************
// Get the console output after a process has been initiated and record it in the log file.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bLogOutput( void )
{
  wxOutputStream * poStdOut;
  wxInputStream  * poStdIn;
  wxInputStream  * poStdErr;
  wxString         os1, os2;
  wxChar           oc1;

  // Check that the log file name is valid
  if( ! m_oNameLog.IsOk( ) )        return( false );

  // Open the file
  wxTextFile  oFileLog( m_oNameLog.GetFullPath( ) );
  if( oFileLog.Exists( ) )
       { if( ! oFileLog.Open( )   ) return( false ); }
  else { if( ! oFileLog.Create( ) ) return( false ); }

  // Clear the file if it contains lines
  oFileLog.Clear( );

  // Get pointers to the input streams
  poStdOut = GetOutputStream( );
  poStdIn  = GetInputStream( );
  poStdErr = GetErrorStream( );

  // Read the console input
  while( bIsExec( ) || poStdIn->IsOk( ) || poStdErr->IsOk( ) )
  {
    // Get a line of data from stdout
    while( poStdIn->CanRead( ) )
    {
      oc1 = poStdIn->GetC( );
      if( oc1==wxT('\t') || (oc1>31 && oc1!=127) ) os1 << oc1;
      if( oc1 == wxT('\n') ) { oFileLog.AddLine( os1 ); os1 = wxT(""); }
    }

    // Get a line of data from stderr
    while( poStdErr->CanRead( ) )
    {
      oc1 = poStdErr->GetC( );
      if( oc1==wxT('\t') || (oc1>31 && oc1!=127) ) os2 << oc1;
      if( oc1 == wxT('\n') ) { oFileLog.AddLine( os2 ); os2 = wxT(""); }
    }

    wxTheApp->Yield( true );  // Yield CPU to other processes
  }

  // Delete the stream objects
  delete poStdOut;
  delete poStdIn;
  delete poStdErr;
  SetPipeStreams( NULL, NULL, NULL );

  if( ! os1.IsEmpty( ) ) oFileLog.AddLine( os1 );
  if( ! os2.IsEmpty( ) ) oFileLog.AddLine( os2 );

  oFileLog.Write( );  // Save the changes to disk
  oFileLog.Close( );  // Close the log file

  return( true );
}

//**************************************************************************************************
// Get the console output after a process has been initiated and dump it to stdout and stderr.

void  PrcBase::PrintOutput( void )
{
  wxOutputStream * poStdOut;
  wxInputStream  * poStdIn;
  wxInputStream  * poStdErr;
  wxChar           oc1;

  // Get pointers to the input streams
  poStdOut = GetOutputStream( );
  poStdIn  = GetInputStream( );
  poStdErr = GetErrorStream( );

  // Read the console input
  while( bIsExec( ) || poStdIn->IsOk( ) || poStdErr->IsOk( ) )
  {
    // Get data from stdout
    while( poStdIn->CanRead( ) )
    {
      oc1 = poStdIn->GetC( );
      if( oc1==wxT('\n') || oc1==wxT('\r') || oc1==wxT('\t') || (oc1>31 && oc1!=127) )
        std::cout << char( oc1 );
    }

    // Get data from stderr
    while( poStdErr->CanRead( ) )
    {
      oc1 = poStdErr->GetC( );
      if( oc1==wxT('\n') || oc1==wxT('\r') || oc1==wxT('\t') || (oc1>31 && oc1!=127) )
        std::cerr << char( oc1 );
    }

    wxTheApp->Yield( true );  // Yield CPU to other processes
  }

  // Delete the stream objects
  delete poStdOut;
  delete poStdIn;
  delete poStdErr;
  SetPipeStreams( NULL, NULL, NULL );
}

//**************************************************************************************************
// Does the binary exist?
//
// Return Values :
//   true  - The binary does    exist
//   false - The binary doesn't exist

bool  PrcBase::bBinExists( void )
{
  if( !m_oNameBin.IsOk( ) || !m_oNameBin.FileExists( ) )
    return( false );

  return( true );
}

//**************************************************************************************************
// Set the file name of the binary to be executed.
//
// Argument List :
//   rosFileName - A string containing the binary file name
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bSetBinFile( const wxString & rosFileName )
{
  // The binary file name shouldn't contain space characters or be empty
  if( rosFileName.IsEmpty( ) )                      return( false );
  if( rosFileName.Find( wxT(' ') ) != wxNOT_FOUND ) return( false );

  m_oNameBin = rosFileName;

  if( ! bFindBinFile( m_oNameBin ) ) return( false );

  return( true );
}

//**************************************************************************************************
// Set the argument list to be appended to the binary name.
//
// Argument List :
//   rosArgLst - A string containing the argument list
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bSetArgLst( const wxString & rosArgLst )
{
  if( rosArgLst.IsEmpty( ) ) return( false );

  m_osArgLst = rosArgLst;

  return( true );
}

//**************************************************************************************************
// Set the Current Working Directory (CWD).
//
// Argument List :
//   rosCwd - The proposed new CWD
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bSetCwd( const wxString & rosCwd )
{
  wxFileName  ofn1;
  wxString    os1;
  bool        bRtn=false;

  if( rosCwd.IsEmpty( ) ) return( false );

  os1 = rosCwd;
#ifndef __WXMSW__
  if( os1.GetChar( os1.Len()-1 ) != wxT('/') ) os1 += wxT('/');
#else
  if( os1.GetChar( os1.Len()-1 ) != wxT('\\') ) os1 += wxT('\\');
#endif // __WXMSW__
  ofn1 = os1;
  if( ofn1.IsDir( ) )
  {
    if( ofn1.DirExists( ) )
    {
      ofn1.MakeAbsolute( );
      bRtn = wxSetWorkingDirectory( ofn1.GetPath( ) );
#if wxCHECK_VERSION( 3,0,0 )
      m_oEnv.cwd = wxGetCwd( );
#endif
    }
  }

  return( bRtn );
}

//**************************************************************************************************
// Set the process log file name.
// (If set all process output can be captured to this file.)
//
// Argument List :
//   rosFileName - A string containing the full path and file name
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bSetLogFile( const wxString & rosFileName )
{
  wxFileName  ofn1;

  ofn1 = rosFileName;

  if( ! ofn1.IsOk( ) ) return( false );

  if( ofn1.GetPath( ).IsEmpty( ) ) ofn1.SetPath( wxT(".") );

  m_oNameLog = ofn1;

  return( true );
}

//**************************************************************************************************
// Escape space characters.
//
// In UNIX like operating systems if path names contain space characters they must each be preceeded
// by a backslash character to be properly processed by the command line interpreter.
//
// Note : This function is required to overcome a short coming in wxWidgets. The class wxFileName
//        probably should be able to do this automatically.
//
// Argument List :
//   rosPath - The path string to be check and if necessary modified
//
// Return Values :
//   The path with all space characters escaped

const wxString & PrcBase::rosEscSpaceChrs( const wxString & rosPath )
{
  static  wxString  osPath;
#ifndef __WXMSW__
          size_t    sz1;
#endif // __WXMSW__

  osPath = rosPath;

#ifndef __WXMSW__
  if( osPath.Find( ' ' ) != wxNOT_FOUND )
  {
    osPath.Trim( FALSE );                 // Trim any space chars from the beginning of the string
    sz1 = osPath.find( ' ' );             // Search for the first space character
    while( sz1 != (size_t) wxNOT_FOUND )
    {
      if( osPath[ sz1-1 ] != '\\' )       // Has the space character been escaped?
      {
        osPath.insert( sz1, wxT("\\") );  // Escape the space character
        sz1 += 2;
      }
      sz1 = osPath.find( ' ', sz1 );      // Find the next space character
    }
  }
#endif // __WXMSW__

  return( osPath );
}

//**************************************************************************************************
// Execute a process asynchronously ie. return as soon as the process has been launched.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bExecAsync( void )
{
  wxString  os1, os2;

  // Only execute simulation if it isn't already running
  if( bIsExec( ) ) return( false );

  // Clear error attributes
  m_osErrMsg = wxT("");

  // Construct the command line to be executed
  os1 = roGetBinFile( ).GetFullPath( );
  if( ! rosGetArgLst( ).IsEmpty( ) )
    os1 << wxT(' ') << rosGetArgLst( );

  // Attempt to execute the simulation
#if wxCHECK_VERSION( 3,0,0 )
  m_iPid = (int) wxExecute( os1, wxEXEC_ASYNC | m_iFlags, this, &m_oEnv );
#else
  m_iPid = (int) wxExecute( os1, wxEXEC_ASYNC | m_iFlags, this );
#endif
  if( m_iPid <= 0 )
  {
    os2.Empty( );
    os2 << wxT("Couldn't start process : ") << os1;
    SetErrMsg( os2 );
    return( false );
  }

  return( true );
}

//**************************************************************************************************
// Execute a process synchronously ie. wait for the process to terminate before returning.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bExecSync( void )
{
  // Not yet implemented ??? 12/03/2010

  return( false );

//  long  liRtn;

//  wxEnableTopLevelWindows( false );
//  liRtn = wxExecute( rosCmd, wxEXEC_SYNC );
//  wxEnableTopLevelWindows( true );
//  if( liRtn != 0 ) return( false );

//  return( true );

// This is how I'd like to do it but it doesn't work 20/11/2003 ???
//  wxEnableTopLevelWindows( false );
//  int iRtn = (int) wxExecute( osCmd, wxEXEC_SYNC );
//  wxEnableTopLevelWindows( true );

//  wxProcess  oProcess( wxPROCESS_DEFAULT );
//  iPid = (int) wxExecute( osCmd, wxEXEC_SYNC );

//  if( iPid == 0 )
//  { // Error gnetlist could not be executed
//    cerr << "The command:\n   " << osCmd << "\ncould not be executed.";
//    return( false );
//  }

//  for( ui1=0; ui1<300; ui1++ )
//  { // Wait up to 30 seconds for the process to complete
//cerr << iPid << '\n';
//    wxUsleep( 100 ); // Sleep for 100msec
//    if( ! oProcess.Exists( iPid ) ) break;
//  }
//  if( oProcess.Exists( iPid ) )
//  { // Error gnetlist had to be terminated prematurely
//    oProcess.Kill( iPid );
//    cerr << "The command:\n   " << osCmd << "\ntook more than 30 sec. to "
//         << "execute and so was terminated prematurely.";
//    return( false );
//  }
}

//**************************************************************************************************
// Stop the simulation currently in progress.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcBase::bKill( void )
{
  if( ! bIsExec( ) )                           return( true );
  if( ! Exists( m_iPid ) )                     return( true );

  if( Kill( m_iPid, wxSIGTERM ) != wxKILL_OK ) return( false );

  m_iPid      = -1;
  m_iExitCode = 0;

  return( true );
}

//**************************************************************************************************
// Remove the log file.
//
// Return Values :
//   Success - true
//   Failure - false

bool  PrcBase::bDelLogFile( void )
{
  if( m_oNameLog.GetFullPath( ).IsEmpty( ) )          return( true );

  if( ! ::wxFileExists( m_oNameLog.GetFullPath( ) ) ) return( true );

  return( ::wxRemoveFile( m_oNameLog.GetFullPath( ) ) );
}

//**************************************************************************************************
// Collect the input / output from the process and print to a text control.
//
// Argument List :
//   roTxtCtl - The text control to contain the simulator output

void  PrcBase::PrintIO( TextCtrl & roTxtCtl )
{
  roTxtCtl.bClear( );    // Clear the text control

  PrintCmd( roTxtCtl );  // Print the process command

  PrintRsp( roTxtCtl );  // Print the process command response
}

//**************************************************************************************************
// Print the command envoking the process to a text control.
//
// Argument List :
//   roTxtCtl - The text control to contain process input

void  PrcBase::PrintCmd( TextCtrl & roTxtCtl )
{
  wxString  os1;

  // Print the process command
  os1.Empty( );
  os1 << wxT("                    *************** PROCESS COMMAND ***************");
  roTxtCtl.bAppendLine( os1 );
  os1.Empty( );
  roTxtCtl.bAppendLine( os1 );
  os1 << roGetBinFile( ).GetFullPath( );
  if( ! rosGetArgLst( ).IsEmpty( ) ) os1 << wxT(' ') << rosGetArgLst( );
  roTxtCtl.bAppendLine( os1 );
  os1.Empty( );
  roTxtCtl.bAppendLine( os1 );
}

//**************************************************************************************************
// Collect the output from the process and print it to a text control.
//
// Argument List :
//   roTxtCtl - The text control to contain the simulator output

void  PrcBase::PrintRsp( TextCtrl & roTxtCtl )
{
  wxString  os1;

  // Print the process command response
  if( ! m_oNameLog.GetFullPath( ).IsEmpty( ) )
  {
    os1.Empty( );
    os1 << wxT("                    *************** PROCESS RESPONSE **************");
    roTxtCtl.bAppendLine( os1 );
    os1.Empty( );
    roTxtCtl.bAppendLine( os1 );
    if( m_oNameLog.IsOk( ) && m_oNameLog.FileExists( ) )
      roTxtCtl.bAppendFile( m_oNameLog.GetFullPath( ) );
    else
    {
      os1 << wxT("Couldn't load the file containing the process ouput : ")
          << m_oNameLog.GetFullPath( );
      roTxtCtl.bAppendLine( os1 );
    }
  }
}

//**************************************************************************************************
// Print the object attributes.
//
// Argument List :
//   rosPrefix - A prefix to every line displayed (usually just spaces)

void  PrcBase::Print( const wxString & rosPrefix )
{
  std::cout << rosPrefix.mb_str() << "m_oNameBin  : " << m_oNameBin.GetFullPath( ).mb_str() << '\n';
  std::cout << rosPrefix.mb_str() << "m_oNameLog  : " << m_oNameLog.GetFullPath( ).mb_str() << '\n';
  std::cout << rosPrefix.mb_str() << "m_osArgLst  : " << m_osArgLst.mb_str( )               << '\n';
  std::cout << rosPrefix.mb_str() << "m_osErrMsg  : " << m_osErrMsg.mb_str( )               << '\n';
  std::cout << rosPrefix.mb_str() << "m_iPid      : " << m_iPid                             << '\n';
  std::cout << rosPrefix.mb_str() << "m_iExitCode : " << m_iExitCode                        << '\n';
}

//**************************************************************************************************
// Call-back function called when the process terminates.
// (WARNING : Be careful of this function definition, if it's incorrect the application can throw a
//            segmentation fault.)
//
// Argument List :
//   iPid    - The PID of the process which just terminated
//   iStatus - The exit code of the process

void  PrcBase::OnTerminate( int iPid, int iStatus )
{
  if( iPid == m_iPid )
  {
    m_iPid      = -1;
    m_iExitCode = iStatus;
  }
}

//**************************************************************************************************
