//**************************************************************************************************
//                                          PrcGnuCap.cpp                                          *
//                                         ---------------                                         *
// Started     : 2003-09-01                                                                        *
// Last Update : 2018-10-26                                                                        *
// Copyright   : (C) 2003-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 "PrcGnuCap.hpp"

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

PrcGnuCap::PrcGnuCap( void ) : PrcSimEngBase( )
{
  // Set the simulation engine type
  m_eSimEng = eSIMR_GNUCAP;

  // Set the simulator binary file name if it can be found
  bSetBinFile( BIN_GNUCAP );
}

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

PrcGnuCap::~PrcGnuCap( )
{
}

//**************************************************************************************************
// Check / format the component lines in the simulation object so that they are compatible with
// GNU-Cap. Ie. add the substrate node to BJT components which are missing it, the substrate node is
// assumed to be connected to the collector. Eg. "Q1 NC NB NE BC109" becomes "Q1 NC NB NE NC BC109".
//
// Argument List :
//   roaCpnts - An array of ArrayComponent objects
//
// Return Values :
//   true  - Success
//   false - Failure
/* ??? 17/08/2009
bool  PrcGnuCap::bFmtCpnts( ArrayComponent & roaCpnts )
{
  wxString  os1;
  size_t    sz1;

  for( sz1=0; sz1<roaCpnts.GetCount( ); sz1++ )
  {
    if( roaCpnts[ sz1 ].m_eType != eCPNT_BJT )        continue;
    if( roaCpnts[ sz1 ].m_osaNodes.GetCount( ) != 4 ) continue;

    os1 = roaCpnts[ sz1 ].m_osaNodes[ 0 ];  // Get the collector node label
    roaCpnts[ sz1 ].m_osaNodes.Add( os1 );  // Add substrate node to node list
  }

  return( true );
}
*/
//**************************************************************************************************
// This function is a generic results file formatter which works in most circumstance.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcGnuCap::bFmtGeneral( void )
{
  wxString  os1, os2;
  size_t    sz1, sz2;
  wxChar    oc1;

  // This function requires the result file to have been opened
  if( ! m_oFileResults.IsOpened( ) ) return( false );

  // Find the beginning of the data area (ie. the last line beginning with '#') and delete all
  // preceding lines
  for( sz1=sz2=0; sz1<m_oFileResults.GetLineCount( ); sz1++ )
  {
    os1 = m_oFileResults.GetLine( sz1 );
    if( ! os1.IsEmpty( ) )
      if( os1.GetChar( 0 ) == wxT('#') )
        sz2 = sz1;
  }
  for( sz1=0; sz1<sz2; sz1++ ) m_oFileResults.RemoveLine( 0 );

  // Delete any simulator error messages (eg. "open circuit: internal node 3"). All lines should
  // start with a digit except for the first line (ie. the column headers).
  for( sz1=1; !m_oFileResults.Eof( ) && sz1<m_oFileResults.GetLineCount( ); sz1++ )
  {
    os1 = m_oFileResults.GetLine( sz1 );

    if( os1.Length( ) <= 1 )
    { // Delete empty lines
      m_oFileResults.RemoveLine( sz1 );
      sz1--;
      continue;
    }

    oc1 = os1.GetChar( 1 );
    if( ! wxIsdigit( oc1 ) && oc1!=wxT('-') && oc1!=wxT('+') )
    { // Delete non-data lines
      m_oFileResults.RemoveLine( sz1 );
      sz1--;
      continue;
    }
  }

  // Check that a data section exists
  if( m_oFileResults.GetLineCount( ) <= 1 )
  {
    SetErrMsg( wxT("Couldn't find a data section in the results file.") );
    return( false );
  }

  return( true );
}

//**************************************************************************************************
// Format the column headers in the results file. Set the label spacing and insert the input
// variable name into the column labels if necessary.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcGnuCap::bFmtColLabels( void )
{
  wxStringTokenizer  ostk1;
  wxString           osLine, os1, os2;
//  int                i1;

  // This function requires the result file to have already been opened
  if( ! m_oFileResults.IsOpened( ) ) return( false );

  // Get the column header line
  ostk1.SetString( m_oFileResults.GetFirstLine( ) );
  if( ostk1.CountTokens( ) < 1 )
  {
    SetErrMsg( wxT("There's insufficient column headers in the results file ie. should be 2 or more.") );
    return( false );
  }

  // GNU-Cap has a bug in the OP and DC analysis results. In the case of OP the column label for the
  // temperature values is missing ie. "Temp". For DC the column label for the sweep source values
  // is missing eg. "Vin". This section of code inserts these labels.
  os1 = ostk1.GetNextToken( );
  os2 = roGetResultsFile( ).GetFullName( ).Right( 3 );
// osLine = wxT("# "); // ??? 06/08/2005 Gwave breaks if there's a space after the initial '#'
  osLine = wxT('#');
  if(      os2 == wxT(".op") ) osLine << wxT("Temp");
  else if( os2 == wxT(".dc") ) osLine << m_osSigSrcName;
  else                         osLine << os1.Right( os1.Length( )-1 );

  // Format each header field
  while( ostk1.HasMoreTokens( ) )
  {
    // Pad the column with spaces to the required width
//    i1 = GNUCAP_COL_WD - (osLine.Length( ) % GNUCAP_COL_WD);
//    osLine.Append( wxT(' '), i1 );

    // Add the next label to the line
    osLine << wxT(' ') << ostk1.GetNextToken( );
  }

  osLine.Trim( ); // Remove trailing space characters

  m_oFileResults.GetFirstLine( ) = osLine;

  return( true );
}

//**************************************************************************************************
// Format the data lines in the results file.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcGnuCap::bFmtDataLines( void )
{
  wxStringTokenizer  ostk1;
  wxString           osLine, os1;
  size_t             sz1;
  double             df1;
//  int                i1;

  // This function requires the result file to have been opened
  if( ! m_oFileResults.IsOpened( ) ) return( false );

  // Check that there is a data section
  if( m_oFileResults.GetLineCount( ) <= 1 )
  {
    SetErrMsg( wxT("There's no data section in the results file.") );
    return( false );
  }

  // Format the data lines
  for( sz1=1; sz1<m_oFileResults.GetLineCount( ); sz1++ )
  {
    // Get a line of data
    osLine = m_oFileResults.GetLine( sz1 );

    // Tokenize the string
    ostk1.SetString( osLine );
    if( ostk1.CountTokens( ) < 1 )   return( false );

    // Format each field
    osLine.Empty( );
    while( ostk1.HasMoreTokens( ) )
    {
      // Add the next parameter to the line
      os1 = ostk1.GetNextToken( );
      if( ! CnvtType::bStrToFlt( os1, &df1 ) ) df1 =      -9.9e-99;
      if( ! CnvtType::bFltToStr( df1,  os1 ) ) os1 = wxT("-9.9e-99");
      osLine << os1 << wxT(' ');

      // Pad the column with spaces to the required width
//      i1 = GNUCAP_COL_WD - (osLine.Length( ) % GNUCAP_COL_WD);
//      osLine.Append( wxT(' '), i1 );
    }

    osLine.Trim( ); // Remove trailing space characters

    m_oFileResults.GetLine( sz1 ) = osLine;
  }

  return( true );
}

//**************************************************************************************************
// This function is a results file formatter for Fourier analysis result.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcGnuCap::bFmtResultsFO( void )
{
  bool               bRtn;
  wxConfig         * poConfig;
  wxStringTokenizer  ostk1;
  wxString           os1, os2, os3;
  size_t             sz1, sz2;
  bool               b1;

  // This function requires the result file to have been opened
  if( ! m_oFileResults.IsOpened( ) ) return( false );

  // Find the beginning of the data area
  for( sz1=sz2=0; sz1<m_oFileResults.GetLineCount( ); sz1++ )
  {
    os1 = m_oFileResults.GetLine( sz1 );
    if( ! os1.IsEmpty( ) )
    {
      if( os1.Contains( wxT("--- actual ---") ) )
      {
        sz2 = sz1;
        break;
      }
    }
  }

  // Delete all the lines before the data area
  for( sz1=0; sz1<sz2; sz1++ ) m_oFileResults.RemoveLine( 0 );

  // Delete unwanted header lines
  for( os1=m_oFileResults.GetFirstLine( ); !m_oFileResults.Eof( );
                                                                 os1=m_oFileResults.GetNextLine( ) )
  {
    if( os1.StartsWith( wxT("#freq") ) )
      m_oFileResults.RemoveLine( m_oFileResults.GetCurrentLine( ) );
  }

  // Setup the configuration pointer
  poConfig = (wxConfig *) wxConfig::Get( );
  if( poConfig == NULL ) return( false );
  poConfig->SetPath( wxT("/GNU-Cap") );

  // Delete unwanted columns of data
  for( ostk1.SetString( m_oFileResults.GetFirstLine( ) ); !m_oFileResults.Eof( );
                                                  ostk1.SetString( m_oFileResults.GetNextLine( ) ) )
  {
    if( ostk1.CountTokens( ) != 7 ) continue;

    // Get the operator field ie. frequency
    os1 = ostk1.GetNextToken( );

    // Do we want actual or relative magnitude?
    poConfig->Read( wxT("FO_MagRel"), &b1, false );
    if( b1 ) for( sz1=0; sz1<3; sz1++ ) ostk1.GetNextToken( );

    // Do we want dB magnitude?
    poConfig->Read( wxT("FO_MagDB"), &b1, false );
    if( b1 )
    {
      ostk1.GetNextToken( );
      os1 << wxT(' ') << ostk1.GetNextToken( );
    }
    else
    {
      os1 << wxT(' ') << ostk1.GetNextToken( );
      ostk1.GetNextToken( );
    }

    // Do we want phase?
    poConfig->Read( wxT("FO_Phase"), &b1, false );
    if( b1 ) os1 << wxT(' ') << ostk1.GetNextToken( );

    // Record the data
    m_oFileResults[ m_oFileResults.GetCurrentLine( ) ] = os1;
  }

  // Construct the data column headers
  os1 = wxT("#Freq");
  for( ostk1.SetString( m_oFileResults.GetFirstLine( ) ); !m_oFileResults.Eof( );
                                                  ostk1.SetString( m_oFileResults.GetNextLine( ) ) )
  {
    // Look for header lines
    if( ostk1.GetNextToken( ) != wxT("#") ) continue;

    os2 = ostk1.GetNextToken( );

    // Do we want dB magnitude?
    os3 = os2;
    poConfig->Read( wxT("FO_MagDB"), &b1, false );
    if( b1 ) os3.insert( 1, wxT("DB") );
    os1 << wxT(' ') << os3;

    // Do we want phase?
    poConfig->Read( wxT("FO_Phase"), &b1, false );
    if( b1 )
    {
      os3 = os2;
      os3.insert( 1, wxT("P") );
      os1 << wxT(' ') << os3;
    }

    // Remove the unwanted header lines
    if( m_oFileResults.GetCurrentLine( ) > 0 )
      m_oFileResults.RemoveLine( m_oFileResults.GetCurrentLine( ) );
  }
  m_oFileResults[ 0 ] = os1;

  // Determine the number of unique data lines
  ostk1.SetString( m_oFileResults.GetFirstLine( ) );
  ostk1.SetString( m_oFileResults.GetNextLine( ) );
  os1 = ostk1.GetNextToken( );
  for( ostk1.SetString( m_oFileResults.GetNextLine( ) ), sz1=1; !m_oFileResults.Eof( );
                                           ostk1.SetString( m_oFileResults.GetNextLine( ) ), sz1++ )
  {
    if( os1 == ostk1.GetNextToken( ) )
      break;
  }

  // Concatinate data lines according to frequency
  for( sz2=1; sz2<=sz1; sz2++ )
  {
    m_oFileResults.GoToLine( sz2-1 );
    ostk1.SetString( m_oFileResults.GetNextLine( ) );
    os1 = ostk1.GetNextToken( );
    for( ostk1.SetString( m_oFileResults.GetNextLine( ) ); !m_oFileResults.Eof( );
                                                  ostk1.SetString( m_oFileResults.GetNextLine( ) ) )
    {
      if( os1 == ostk1.GetNextToken( ) )
        m_oFileResults[ sz2 ] << wxT(' ') << ostk1.GetString( );
    }
  }

  // Delete data lines which are no longer needed
  m_oFileResults.GoToLine( sz1+1 );
  while( ! m_oFileResults.Eof( ) )
    m_oFileResults.RemoveLine( m_oFileResults.GetCurrentLine( ) );

  // Format the column header line and insert it at the start of the file
  bRtn = bFmtColLabels( );

  // Format the data lines
  if( ! bFmtDataLines( ) ) bRtn = false;

  return( bRtn );
}

//**************************************************************************************************
// Make the process argument list based on the contents of a simulation object.
//
// Argument List :
//   roSimn - The simulation object
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcGnuCap::bMakeArgLst( SimnBase & roSimn )
{
  wxString  os1;

  // Record the sweep source component name since GNU-Cap has a bug in the DC
  // analysis results. The column label for the sweep source values is missing.
  m_osSigSrcName = roSimn.m_oCpntSigSrc.rosGetName( );

  // Envoke the base class operations
  if( ! PrcSimEngBase::bMakeArgLst( roSimn ) ) return( false );

  // Construct the command line to execute the simulation
  os1 = wxT("-b ") + rosEscSpaceChrs( roSimn.roGetSaveFile( ).GetFullPath( ) );
  if( ! bSetArgLst( os1 ) )
  {
    SetErrMsg( wxT("Couldn't set argument list") );
    return( false );
  }

  return( true );
}

//**************************************************************************************************
// Format the contents of the results file so that gWave can read it ie. a header line at the top
// containing parameter names followed by the columns of data.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcGnuCap::bFmtResults( void )
{
  bool      bRtn;
  wxString  os1;

  // Clear error message
  PrcBase::ClrErrMsg( );

  // Check that the results file exists
  if( ! roGetResultsFile( ).FileExists( ) )
  {
    os1.Empty( );
    os1 << wxT("Results file doesn't exist : \n\n") << roGetResultsFile( ).GetFullPath( );
    PrcBase::SetErrMsg( os1 );
    return( false );
  }

  // Attempt to open the results file
  if( ! m_oFileResults.Open( roGetResultsFile( ).GetFullPath( ) ) )
  {
    os1.Empty( );
    os1 << wxT("Results file couldn't be opened : \n\n") << roGetResultsFile( ).GetFullPath( );
    PrcBase::SetErrMsg( os1 );
    return( false );
  }

  // Format the simulation results
  os1 = roGetResultsFile( ).GetFullName( ).Right( 3 );
  if( os1 != wxT(".fo") )
  {
    bRtn = bFmtGeneral( );

    // Format the column header line and insert it at the start of the file
    if( bRtn ) bRtn = bFmtColLabels( );

    // Format the data lines
    if( bRtn ) bRtn = bFmtDataLines( );
  }
  else bRtn = bFmtResultsFO( );

  // Align all the columns in the results file
  if( bRtn ) bRtn = PrcSimEngBase::bFmtResults( );

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

  return( bRtn );
}

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