//**************************************************************************************************
//                                        CmdNgSpicePR.cpp                                         *
//                                       ------------------                                        *
// Started     : 2007-10-16                                                                        *
// Last Update : 2018-09-25                                                                        *
// Copyright   : (C) 2007-2018 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 "CmdNgSpicePR.hpp"

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

CmdNgSpicePR::CmdNgSpicePR( void ) : CmdBase( )
{
  bSetDefaults( );
}

//**************************************************************************************************
// Copy constructor.
//
// Argument List :
//   roCmdPR - A reference to the object to be copied

CmdNgSpicePR::CmdNgSpicePR( const CmdNgSpicePR & roCmdPR ) : CmdBase( )
{
  *this = roCmdPR;
}

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

CmdNgSpicePR::~CmdNgSpicePR( )
{
}

//**************************************************************************************************
// Check that the object attributes are valid.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  CmdNgSpicePR::bValidate( void )
{
  CmdBase::bValidate( );

  if( m_osaNodes.GetCount( )<=0 && m_osaCpnts.GetCount( )<=0 )
    SetErrMsg( wxT("No components or nodes have been selected.") );
  if( ! m_bParams[ ePARAM_VLT ] )
    SetErrMsg( wxT("No parameters selected.") );

  return( bIsValid( ) );
}

//**************************************************************************************************
// Set the object attributes to they're default values.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  CmdNgSpicePR::bSetDefaults( void )
{
  int  i1;

  CmdBase::bSetDefaults( );

  m_eSimEng  = eSIMR_NGSPICE;
  m_eCmdType = eCMD_PR;

  m_eAnaType = eCMD_NONE;

  m_osaNodes.Empty( );
  m_osaCpnts.Empty( );

  for( i1=0; i1<(int)ePARAM_NONE; i1++ ) m_bParams[ i1 ] = false;
  for( i1=0; i1<(int)eCPXPT_NONE; i1++ ) m_bCpxPts[ i1 ] = false;

  return( true );
}

//**************************************************************************************************
// Set the analysis type.
//
// Argument List :
//   eAnaType - An analysis command type
//
// Return Values :
//   true  - Success
//   false - Failure

bool  CmdNgSpicePR::bSetAnaType( eTypeCmd eAnaType )
{
  // Does anything actually need to be done?
  if( eAnaType == m_eAnaType ) return( true );

  // Check that the desired analysis type is supported
  switch( eAnaType )
  {
    case eCMD_OP :
    case eCMD_DC :
    case eCMD_AC :
    case eCMD_TR : break;
    default      : return( false );
  }

  m_eAnaType = eAnaType;

  return( true );
}

//**************************************************************************************************
// Parse the command string.
//
// Eg.s : .PRINT AC VM(6,0) 0-VM(5) VM(1,7) VM(2) VP(6,0) 0-VP(5) VP(1,7) VP(2)
//        .PRINT AC VDB(4)
//
// Return Values :
//   true  - Success
//   false - Failure

bool  CmdNgSpicePR::bParse( void )
{
  wxStringTokenizer  ostk1;
  Component          oCpnt1;
  wxString           os1, os2;
  int                i1, i2;
  size_t             sz1;

  // Clear the object attributes
  os1 = *this;
  bSetDefaults( );
  assign( os1 );

  // Tokenize the command string
  ostk1.SetString( *this );
  if( ostk1.CountTokens( ) < 3 ) return( bValidate( ) );

  // Check command type
  os1 = ostk1.GetNextToken( ).Left( 3 ).Upper( );
  if( os1 != wxT(".PR") )        return( bValidate( ) );

  // Extract the analysis type
  os1 = ostk1.GetNextToken( );
  if(      os1 == wxT("DC")   ) m_eAnaType = eCMD_DC;
  else if( os1 == wxT("AC")   ) m_eAnaType = eCMD_AC;
  else if( os1 == wxT("TRAN") ) m_eAnaType = eCMD_TR;
  else                           return( bValidate( ) );

  // Standardize the argument list format; there are 3 possible formats eg. V(n), V(n,m) or 0-V(n),
  // reformat the latter to : V(0,n).
  os2.Empty( );
  while( ostk1.HasMoreTokens( ) )
  {
    os1 = ostk1.GetNextToken( );
    if( os1.Length( ) < 4 )      return( bValidate( ) );

    if( os1.StartsWith( wxT("0-") ) )
    {
      i1 = os1.Index( wxT('(') );
      i2 = os1.Index( wxT(')') );
      if( i1!=-1 && i2!=-1 && i2>(i1+1) )
        os1 = os1.Mid( 2, (size_t) i1-1 ) + wxT("0,") + os1.Mid( (size_t) i1+1 );
    }

    os2 << os1 << wxT(' ');
  }
  ostk1.SetString( os2 );

  // Extract the parameters to derive, any complex parts and test component and/or test node labels
  while( ostk1.HasMoreTokens( ) )
  {
    // Extract the next field
    os1 = ostk1.GetNextToken( );

    // Extract the parameter specifiers
    switch( (char) os1.GetChar( 0 ) )
    {
      case wxT('V') : m_bParams[ ePARAM_VLT ] = true; break;
      case wxT('I') : m_bParams[ ePARAM_CUR ] = true; break;
      case wxT('P') : m_bParams[ ePARAM_PWR ] = true; break;
      case wxT('R') : m_bParams[ ePARAM_RES ] = true; break;
      default :                  return( bValidate( ) );
    }

    // Extract the complex parts if the analysis type is AC
    if( m_eAnaType == eCMD_AC )
    {
      switch( (char) os1.GetChar( 1 ) )
      {
        case wxT('M') : m_bCpxPts[ eCPXPT_MAG   ] = true; break;
        case wxT('P') : m_bCpxPts[ eCPXPT_PHASE ] = true; break;
        case wxT('R') : m_bCpxPts[ eCPXPT_REAL  ] = true; break;
        case wxT('I') : m_bCpxPts[ eCPXPT_IMAG  ] = true; break;
        case wxT('D') :
          if( os1.Mid( 1, 2 ).Upper( ) == wxT("DB") )
          {
            m_bCpxPts[ eCPXPT_MAG   ] = true;
            m_bCpxPts[ eCPXPT_MAGDB ] = true;
            break;
          }
          else                   return( bValidate( ) );
        default :                return( bValidate( ) );
      }
    }

    // Extract the node and component label/s, there are 2 possible formats eg. V(n) or V(n,m,...),
    // the first denotes a node and the second component.
    i1 = os1.Index( wxT('(') );
    i2 = os1.Index( wxT(')') );
    if( i1!=-1 && i2!=-1 && i2>(i1+1) )
    {
      os1 = os1.Mid( (size_t) i1+1, (size_t) i2-i1-1 );

      if( os1.Find( wxT(',') ) == wxNOT_FOUND )
      { // This is a node, add it to the list if it's not already there
        if( m_osaNodes.Index( os1 ) == wxNOT_FOUND ) m_osaNodes.Add( os1 );
      }
      else
      { // This is a component, add it to the list if it's not already there
        for( sz1=0; sz1<NetList::m_oaCpnts.GetCount( ); sz1++ )
        { // Match the node pair to a component label
          oCpnt1 = NetList::m_oaCpnts.Item( sz1 );
          if( oCpnt1.rosGetNodes( ) == os1 )
          {
            // Add the component name to the test component list
            os2 = oCpnt1.rosGetName( );
            if( m_osaCpnts.Index( os2 ) == wxNOT_FOUND ) m_osaCpnts.Add( os2 );
            break;
          }
        }
      }
    }
    else                         return( bValidate( ) );
  }

  return( bValidate( ) );
}

//**************************************************************************************************
// Format the command string.
//
// Eg.s : .PRINT AC VM(6,0) 0-VM(5) VM(1,7) VM(2) VP(6,0) 0-VP(5) VP(1,7) VP(2)
//        .PRINT AC VDB(4)
//
// Return Values :
//   true  - Success
//   false - Failure

bool  CmdNgSpicePR::bFormat( void )
{
  wxString           osCmd, os1, os2;
  wxStringTokenizer  ostk1;
  Component          oCpnt1;
  int                i1;
  size_t             sz1, sz2;

  // Set the command name
  osCmd = wxT(".PRINT ");

  // Add the analysis type
  switch( m_eAnaType )
  {
    case eCMD_OP :
    case eCMD_DC : osCmd << wxT("DC");   break;
    case eCMD_AC : osCmd << wxT("AC");   break;
    case eCMD_TR : osCmd << wxT("TRAN"); break;
    default      :                       break;
  }

  // Get the paramter list, replace component labels with node pairs and add it to the command
  os1 = rosGetParamLst( );
  sz1 = os1.find( wxT('('), 0 );
  sz2 = os1.find( wxT(')'), sz1 );
  while( sz1!=wxString::npos && sz2!=wxString::npos && sz2>sz1+1 )
  {
    os2 = os1.SubString( sz1+1, sz2-1 );
    oCpnt1 = NetList::roGetCpnt( os2 );
    if( m_osaCpnts.Index( os2 )!=wxNOT_FOUND && oCpnt1.bIsValid( ) )
    {
      os1.Remove( sz1+1, sz2-sz1-1 );
      os1.insert( sz1+1, oCpnt1.rosGetNodes( ) );
    }
    sz1 = os1.find( wxT('('), sz2 );
    sz2 = os1.find( wxT(')'), sz1 );
  }
  osCmd << wxT(' ') << os1;

  // Modify the argument list slightly; reformat V(0,n) to 0-V(n) otherwise NG-Spice barfs
  ostk1.SetString( osCmd );
  osCmd.Empty( );
  while( ostk1.HasMoreTokens( ) )
  {
    os1 = ostk1.GetNextToken( );
    if( (i1=os1.Find( wxT("(0,") )) != wxNOT_FOUND )
    {
      os1.Remove( (size_t) i1+1, 2 );
      os1.Prepend( wxT("0-") );
    }
    if( ! osCmd.IsEmpty( ) ) osCmd << wxT(' ');
    osCmd << os1;
  }

  // Assign the command string value to the base class
  assign( osCmd );

  return( bValidate( ) );
}

//**************************************************************************************************
// Get the list of parameters to print.
//
// Return Values :
//   The number of parameters to derive

const wxString & CmdNgSpicePR::rosGetParamLst( void )
{
  static  wxString  osParamLst;
          wxString  osParam, osCpxPt;
          int       i1, i2, i3;

  // Clear the static variable
  osParamLst = wxT("");

  // Sequence through the various parameter types
  for( i1=0; i1<(int)ePARAM_NONE; i1++ )
  {
    if( ! m_bParams[ i1 ] ) continue;

    switch( (eTypeParam) i1 )
    {
      case ePARAM_VLT : osParam = wxT('V'); break;
      case ePARAM_CUR : osParam = wxT('I'); break;
      case ePARAM_PWR : osParam = wxT('P'); break;
      case ePARAM_RES : osParam = wxT('R'); break;
      default         :                     break;
    }

    // Sequence through the various complex parts
    for( i2=0; i2<(int)eCPXPT_NONE-1; i2++ )
    {
      if( m_eAnaType == eCMD_AC )
      {
        if( ! m_bCpxPts[ i2 ] ) continue;

        switch( (eTypeCpxPt) i2 )
        {
          case eCPXPT_MAG   : osCpxPt = ( m_bCpxPts[ eCPXPT_MAGDB ] ? wxT("DB") : wxT("M") ); break;
          case eCPXPT_PHASE : osCpxPt = wxT('P'); break;
          case eCPXPT_REAL  : osCpxPt = wxT('R'); break;
          case eCPXPT_IMAG  : osCpxPt = wxT('I'); break;
          default           :                     break;
        }
      }
      else osCpxPt = wxT("");

      // Sequence throught all the components and nodes
      for( i3=0; i3<(int)m_osaCpnts.GetCount( ); i3++ )
        osParamLst << osParam << osCpxPt << wxT('(') << m_osaCpnts.Item( i3 ) << wxT(") ");
      for( i3=0; i3<(int)m_osaNodes.GetCount( ); i3++ )
        osParamLst << osParam << osCpxPt << wxT('(') << m_osaNodes.Item( i3 ) << wxT(") ");

      if( m_eAnaType != eCMD_AC ) break;
    }
  }
  osParamLst.Trim( );

  return( osParamLst );
}

//**************************************************************************************************
// Get the total number of parameters to print.
//
// Return Values :
//   The number of parameters to derive

int  CmdNgSpicePR::iGetParamCnt( void )
{
  int  iProbes, iParams, iCpxPts, i1;

  iProbes = m_osaNodes.GetCount( ) + m_osaCpnts.GetCount( );

  for( iParams=0, i1=0; i1<(int)ePARAM_NONE; i1++ )
    if( m_bParams[ i1 ] )
      iParams++;

  if( m_eAnaType == eCMD_AC )
  {
    for( iCpxPts=0, i1=0; i1<(int)eCPXPT_NONE-1; i1++ )
      if( m_bCpxPts[ i1 ] )
        iCpxPts++;
  }
  else iCpxPts = 1;

  return( iProbes * iParams * iCpxPts );
}

//**************************************************************************************************
// Copy the contents of another CmdNgSpicePR object.
//
// Argument List :
//   roCmdPR - A reference to a CmdNgSpicePR object
//
// Return Values :
//   A reference to this object

CmdNgSpicePR & CmdNgSpicePR::operator = ( const CmdNgSpicePR & roCmdPR )
{
  int  i1;

  (CmdBase &) *this = (CmdBase &) roCmdPR;

  bSetAnaType( roCmdPR.m_eAnaType );

  m_osaNodes = roCmdPR.m_osaNodes;
  m_osaCpnts = roCmdPR.m_osaCpnts;

  for( i1=0; i1<(int)ePARAM_NONE; i1++ ) m_bParams[ i1 ] = roCmdPR.m_bParams[ i1 ];
  for( i1=0; i1<(int)eCPXPT_NONE; i1++ ) m_bCpxPts[ i1 ] = roCmdPR.m_bCpxPts[ i1 ];

  bFormat( );

  return( *this );
}

//**************************************************************************************************
// Copy the contents of a CmdGnuCapPR object.
//
// Argument List :
//   roCmdPR - A reference to a CmdGnuCapPR object
//
// Return Values :
//   A reference to this object

CmdNgSpicePR & CmdNgSpicePR::operator = ( const CmdGnuCapPR & roCmdPR )
{
  int  i1;

  (CmdBase &) *this = (CmdBase &) roCmdPR;

  bSetAnaType( roCmdPR.eGetAnaType( ) );

  m_osaNodes = roCmdPR.m_osaNodes;
  m_osaCpnts = roCmdPR.m_osaCpnts;

  for( i1=0; i1<(int)ePARAM_NONE; i1++ ) m_bParams[ i1 ] = roCmdPR.m_bParams[ i1 ];
  for( i1=0; i1<(int)eCPXPT_NONE; i1++ ) m_bCpxPts[ i1 ] = roCmdPR.m_bCpxPts[ i1 ];

  bFormat( );

  return( *this );
}

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

void  CmdNgSpicePR::Print( const wxString & rosPrefix )
{
  int     i1;
  size_t  sz1;

  CmdBase::Print( rosPrefix + wxT("CmdBase::") );

  std::cout << rosPrefix.mb_str( ) << "m_eAnaType      : ";
  switch( m_eAnaType )
  {
    case eCMD_OP   : std::cout << "eCMD_OP";   break;  // Operating point analysis
    case eCMD_DC   : std::cout << "eCMD_DC";   break;  // DC analysis
    case eCMD_AC   : std::cout << "eCMD_AC";   break;  // AC analysis
    case eCMD_TR   : std::cout << "eCMD_TR";   break;  // Transient analysis
    case eCMD_FO   : std::cout << "eCMD_FO";   break;  // Fourier analysis
    case eCMD_DI   : std::cout << "eCMD_DI";   break;  // Distortion analysis
    case eCMD_NO   : std::cout << "eCMD_NO";   break;  // Noise analysis
    case eCMD_PZ   : std::cout << "eCMD_PZ";   break;  // Pole-zero analysis
    case eCMD_SE   : std::cout << "eCMD_SE";   break;  // Sensitivity analysis
    case eCMD_TF   : std::cout << "eCMD_TF";   break;  // Transfer function analysis
    case eCMD_NONE : std::cout << "eCMD_NONE"; break;  // Analysis type not set
    default        : std::cout << "Invalid";   break;
  }
  std::cout << '\n';

  std::cout << rosPrefix.mb_str( ) << "m_osaCpnts[ n ] :";
  for( sz1=0; sz1<m_osaCpnts.GetCount( ); sz1++ )
    std::cout << ' ' << m_osaCpnts[ sz1 ].mb_str( );
  std::cout << '\n';

  std::cout << rosPrefix.mb_str( ) << "m_osaNodes[ n ] :";
  for( sz1=0; sz1<m_osaNodes.GetCount( ); sz1++ )
    std::cout << ' ' << m_osaNodes[ sz1 ].mb_str( );
  std::cout << '\n';

  std::cout << rosPrefix.mb_str( ) << "m_bParams [ n ] :";
  for( i1=0; i1<(int)ePARAM_NONE; i1++ )
    std::cout << ( m_bParams[ i1 ] ? " true " : " false" );
  std::cout << '\n';

  std::cout << rosPrefix.mb_str( ) << "m_bCpxPts [ n ] :";
  for( i1=0; i1<(int)eCPXPT_NONE; i1++ )
    std::cout << ( m_bCpxPts[ i1 ] ? " true " : " false" );
  std::cout << '\n';
}

//**************************************************************************************************
//                                          Test Utility                                           *
//**************************************************************************************************

#ifdef TEST_CMDNGSPICEPR

using  namespace  std;

// Function prototypes

void  Usage( char * psAppName );

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

int  main( int argc, char * argv[ ] )
{
  wxString  osCmd;
  wxString  os1;

  // Validate the argument count passed to the application
  if( argc > 2 )           { Usage( argv[ 0 ] ); exit( EXIT_FAILURE ); }

  // Process the command line arguments
  os1 = wxConvLibc.cMB2WC( argv[ 1 ] );
  if( argc > 1 )
  {
    if( os1 == wxT("-h") ) { Usage( argv[ 0 ] ); exit( EXIT_SUCCESS ); }
    else                   { Usage( argv[ 0 ] ); exit( EXIT_FAILURE ); }
  }

  // Display the utility banner
  cout << "\n  Class CmdNgSpicePR Test Utility"
       << "\n     Version 1.03 (2014-04-04)\n";

  // Create a NG-SPICE PRINT command object
  CmdNgSpicePR  oCmd_PR;

  // Use the following command example to check the formatter and the parser :
  osCmd = wxT(".PRINT AC VM(6,0) 0-VM(5) VM(1,7) VM(2) VP(6,0) 0-VP(5) VP(1,7) VP(2)");

  // Set things up for a formatter test
  oCmd_PR.bSetAnaType( eCMD_AC );
  oCmd_PR.m_osaCpnts.Add( wxT("6,0") );
  oCmd_PR.m_osaCpnts.Add( wxT("0,5") );
  oCmd_PR.m_osaCpnts.Add( wxT("1,7") );
  oCmd_PR.m_osaNodes.Add( wxT("2") );
  oCmd_PR.m_bParams[ ePARAM_VLT   ] = true;
  oCmd_PR.m_bCpxPts[ eCPXPT_MAG   ] = true;
  oCmd_PR.m_bCpxPts[ eCPXPT_PHASE ] = true;
  cout << "\nRun Formatter   : " << ( oCmd_PR.bFormat( ) ? "OK" : "FAULT" );
  cout << "\nTest Cmd Format : " << ( oCmd_PR == osCmd   ? "OK" : "FAULT" );
  cout << "\nExample Command : " << osCmd .mb_str( );
  cout << "\noCmdPR Contents : " << oCmd_PR.mb_str( ) << '\n';

  // Set things up for a parser test
  oCmd_PR.bSetString( osCmd );
  cout << "\nRun Parser      : " << ( oCmd_PR.bParse( ) ? "OK" : "FAULT" );
  oCmd_PR.bFormat( );
  cout << "\nTest Cmd Format : " << ( oCmd_PR == osCmd  ? "OK" : "FAULT" );
  cout << "\nExample Command : " << osCmd .mb_str( );
  cout << "\noCmdPR Contents : " << oCmd_PR.mb_str( ) << '\n';

  cout << '\n';

  exit( EXIT_SUCCESS );
}

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

void  Usage( char * psAppName )
{
  cout << "\nUsage   : " << psAppName << " [-OPTIONS]"
       << "\nOptions :"
       << "\n  -h : Print usage (this message)\n";
}

#endif // TEST_CMDNGSPICEPR

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