//**************************************************************************************************
//                                         PrcNgSpice.cpp                                          *
//                                        ----------------                                         *
// Started     : 2004-05-07                                                                        *
// Last Update : 2018-10-26                                                                        *
// 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 "PrcNgSpice.hpp"

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

PrcNgSpice::PrcNgSpice( void ) : PrcSimEngBase( )
{
  // Set the simulation engine type
  m_eSimEng = eSIMR_NGSPICE;

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

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

PrcNgSpice::~PrcNgSpice( )
{
}

//**************************************************************************************************
// Match a component to a set of node labels.
//
// Component voltages cannot be specified using the component label in the NG-Spice print statement.
// The two node labels connected to the component must be specified. When parsing the print
// statement the component label must be derived from the node labels. This may not work where
// components are connected in parallel. The nodes are specified in the form "<Node1>,<Node2>"
// eg. "1,2".
//
// Argument List :
//   roSimn     - The simulation object
//   rosToNodes - The nodes to match, also used to return the component label
//
// Return Values :
//   true  - Success (a component with the specified nodes was found)
//   false - Failure

bool  PrcNgSpice::bMatchCpnt( SimnNgSpice & roSimn, wxString & rosToNodes )
{
  wxArrayString  osaNodes;
  Component      tCpnt;
  wxString       os1;
  size_t         sz1;

  // Argument validity checks
  if( rosToNodes.IsEmpty( ) )             return( false );
  if( roSimn.m_oaCpnts.GetCount( ) <= 0 ) return( false );
  if( rosToNodes.Freq( wxT(',') ) != 1 )  return( false );

  // Extract the node labels
  osaNodes.Add( rosToNodes.BeforeFirst( wxT(',') ) );
  osaNodes.Add( rosToNodes.AfterLast( wxT(',') ) );

  // Attempt to match the nodes with a component
  for( sz1=0; sz1<roSimn.m_oaCpnts.GetCount( ); sz1++ )
  {
    tCpnt = roSimn.m_oaCpnts.Item( sz1 );
    if( ! tCpnt.bParse( ) )            continue;
    if( tCpnt.rosaGetNodes( ) == osaNodes ) break;
  }

  // Was a match found?
  if( ! tCpnt.bIsValid( ) )               return( false );

  rosToNodes = tCpnt.rosGetName( );

  return( true );
}

//**************************************************************************************************
// Convert a list of component labels to the corresponding list of node pairs.
//
// Argument List :
//   rosaCpnts - A list of component labels
//   roSimn    - A simulation object

void  PrcNgSpice::Convert2Nodes( wxArrayString & rosaCpnts, SimnNgSpice & roSimn )
{
  Component  tCpnt;
  wxString   os1;
  size_t     sz1;

  for( sz1=0; sz1<rosaCpnts.GetCount( ); sz1++ )
  {
    os1 = rosaCpnts.Item( sz1 );
    tCpnt = roSimn.roGetCpnt( os1 );
    if( ! tCpnt.bParse( ) )                      continue;
    if( tCpnt.rosaGetNodes( ).GetCount( ) != 2 ) continue;
    os1.Empty( );
    os1 << tCpnt.rosaGetNodes( ).Item( 0 ) << wxT(',') << tCpnt.rosaGetNodes( ).Item( 1 );
    rosaCpnts.Item( sz1 ) = os1;
  }
}

//**************************************************************************************************
// Create a WIDTH command based on a PRINT command.
//
// By default raw output from NG-Spice uses an 80 column output width which allows about two
// dependant variables to be displayed. To display more information a .WIDTH statement must be added
// to increase the number columns in the output.
//
// Argument List :
//   rosCmdPR - The Print command
//
// Return Values :
//   Success - A .WIDTH command
//   Failure - An empty string

wxString & PrcNgSpice::rosMakeCmdWIDTH( wxString & rosCmdPR )
{
  static  wxString   osCmdWIDTH;
  wxStringTokenizer  ostk1;
  wxString           os1;
  int                i1=0;

  osCmdWIDTH.Empty( );

  ostk1.SetString( rosCmdPR );
  if( ostk1.GetNextToken( ).StartsWith( wxT(".PR") ) )
  {
    os1 = ostk1.GetNextToken( );

    if(      os1.StartsWith( wxT("DC") ) )
      i1 = 8 + 16 + ostk1.CountTokens( ) * 16;
    else if( os1.StartsWith( wxT("AC") ) )
      i1 = 8 + 2 * 16 + ostk1.CountTokens( ) * 16;
    else if( os1.StartsWith( wxT("TR") ) )
      i1 = 8 + 16 + ostk1.CountTokens( ) * 16;

    if( i1 > 80 ) osCmdWIDTH << wxT(".WIDTH OUT=") << i1;
  }

  return( osCmdWIDTH );
}

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

bool  PrcNgSpice::bFmtGeneral( void )
{
  wxString  os1;
  size_t    sz1, sz2;

  // This function requires the results file to be open
  if( ! m_oFileResults.IsOpened( ) ) return( false );

  // Find the beginning of the data area (ie. the first line starting with "Index")
  for( sz1=0; sz1<m_oFileResults.GetLineCount( ); sz1++ )
  {
    os1 = m_oFileResults.GetLine( sz1 );
    if( os1.StartsWith( wxT("Index") ) ) break;
  }

  // Delete everything before the data area
  sz2 = sz1;
  for( sz1=0; sz1<sz2; sz1++ )             m_oFileResults.RemoveLine( 0 );
  if( m_oFileResults.GetLineCount( ) > 0 ) m_oFileResults.RemoveLine( 1 );

  // Delete lines other than data lines
  for( sz1=1; sz1<m_oFileResults.GetLineCount( ); sz1++ )
  {
    os1 = m_oFileResults.GetLine( sz1 );
    if( ! wxIsdigit( os1.GetChar( 0 ) ) )
    {
      m_oFileResults.RemoveLine( sz1 );
      sz1--;
    }
  }

  // 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 header in the results file.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcNgSpice::bFmtColLabels( void )
{
  wxStringTokenizer  ostk1;
  wxString           osLine, os1;

  // This function requires the results file to be open
  if( ! m_oFileResults.IsOpened( ) ) return( false );

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

  // Dispose of the first field (ie. "Index")
  ostk1.GetNextToken( );

  // Extract the operator field and replace the NG-Spice column labels
  os1 << ostk1.GetNextToken( ) << wxT(' ') << m_osColLbls;
  ostk1.SetString( os1 );

  // Format each field
  osLine.Empty( );
  while( ostk1.HasMoreTokens( ) )
  {
    // Add the next label to the line
    // (06/08/2005) gWave breaks if there's a space after initial '#' ???
    if( osLine.IsEmpty( ) ) osLine << wxT('#');
    else                    osLine << wxT(' ');
    osLine << ostk1.GetNextToken( );
  }

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

  m_oFileResults.GetFirstLine( ) = osLine;

  return( true );
}

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

bool  PrcNgSpice::bFmtDataLines( void )
{
  wxStringTokenizer  ostk1;
  wxString           osLine, os1;
  size_t             sz1;
  double             df1;

  // This function requires the results file to be open
  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( ) < 3 )   return( false );

    // Dispose of the first field
    ostk1.GetNextToken( );

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

    m_oFileResults.GetLine( sz1 ) = osLine;

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

  return( true );
}

//**************************************************************************************************
// Format any phase values contained in the results file.
//
// This is only pertinent for an AC analysis. It involves removing the discontinuity at 360 degree
// in the data.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PrcNgSpice::bFmtPhaseData( void )
{
  // ??? (02/02/2006) Not yet implemented

  return( true );
}

//**************************************************************************************************
// 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  PrcNgSpice::bMakeArgLst( SimnBase & roSimn )
{
  wxString  os1;

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

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

  // Get results file column labels (replace node pairs with component names)
  if( roSimn.eGetSimEng( ) == eSIMR_NGSPICE )
    m_osColLbls = ((SimnNgSpice &) roSimn).m_oCmdPR.rosGetParamLst( );

  return( true );
}

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

bool  PrcNgSpice::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
  bRtn = bFmtGeneral( );

  // Format the column header line
  if( bRtn ) bRtn = bFmtColLabels( );

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

  // 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 );
}

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