//**************************************************************************************************
//                                          StrUtils.cpp                                           *
//                                         --------------                                          *
// Started     : 2008-05-15                                                                        *
// Last Update : 2016-10-23                                                                        *
// Copyright   : (C) 2008-2016 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 "StrUtils.hpp"

//**************************************************************************************************
// Compare algorithm for sorting node labels.
//
// As a general rule node labels are automatically generated and are purely numeric. Where a label
// diverges from this form it's often because the user has identified it as having some special
// significance. When displayed in a list control on a GUI it's more convenient to have these
// labels positioned towards the top.
//
// Argument List :
//   rosArg1 - a reference to the first  string to be compared
//   rosArg2 - a reference to the second string to be compared
//
// Return Values :
//   If ros1 > ros2 then -1
//   If ros1 = ros2 then  0
//   If ros1 < ros2 then  1

int  iStrCmpNode( const wxString & rosArg1, const wxString & rosArg2 )
{
  wxChar  oc1, oc2;
  size_t  sz1, sz2;
  long    li1, li2;

  // First look for empty strings
  sz1 = rosArg1.Len( );
  sz2 = rosArg2.Len( );
  if( sz1==0 && sz2!=0 ) return(  1 );
  if( sz1==0 && sz2==0 ) return(  0 );
  if( sz1!=0 && sz2==0 ) return( -1 );

  // Now do some simple tests
  oc1 = rosArg1.at( 0 );
  oc2 = rosArg2.at( 0 );
  if( wxIsalpha( oc1 ) && wxIsdigit( oc2 ) ) return( -1 );
  if( wxIsdigit( oc1 ) && wxIsalpha( oc2 ) ) return(  1 );

  // If the node names are purely numeric do the compare
  if( rosArg1.ToLong( &li1 ) && rosArg2.ToLong( &li2 ) )
  {
    if( li1 >  li2 ) return(  1 );
    if( li1 == li2 ) return(  0 );
    if( li1 <  li2 ) return( -1 );
  }

  // It's all got too hard, just compare the labels as component labels
  return( iStrCmpCpnt( rosArg1, rosArg2 ) );
}

//**************************************************************************************************
// Compare algorithm for sorting component labels.
//
// As a rule component labels are comprised of a single letter (signifying the component type)
// followed by a number. Where a label diverges from this form it's often because the user has
// identified it as having some significance. When displayed in a list control on a GUI it's more
// convenient to have these special labels positioned towards the top. Any remaining labels are
// simply sorted alphabetically.
//
// Argument List :
//   rosArg1 - a reference to the first  string to be compared
//   rosArg2 - a reference to the second string to be compared
//
// Return Values :
//   If ros1 > ros2 then -1
//   If ros1 = ros2 then  0
//   If ros1 < ros2 then  1

int  iStrCmpCpnt( const wxString & rosArg1, const wxString & rosArg2 )
{
  wxString  osStr1, osStr2;
  ulong     ulNum1, ulNum2;
  wxString  osEnd1, osEnd2;
  wxString  os1;
  wxChar    oc1;
  size_t    sz1, sz2;

  // First look for empty strings
  sz1 = rosArg1.Len( );
  sz2 = rosArg2.Len( );
  if( sz1==0 && sz2!=0 ) return(  1 );
  if( sz1==0 && sz2==0 ) return(  0 );
  if( sz1!=0 && sz2==0 ) return( -1 );

  // Parse the first alpha and numeric components of the first argument
  for( sz1=0, osStr1=wxT(""); sz1<rosArg1.Len( ); sz1++ )
  {
    oc1 = rosArg1.at( sz1 );
    if( ! wxIsalpha( oc1 ) ) break;
    osStr1 += oc1;
  }
  for( os1=wxT(""); sz1<rosArg1.Len( ); sz1++ )
  {
    oc1 = rosArg1.at( sz1 );
    if( ! wxIsdigit( oc1 ) ) break;
    os1 += oc1;
  }
  if( ! os1.IsEmpty( ) ) os1.ToULong( &ulNum1 );
  else                   ulNum1 = 0;
  osEnd1 = rosArg1.Mid( sz1 );

  // Parse the first alpha and numeric components of the second argument
  for( sz1=0, osStr2=wxT(""); sz1<rosArg2.Len( ); sz1++ )
  {
    oc1 = rosArg2.at( sz1 );
    if( ! wxIsalpha( oc1 ) ) break;
    osStr2 += oc1;
  }
  for( os1=wxT(""); sz1<rosArg2.Len( ); sz1++ )
  {
    oc1 = rosArg2.at( sz1 );
    if( ! wxIsdigit( oc1 ) ) break;
    os1 += oc1;
  }
  if( ! os1.IsEmpty( ) ) os1.ToULong( &ulNum2 );
  else                   ulNum2 = 0;
  osEnd2 = rosArg2.Mid( sz1 );

  // Compare the first string component of the two arguments
  sz1 = osStr1.Len( );
  sz2 = osStr2.Len( );
  if( sz1 == sz2 )
    if( osStr1 != osStr2 )
      return( osStr1.Cmp( osStr2 ) );
  if( sz1 > sz2 ) return( -1 );
  if( sz1 < sz2 ) return(  1 );

  // Compare the first numeric component of the two arguments
  sz1 = osEnd1.Len( );
  sz2 = osEnd2.Len( );
  if( sz1==0 && sz2==0 )
  {
    if( ulNum1 >  ulNum2 ) return(  1 );
    if( ulNum1 == ulNum2 ) return(  0 );
    if( ulNum2 >  ulNum1 ) return( -1 );
  }
  if( sz1 > sz2 ) return( -1 );
  else            return(  1 );

  // If all else fails do it all again
//std::cout << "\niStrCmpCpnt( ) : Look out! Re-entrance\n";
  return( iStrCmpCpnt( osEnd1, osEnd2 ) );
}

//**************************************************************************************************
// Compare algorithm for sorting signal source labels.
//
// Signal source labels form a sub-set of all component labels which, as a general rule, are
// comprised of a single letter (signifying the component type) followed by a number. Where a label
// diverges from this form it's often because the user has identified it as having some special
// significance. When displayed in a list control on a GUI it's more convenient to have these
// significant labels positioned towards the top.
//
// Argument List :
//   rosArg1 - a reference to the first  string to be compared
//   rosArg2 - a reference to the second string to be compared
//
// Return Values :
//   If ros1 > ros2 then -1
//   If ros1 = ros2 then  0
//   If ros1 < ros2 then  1

int  iStrCmpSrc( const wxString & rosArg1, const wxString & rosArg2 )
{
  eTypeCpnt  eCpnt1, eCpnt2;
  size_t     sz1, sz2;

  // First look for empty strings
  sz1 = rosArg1.Len( );
  sz2 = rosArg2.Len( );
  if( sz1==0 && sz2!=0 ) return(  1 );
  if( sz1==0 && sz2==0 ) return(  0 );
  if( sz1!=0 && sz2==0 ) return( -1 );

  // Compare component types
  eCpnt1 = Component::eGetType( rosArg1 );
  eCpnt2 = Component::eGetType( rosArg2 );
  switch( eCpnt1 )
  {
    case eCPNT_IVS : sz1 = 5; break;
    case eCPNT_ICS : sz1 = 4; break;
    case eCPNT_IND : sz1 = 3; break;
    case eCPNT_CAP : sz1 = 2; break;
    case eCPNT_RES : sz1 = 1; break;
    default        : sz1 = 0; break;
  }
  switch( eCpnt2 )
  {
    case eCPNT_IVS : sz2 = 5; break;
    case eCPNT_ICS : sz2 = 4; break;
    case eCPNT_IND : sz2 = 3; break;
    case eCPNT_CAP : sz2 = 2; break;
    case eCPNT_RES : sz2 = 1; break;
    default        : sz2 = 0; break;
  }
  if( sz1>0 && sz2>0 )
  {
    if( sz1 > sz2 ) return( -1 );
    if( sz1 < sz2 ) return(  1 );
  }

  // It's all got too hard, just compare the labels as component labels
  return( iStrCmpCpnt( rosArg1, rosArg2 ) );
}

//**************************************************************************************************
// Reduce a string down so that it can be displayed in a single line.
//
// (This function is intended for debugging purposes. Multi-line and formated messages can be sent
//  to std::cout or std::cerr.)
//
// Argument List :
//   ros1 - The string to be reformatted
//
// Return Values :
//   A reference to string containing a single line

const wxString & rosStrToLine( const wxString & ros1 )
{
  static  wxString  os1;
          size_t    sz1;
          char      c1;

  // Copy the input string
  os1 = ros1;

  // Remove white-space (space, tab, '\n' & '\r') from both ends of the string
  os1.Trim( false );
  os1.Trim( true );

  // Replace '\n' & '\r' within the string with space characters
  for( sz1=0; sz1<os1.Length( ); sz1++ )
  {
    c1 = os1.GetChar( sz1 );

    if( c1=='\n' || c1=='\r' )
      os1.SetChar( sz1, ' ' );
  }

  // Replace sequences of space characters with one space character
  while( (sz1=os1.find(wxT("  "))) != wxString::npos )
    os1.erase( sz1, 1 );

  return( os1 );
}

//**************************************************************************************************
// Convert a wxString object to an wxArrayString object.
//
// Argument List :
//   ros1 - A string object containing zero or more string tokens
//
// Return Values :
//   A reference to a string array containing the string tokens

const wxArrayString & roasStrToArr( const wxString & ros1 )
{
  static  wxArrayString      osa1;
          wxStringTokenizer  ostk1;

  osa1.Empty( );

  if( ! ros1.IsEmpty( ) )
  {
    ostk1.SetString( ros1 );
    while( ostk1.HasMoreTokens( ) )
      osa1.Add( ostk1.GetNextToken( ) );
  }

  return( osa1 );
}

//**************************************************************************************************
// Convert an wxArrayString object to a space delimited wxString object.
//
// Argument List :
//   roas1 - An string array object containing zero or more string tokens
//
// Return Values :
//   A reference to a string object containing the string tokens separated by space characters

const wxString & rosArrToStr( const wxArrayString & roas1 )
{
  static  wxString  os1;
          size_t    sz1;

  os1.Empty( );

  if( ! roas1.IsEmpty( ) )
  {
    for( sz1=0; sz1<roas1.GetCount( ); sz1++ )
    {
      if( sz1 > 0 ) os1 << wxT(' ');
      os1 << roas1.Item( sz1 );
    }
  }

  return( os1 );
}

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

#ifdef TEST_STRUTILS

using  namespace  std;

// Function prototypes

void  Usage( char * psAppName );
void  PrintArray( wxArrayString & roas );

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

int  main( int argc, char * argv[ ] )
{
  wxArrayString  oas1;
  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.at( 0 ) == wxT('-') )
    {
      if( os1.at( 1 ) == wxT('h') ) { Usage( argv[0] ); exit( EXIT_SUCCESS ); }
      else                          { Usage( argv[0] ); exit( EXIT_FAILURE ); }
    }
  }

  // Display the utility banner
  cout << "\n    StrUtils Test Utility"
       << "\n  Version 1.02 (2014-04-04)\n";

  // Exercise the function : iStrCmpCpnt( )
  oas1.Empty( );
  oas1.Add( wxT("3,1") );
  oas1.Add( wxT("2,0") );
  std::cout << "\nTest iStrCmpCpnt( ) :\n";
  std::cout << "  Before : "; PrintArray( oas1 );
  oas1.Sort( &iStrCmpCpnt );
  std::cout << "  After  : "; PrintArray( oas1 ); // */

  // Exercise the function : iStrCmpNode( )
  oas1.Empty( );
  oas1.Add( wxT("Vcc") );
  oas1.Add( wxT("1") );
  oas1.Add( wxT("2") );
  oas1.Add( wxT("2SK1058_Drive") );
  oas1.Add( wxT("3") );
  oas1.Add( wxT("V1") );
  std::cout << "\nTest iStrCmpNode( ) :\n";
  std::cout << "  Before : "; PrintArray( oas1 );
  oas1.Sort( &iStrCmpNode );
  std::cout << "  After  : "; PrintArray( oas1 ); // */

  // Exercise the function : iStrCmpSrc( )
  oas1.Empty( );
  oas1.Add( wxT("Iin") );
  oas1.Add( wxT("Rin") );
  oas1.Add( wxT("Vin") );
  oas1.Add( wxT("V2") );
  oas1.Add( wxT("I1") );
  std::cout << "\nTest iStrCmpSrc( ) :\n";
  std::cout << "  Before : "; PrintArray( oas1 );
  oas1.Sort( &iStrCmpNode );
  std::cout << "  After  : "; PrintArray( oas1 ); // */

  std::cout << '\n';

  exit( EXIT_SUCCESS );
}

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

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

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

void  PrintArray( wxArrayString & roas )
{
  size_t  sz1;

  for( sz1=0; sz1<roas.GetCount( ); sz1++ )
  {
    if( sz1 > 0 ) std::cout << "  ";
    std::cout << roas.Item( sz1 ).mb_str( );
  }

  std::cout << '\n';
}

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

#endif // TEST_STRUTILS
