//********************************************************************************
//* File       : TFMTest/.cpp                                                    *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2025-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice below                               *
//* Date       : 26-Dec-2025                                                     *
//* Version    : (see tfmtVersion string below)                                  *
//*                                                                              *
//* Description: This a simple test and demo application supporting the          *
//*              TFMan class (Temporary File Management).                        *
//*              -- This application _does not_ access the AnsiCmd library.      *
//*                 It is a stand-alone utility used only for stress-testing     *
//*                 the TFMan class methods.                                     *
//*              -- Each of the public methods of the TFMand class is exercised  *
//*                 by this application.                                         *
//*              -- This utility uses the author's gString class for text        *
//*                 analysis and formatting.                                     *
//*              -- Support for C++17 (g++ v:8 and higher) is required.          *
//*                 See 'Makefile' for details.                                  *
//*                                                                              *
//* Development Tools:                                                           *
//* ---------------------------------------------------------------------------- *
//*  G++ v:14.3.1 20250523 (C++17)                                               *
//*  Fedora Linux v:41                                                           *
//*  GNOME Terminal v:3.54.4 (GNOME 47)                                          *
//* ---------------------------------------------------------------------------- *
//*                                                                              *
//* The public methods of the TFMan class are listed below.                      *
//* TEST  NAME                                         DESCRIPTION               *
//* ----  -------------------------------------------  ------------------------- *
//*  OK   ~TFMan(void);                                Destructor                *
//*  OK   TFMan(void);                                 Default constructor       *
//*  OK   TFMan(const char* prefix);                   Init. constructor         *
//*  OK   bool createTempFile(gString& tmpPath);       Create file               *
//*  OK   bool deleteTempFile(const gString& tmpPath); Delete file               *
//*  OK   bool clearTempFiles(void);                   Erase container contents  *
//*  OK   const char* getTempPath(void);               Report container subdir   *
//*  OK   bool isActive(void);                         Report whether connected  *
//*  OK   const char* tfmVersion(void);                Report TFMan version      *
//*                                                                              *
//********************************************************************************
//* Copyright Notice:                                                            *
//* 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.                                                           *
//*                                                                              *
//* This program is distributed in the hope that it will be useful, but          *
//* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY   *
//* or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License     *
//* for more details.                                                            *
//*                                                                              *
//* You should have received a copy of the GNU General Public License along      *
//* with this program.  If not, see <http://www.gnu.org/licenses/>.              *
//*                                                                              *
//********************************************************************************
//* Version History:                                                             *
//* v: 0.00.02 26-Dec-2025                                                       *
//*   -- For the 'redirect' option, replace capture of output to a temp file with*
//*      a system pipe which redirects 'stdout' directly to the 'less' utility.  *
//*   -- Clean up the comments and remove the debugging code.                    *
//* v: 0.00.01 22-Dec-2025                                                       *
//*   -- Create a simple demo app (this app) based on the Test_Tfman() method    *
//*      in the AnsiCmdTest.cpp source module of the AnsiCmd class library.      *
//*   -- Documentation update describing functionality.                          *
//*                                                                              *
//********************************************************************************

//****************
//* Header Files *
//****************

using namespace std ;         //* C++ Scope quailfier
#include <unistd.h>           //* UNIX system interface
#include "TFMan.hpp"          //* Temporary file management
#include "gString.hpp"        //* Text analysis and formatting

//*******************
//* Constant Values *
//*******************
enum tfOption : short
{
   tfTEST_BASIC,              //* Perform basic tests
   tfTEST_FULL,               //* Perform full test suite
   tfHELP,                    //* Display application invocation help
   tfVERSION,                 //* Report the application, TFMan and gString versions
   tfOPTIONS                  //* If no option specified
} ;

static const int ZERO = 0 ;
typedef struct tm Tm ;
//* Class for reporting the system local date/time.                            *
//* ---------------------------------------------------------------------------*
#define DECODE_TZ (1)   // if non-zero, decode time-zone fields of localTime
const int64_t maxETIME32 = 0x07FFFFFFF,      // largest 32-bit time_t
              minETIME32 = -maxETIME32 - 1 ; // smallest 32-bit time_t
const short ltFMT_LEN = 16 ;                 // length of time string buffers

//* This is a simplified version of the localTime class used  *
//* in many of the author's other projects.                   *
//* Note that the struct tm equivalents are SIGNED int values.*
class localTime
{
   public:
   time_t   epoch ;        //* system epoch time (seconds since the epoch: 01-Jan-1970)
   uint16_t day ;          //* Day of the week (0 == Sun, ..., 6 == Sat)
   uint16_t date ;         //* Day of the month (1-[28|29|30|31])
   uint16_t month ;        //* Month of the year (1 == Jan, ... 12 == Dec)
   uint16_t year ;         //* Year (four digit year)
   uint16_t hours ;        //* Time of day, hour (0-23)
   uint16_t minutes ;      //* Time of day, minutes (0-59)
   uint16_t seconds ;      //* Time of day, seconds (0-59)
   uint16_t julian ;       //* Completed days since beginning of year
} ;


//**************
//* Local data *
//**************

static const char *tfmtVersion = "0.0.02" ;  // TFMTest version
static const char *crYears = "2025-2026" ;   // copyright years
static const char *fnamePrefix = "SAM_" ;    // subdirectory name prefix token

static TFMan *tfm = NULL ;          //* Pointer to TFMan object
static bool verbose = false ;       //* Set if verbose output specified
static bool redirect = false ;      //* Set if redirection to 'less' specified
//* If redirection specified, file handle which pipes output   *
//* stream, 'stdout' to an external application, 'less'.       *
static FILE *out_redir = NULL ;


//********************
//* Local prototypes *
//********************
static bool basicTests ( bool tfmDelete = true ) ;
static bool comprehensiveTests ( void ) ;
static void write ( const gString& txt, bool nl = true ) ;
static void write ( const wchar_t* txt, bool nl = true ) ;
//static void write ( const char* txt, bool nl = true ) ; // (not used by this application)
static bool reportStats ( const char* fspec, FileStats& fstats ) ;
static short reportSubdir ( gString& gsFNames ) ;
static void formatTimestamp ( time_t epoch_time, gString& gsTStamp ) ;
static tfOption commlineArgs ( int argc, const char** argv, const char** argenv) ;
static void reportTitle ( gString& gsTitle ) ;
static void reportHelp ( void ) ;
static void reportVersion ( short msgWidth ) ;


//*************************
//*        main           *
//*************************
//********************************************************************************
//* Program entry point.                                                         *
//*                                                                              *
//* Input  : argc   : number of arguments                                        *
//*          argv   : text of arguments                                          *
//*          argenv : text of environment variables (ignored in this app)        *
//*                                                                              *
//* Returns: zero                                                                *
//********************************************************************************

int main ( int argc, char* argv[], char* argenv[] )
{
   gString gs ;
   tfOption tfOpt ;
   bool status = false ;

   //* Say Hello *
   reportTitle ( gs ) ;
   system ( "clear" ) ;
   write ( gs, false ) ;   // (this write bypasses redirection)

   //* Capture and parsing of command-line arguments *
   tfOpt = commlineArgs ( argc, (const char**)argv, (const char**)argenv ) ;

   //* Create an instance of the TFMan class *
   tfm = new TFMan ( fnamePrefix ) ;

   //* If specified, redirect output through a pipe connected  *
   //* to the 'less'utility. NOTE: If pipe fails, then output  *
   //* will be directly to stdout.                             *
   if ( redirect )
   {
      out_redir = popen ( "less -c", "w" ) ;
      if ( out_redir != NULL )
         write ( gs, false ) ;            // write the header
      else                                // if popen() fails, write to stdout instead.
         redirect = false ;
      if ( ! redirect )                   // report that redirection failed
      {
         gs = "Error, Unable to redirect test results. Writing to 'stdout'.\n" ;
         gs.padCols( (gs.gscols() * 2) + 2, L'-' ) ;
         write ( gs ) ;
      }
   }

   //**********************************
   //* Launch the specified operation *
   //**********************************
   switch ( tfOpt )
   {
      case tfTEST_BASIC:            //* Perform basic tests
         status = basicTests () ;
         break ;

      case tfTEST_FULL:             //* Perform full range of tests
         status = comprehensiveTests () ;
         break ;

      case tfHELP:                  //* Display application invocation help
         reportHelp () ;
         break ;

      case tfVERSION:               //* Report the application, TFMan and gString versions
         reportVersion ( (gs.gscols()) / 2 ) ;
         break ;

      case tfOPTIONS:               //* If no option specified
      default:
         write ( L"  Type: \"tfmtest --help\"" ) ;
   }

   if ( tfm != NULL )                     // release the dynamic allocation
      delete tfm ;

   if ( (tfOpt == tfTEST_BASIC) || (tfOpt == tfTEST_FULL) )
   {
      if ( status )
         write ( L" -- All tests completed successfully!\n"
                 L"    Bye-bye!\n" ) ;
      else
         write ( L" -- One or more tests failed! Verify location of system temp file directory.\n"
                 L"    Bye-bye!\n" ) ;

      //* If output was redirected, then close the pipe, *
      //* releasing control of 'stdout'.                 *
      if ( out_redir != NULL )
      { pclose ( out_redir ) ; out_redir = NULL ; }
   }
   exit ( ZERO ) ;                        // Exit, returning control to the shell.

}  //* End main() *

//*************************
//*      basicTests       *
//*************************
//********************************************************************************
//* Perform some basic tests of the TFMan class:                                 *
//*  1) Test the connection: container subdirectory created and accessible.      *
//*  2) Get filespec of container subdirectory.                                  *
//*  3) Create an empty temporary file.                                          *
//*  4a) Open the target file,                                                   *
//*  4b) Write some sample data and close the file.                              *
//*  4c) Open the target file and read the text data, then close the file        *
//*  5) Delete the temporary file and verify deletion.                           *
//*  -- and optionally --                                                        *
//*  6) Delete the TFMan object, and verify that container subdirectory          *
//*     and its contents have been deleted.                                      *
//*                                                                              *
//* Input  : (optional, default: true)                                           *
//*          if 'true',  delete the TFMan object at end of test                  *
//*          if 'false', do not delete the TFMan object                          *
//*                                                                              *
//* Returns: 'true' if all tests successful, else 'false'                        *
//********************************************************************************

static bool basicTests ( bool tfmDelete )
{
   const char *txtData = "This sentence written to the temporary file." ;

   gString gs, gs2, gsTStamp, gsSubspec, gst ;
   char ibuff[128] ;                      // file data input buffer
   FileStats fstats ;                     // file statistics
   int  tnum = 1 ;                        // test number
   bool status ;                          // status of test
   bool success = false ;                 // return value

   //* Verify that communications have been established *
   //* Note: We "know" that caller has created the      *
   //* TFMan object, but be safe.                       *
   if ( (tfm != NULL) && (tfm->isActive ()) )
   {
      gs.compose( "%02d) Connection is active. version:%s", 
                  &tnum, (tfm->tfmVersion ()) ) ; ++tnum ;
      write ( gs ) ;

      //* Report the filespec of container subdirectory *
      gsSubspec = tfm->getTempPath() ;
      gs.compose( "%02d) Container Subdir : '%s'", &tnum, gsSubspec.ustr() ) ; ++tnum ;
      if ( verbose )
         gs.append( " -- Specified subdirectory name prefix: \"%s\"", fnamePrefix ) ;
      write ( gs ) ;

      //* Create a temporary file *
      if ( (status = tfm->createTempFile ( gst )) )
      {
         gs.compose( "%02d) Temp file created: '%s'", &tnum, gst.ustr() ) ; ++tnum ;
         write ( gs ) ;

         //* Open the file for writing. (this proves that it exists) *
         ofstream ofs ( gst.ustr(), ofstream::out | ofstream::trunc ) ;
         if ( ofs.is_open() )
         {
            gs.compose( "%02d) File creation verified.", &tnum ) ;
            //* For verbose output, get file stats *
            if ( verbose )
            {
               reportStats ( gst.ustr(), fstats ) ;
               formatTimestamp ( fstats.stx_mtime.tv_sec, gsTStamp ) ;
               gs2.formatInt( fstats.stx_size, 6, true ) ;
               gs.append( " -- %S (%S bytes)", gsTStamp.gstr(), gs2.gstr() ) ;
            }
            write ( gs ) ;

            ofs << txtData << endl ;
            gs.compose( "    %02da) Data Written: '%s'", &tnum, txtData ) ;
            if ( verbose )
               gs.append( " -- File Closed." ) ;
            write ( gs ) ;
            ofs.close() ;

            //* Open the file for reading and extract the line of text. *
            ifstream ifs ( gst.ustr(), ifstream::in ) ;
            if ( ifs.is_open() )
            {
               ifs.getline( ibuff, 128 ) ;
               ifs.close() ;

               gs.compose( "    %02db) Data Read   : '%s'", &tnum, ibuff ) ;
               if ( verbose )
               {
                  gs.append( " -- File Closed." ) ;
                  gs2 = ibuff ;
                  if ( (gs2.compare( txtData )) == ZERO )
                     gs.append( "\n    %02dc) Data comparison successful.", &tnum ) ;
                  else
                     gs.append( "\n    %02dc) Retrieved data do not match source!" ) ;
                  reportStats ( gst.ustr(), fstats ) ;
                  formatTimestamp ( fstats.stx_mtime.tv_sec, gsTStamp ) ;
                  gs2.formatInt( fstats.stx_size, 6, true ) ;
                  gs.append( " -- %S (%S bytes)", gsTStamp.gstr(), gs2.gstr() ) ;
               }
               write ( gs ) ;
            }
            else
               write ( L"Unable to open temp file for read." ) ;
            ++tnum ;

            //* Delete the temporary file *
            gs.compose( "%02d) Deleting temp file: '%S'", &tnum, gst.gstr() ) ; ++tnum ;
            if ( (status = tfm->deleteTempFile ( gst )) )
            {
               if ( !(reportStats ( gst.ustr(), fstats )) )
                  gs.append( " -- OK!" ) ;
               else
                  gs.append( " -- ERROR!" ) ;
            }
            else
               gs.append( " -- ERROR!" ) ;
            write ( gs ) ;
         }
         else
            write ( L"Unable to open temp file for writing." ) ;

         //* Delete the TFMan objects.                      *
         //* The destructor should delete the application's *
         //* tempfile container subdirectory and its        *
         //* contents (if any).                             *
         if ( tfmDelete )
         {
            gs.compose( "%02d) Deleting TFMan Object", &tnum ) ;

            delete tfm ;
            tfm = NULL ;

            //* Verify that container subdirectory was deleted *
            if ( !(reportStats ( gsSubspec.ustr(), fstats )) )
            {
               gs.append( " -- OK!" ) ;
               if ( verbose )
                  gs.append( "\n    %02da) Container Subdir successfully deleted.", &tnum ) ;
               success = true ;  // all tests complete
            }
            else
            {
               gs.append( " -- ERROR!" ) ;
               if ( verbose )
                  gs.append( "\n    %02da) Container Subdir NOT deleted.", &tnum ) ;
            }
            write ( gs ) ;
         }

         // If final test has been skipped, declare *
         //* success for the first five tests.      *
         else if ( tnum == 6 )
            success = true ;
      }
      else
         write ( L"Temp-file creation failed." ) ;
   }
   else
      write ( L"Connection to temporary-file manager failed." ) ;

   return success ;

}  //* End basicTests() *

//*************************
//*  comprehensiveTests   *
//*************************
//********************************************************************************
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if all tests successful, else 'false'                        *
//********************************************************************************

static bool comprehensiveTests ( void )
{
   const short TFILES = 3 ;
   gString gs, gs2, gs3, gsSubspec, gsTStamp, gst ;
   char tmpspec[TFILES][PATH_MAX] = { { "" }, { "" }, { "" } } ;
   FileStats fstats ;                     // file statistics
   int  tnum = 6 ;                        // test number
   bool status = false ;

   if ( (basicTests ( false )) )
   {
      gsSubspec = tfm->getTempPath () ;   // save filespec of container subdirectory

      //* Create three(3) temp files, then delete them en mas.*
      short cCnt = ZERO ;
      for ( ; cCnt < TFILES ; )
      {
         if ( (tfm->createTempFile ( gst )) )
            gst.copy( tmpspec[cCnt++], PATH_MAX ) ;
      }
      gs.compose( "%02d) Group of %02hd files created in container subdirectory:\n", 
                  &tnum, &cCnt ) ;

      wchar_t tfchar = L'a' ;
      for ( short f = ZERO ; f < TFILES ; ++f )
      {
         if ( *tmpspec[f] != '\0' )
         {
            gs.append( "    %02d%C) %s", &tnum, &tfchar, tmpspec[f] ) ;
            //* For verbose output, include timestamp and file size *
            if ( verbose )
            {
               if ( (reportStats ( tmpspec[f], fstats )) )
               {
                  formatTimestamp ( fstats.stx_mtime.tv_sec, gsTStamp ) ;
                  gs2.formatInt( fstats.stx_size, 6, true ) ;
                  gs.append( " -- %S (%S bytes)", gsTStamp.gstr(), gs2.gstr() ) ;
               }
               else
                  gs.append( " -- stat request failed." ) ;
            }
            gs.append( L'\n' ) ;
         }
         else
            gs.append( "    %02d%C) temp file not created\n", &tnum, &tfchar ) ;
         ++tfchar ;
      }
      write ( gs, false ) ;
      ++tnum ;

      //* Delete all files in the container subdirectory, *
      //* then verify that the files were deleted.        *
      short fCnt ;
      bool cleared = tfm->clearTempFiles () ;
      gs.compose( "%02d) Clear the container subdirectory.", &tnum ) ;
      fCnt = reportSubdir ( gs2 ) ;
      if ( cleared && (fCnt == ZERO) )
         gs.append( " -- OK!" ) ;
      else
      {
         gs.append( " -- ERROR! %hd file(s) not deleted.", &fCnt ) ;
         if ( verbose )
         {
            int sindx = ZERO ;
            tfchar = L'a' ;
            gs.append( L'\n' ) ;
            for ( short f = ZERO ; f < fCnt ; ++f )
            {
               gs3 = &gs2.gstr()[sindx] ;
               sindx = gs2.after( L'\n', sindx ) ;
               gs3.limitChars( (gs3.find( L'\n' )) ) ;
               gs.append( "    %02d%C) %S/%S", 
                          &tnum, &tfchar, gsSubspec.gstr(), gs3.gstr() ) ;
               if ( f < (fCnt - 1) ) { gs.append( L'\n' ) ; }
               ++tfchar ;
            }
         }
      }
      write ( gs ) ;
      ++tnum ;

      //* Delete the TFMan object. The destructor should delete *
      //* the application's tempfile container subdirectory and *
      //* its contents (if any). Verify that subdir was deleted.*
      gs.compose( "%02d) Deleting TFMan Object", &tnum ) ;
      delete tfm ;
      tfm = NULL ;
      if ( !(reportStats ( gsSubspec.ustr(), fstats )) )
      {
         gs.append( " -- OK!" ) ;
         if ( verbose )
            gs.append( "\n    %02da) %S successfully deleted.", &tnum, gsSubspec.gstr() ) ;
      }
      else
      {
         gs.append( " -- ERROR!" ) ;
         if ( verbose )
            gs.append( "\n    %02da) %S - delete failed.", &tnum, gsSubspec.gstr() ) ;
      }
      write ( gs ) ;
      ++tnum ;

      //* Create a TFMan object using the default constructor.       *
      //* This will create a new container subdirectory.             *
      //* Create a temporary file within the container subdirectory, *
      bool tfactive = false ;
      if ( true )
      {
         TFMan tfAuto ;
         gs.compose( "%02d) Create a TFMan object as an automatic variable.", &tnum ) ;
         if ( (tfactive = tfAuto.isActive ()) )
         {
            gsSubspec = tfAuto.getTempPath () ;
            gs.append( " -- OK!\n"
                       "    %S", gsSubspec.gstr() ) ;
            if ( verbose )                // append stat data
            {
               reportStats( gsSubspec.ustr(), fstats ) ;
               formatTimestamp ( fstats.stx_mtime.tv_sec, gsTStamp ) ;
               gs2.formatInt( fstats.stx_size, 6, true ) ;
               gs.append( " -- %S (%S bytes)", gsTStamp.gstr(), gs2.gstr() ) ;
            }
         }
         else
            gs.append( " -- ERROR! Unable to create object." ) ;
         write ( gs ) ;
         ++tnum ;

         if ( tfactive )
         {
            gs.compose( "%02d) Create a temporary file within the container subdirectory.", &tnum ) ;
            if ( (tfAuto.createTempFile ( gst )) )
            {
               gs.append( " -- OK!" ) ;
               if ( verbose )
               {
                  reportStats( gst.ustr(), fstats ) ;
                  formatTimestamp ( fstats.stx_mtime.tv_sec, gsTStamp ) ;
                  gs2.formatInt( fstats.stx_size, 6, true ) ;
                  gs.append( "\n    %02da) %S -- %S (%S bytes)", 
                             &tnum, gst.gstr(), gsTStamp.gstr(), gs2.gstr() ) ;
               }
            }
            else
               gs.append( " -- ERROR! - Unable to create new temp file." ) ;
            write ( gs ) ;
            ++tnum ;
         }
      }
      //* The TFMan destructor is called implicitly when the object  *
      //* goes out-of-scope. The destructor should delete the        *
      //* container subdirectory and its contents.                   *
      if ( tfactive )
      {
         bool exists ;
         exists = reportStats ( gsSubspec.ustr(), fstats ) ;
         gs.compose( "%02d) Object is out-of-scope. Verify container and contents deleted. -- %s", 
                     &tnum, (exists ? "ERROR!" : "OK!") ) ;
         write ( gs ) ;
         status = exists ? false : true ;
      }
   }
   else
      write ( L"**Basic Tests failed. Test sequence aborted." ) ;

   return status ;

}  //* End comprehensiveTests() *

//*************************
//*         write         *
//*************************
//********************************************************************************
//* All test results are routed through this function.                           *
//*  1) 'redirect' flag reset: write to stdout                                   *
//*  2) 'redirect' flag set  : pipe to the 'less' utility                        *
//*                                                                              *
//* Input  : txt : text to be written                                            *
//*                a) gString object by reference                                *
//*                b) pointer to wchar_t (wide character) string                 *
//*                c) pointer to UTF-8 (char) string                             *
//*          nl  : (optional, default: true)                                     *
//*                if 'true',  append a newline character to output              *
//*                if 'false', flush the buffer, but do not append newline       *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void write ( const gString& txt, bool nl )
{
   if ( redirect && (out_redir != NULL) )
   {
      gString gs( txt ) ;
      if ( nl ) { gs.append( L'\n' ) ; }
      fprintf ( out_redir, gs.ustr() ) ;
   }
   else
   {
      wcout << txt ;
      if ( nl ) { wcout << L"\n" ; }
      wcout.flush() ;
   }

}  //* End write() *

static void write ( const wchar_t* txt, bool nl )
{
   gString gs( txt ) ;
   write ( gs, nl ) ;
}  //* End write() *
//static void write ( const char* txt, bool nl ) // (not used by this application)
//{
//   gString gs( txt ) ;
//   write ( gs, nl ) ;
//}  //* End write() *

//*************************
//*     reportSubdir      *
//*************************
//********************************************************************************
//* Scan the container subdirectory and report all files found there.            *
//*                                                                              *
//* Input  : gsFNames : receives a newline delimited list of files in the        *
//*                     container subdirectory within the system temp directory. *
//*                                                                              *
//* Returns: 'true' if successful, else 'false'                                  *
//********************************************************************************

static short reportSubdir ( gString& gsFNames )
{
   const char PCHAR = '.' ;               // full-stop character
   DIR* dirPtr = NULL ;                   // pointer to directory object
   deStats* destat ;                      // directory-entry record
   gString subDir ;                       // filespec of container subdirectory
   short fCnt = ZERO ;                    // return value
   bool trgFile ;                         // set if potential target file
   subDir = tfm->getTempPath () ;         // get target filespec
   gsFNames.clear() ;                     // initialize buffer

   if ( (dirPtr = opendir ( subDir.ustr() )) != NULL )
   {
      //* Read each item in the directory *
      while ( (destat = readdir64 ( dirPtr )) != NULL )
      {
         //* Do not include 'current dir' and 'parent dir' names. *
         trgFile = true ;
         if ( destat->d_name[0] == PCHAR )
         {
            if ( (destat->d_name[1] == '\0') ||    // ('this' directory)
                 ((destat->d_name[1] == PCHAR) &&  // ('parent' directory)
                  (destat->d_name[2] == '\0')) )
            { trgFile = false ; }
         }
         if ( (trgFile != false) && (destat->d_type != DT_DIR) )
         {
            gsFNames.append( "%s\n", destat->d_name ) ;
            ++fCnt ;
         }
      }
      closedir ( dirPtr ) ;               // Close the directory
   }
   return fCnt ;

}  //* End reportSubdir() *

//*************************
//*      reportStats      *
//*************************
//********************************************************************************
//* Get raw file stats for specified file.                                       *
//*                                                                              *
//* Input  : fspec  : pointer to full filespec (not a relative path)             *
//*                                                                              *
//* Returns: 'true' if successful, else 'false'                                  *
//********************************************************************************

static bool reportStats ( const char* fspec, FileStats& fstats )
{
   bool status = false ;

   status = bool((statx ( sx_dirfd, fspec, sx_flags, sx_mask, &fstats )) == ZERO) ;

   return status ;

}  //* End reportStats() *

//*************************
//*    formatTimestamp    *
//*************************
//********************************************************************************
//* Convert an epoch time value to a human readable canonical timestamp.         *
//*                                                                              *
//* Input  : epoch_time : number of seconds since the epoch (from file stats)    *
//*          gsTStamp   : (by reference) receives the formatted text string      *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void formatTimestamp ( time_t epoch_time, gString& gsTStamp )
{
   const int64_t SECS_PER_HOUR = 0x00E10 ;
   const int64_t SECS_PER_DAY  = SECS_PER_HOUR * 24 ;
   localTime lt ;                         // receives broken-down time
   Tm tm ;                                // Linux time structure

   //* Range check the epoch value *
   if ( epoch_time < SECS_PER_DAY )
      epoch_time = time ( NULL ) ;

   //* Decode the system time *
   if ( (localtime_r ( &epoch_time, &tm )) != NULL )
   {
      lt.julian  = tm.tm_yday ;         // Julian date
      lt.day     = tm.tm_wday ;         // 0 == Sunday ... 6 == Saturday
      lt.date    = tm.tm_mday ;         // today's date
      lt.month   = tm.tm_mon + 1 ;      // month
      lt.year    = tm.tm_year + 1900 ;  // year
      lt.hours   = tm.tm_hour ;         // hour
      lt.minutes = tm.tm_min ;          // minutes
      lt.seconds = tm.tm_sec ;          // seconds

      gsTStamp.compose( "%04hd-%02hd-%02hdT%02hd:%02hd:%02hd",
                        &lt.year, &lt.month, &lt.date,
                        &lt.hours, &lt.minutes, &lt.seconds ) ;
   }
   else        // system call failed (unlikely)
      gsTStamp = "(unknown)" ;

}  //* End formatTimestamp() *

//*************************
//*     commlineArgs      *
//*************************
//********************************************************************************
//* Validate command-line arguments.                                             *
//*                                                                              *
//*                                                                              *
//* Input  : argc   : number of arguments                                        *
//*          argv   : text of arguments                                          *
//*          argenv : text of environment variables (ignored in this app)        *
//*                                                                              *
//* Returns: user option                                                         *
//********************************************************************************

static tfOption commlineArgs ( int argc, const char** argv, const char** argenv)
{
   gString gs ;
   tfOption tfOpt = tfOPTIONS ;           // return value
   bool help = false,
        vers = false ;

   if ( argc > 1 )
   {
      for ( int arg = 1 ; arg < argc ; ++arg )
      {
         gs = argv[arg] ;
         if ( (gs.compare( L"--version", true, 6 )) == ZERO )
            vers = true ;

         else if ( ((gs.compare( L"--help", true, 6 )) == ZERO) ||
                   ((gs.compare( L"-h", true, 2 )) == ZERO) )
            help = true ;

         else if ( (gs.compare( L"--basic", true, 6 )) == ZERO )
            tfOpt = tfTEST_BASIC ;

         else if ( (gs.compare( L"--full", true, 6 )) == ZERO )
            tfOpt = tfTEST_FULL ;

         else if ( (gs.compare( L"--verbose", true, 6 )) == ZERO )
            verbose = true ;

         else if ( (gs.compare( L"--redirect", true, 6 )) == ZERO )
            redirect = true ;

         else
         {
            wcout << "  Unknown option: '" << gs << "' received." << endl ;
            tfOpt = tfOPTIONS ;
            break ;
         }
      }
   }

   //* Validate the specified arguments:           *
   //* a) Either Basic or Full test s/b specified. *
   //* b) Version overrides everything else.       *
   //* c) Help overrides everything except version.*
   //* d) If no arg or invalid arg specified, then *
   //*    a default tfOPTIONS will be returned.    *
   if ( vers )
   { tfOpt = tfVERSION ; verbose = redirect = false ; }
   else if ( help )
   { tfOpt = tfHELP ; verbose = redirect = false ; }

   return tfOpt ;

}  //* End commlineArgs() *

//*************************
//*      reportTitle      *
//*************************
//********************************************************************************
//* Format the application title and return it to caller.                        *
//*                                                                              *
//********************************************************************************

static void reportTitle ( gString& gsTitle )
{
   const char* Title = 
             "TFMTest (tfmtest) Copyright (c) %s The Software Samurai\n" ; 
   gsTitle.compose( Title, crYears) ;
   gsTitle.padCols( gsTitle.gscols() * 2, L'=' ) ;
   gsTitle.append( L'\n' ) ;

}  //* End reportTitle() *

//*************************
//*      reportHelp       *
//*************************
//********************************************************************************
//* Display command line help, then exit.                                        *
//*                                                                              *
//********************************************************************************

static void reportHelp ( void )
{
   const wchar_t* helpMe = 
     L" tfmtest [basic | full] [SUB-OPTIONS]\n"
      "   Test the \"TFMan\" class temporary-file management system.\n"
      " --basic        : Perform basic tests of the tempfile system.\n"
      " --full         : Perform comprehensive tests of the tempfile system.\n"
      " --verbose      : Perform specified test group with verbose output.\n"
      " --redirect     : Redirect output to the 'less' utility.\n"
      " --help (or -h) : Display application help, then exit.\n"
      " --version      : Report application version, then exit.\n" ;

   write( helpMe ) ;

}  //* End reportHelp() *

//*************************
//*     reportVersion     *
//*************************
//********************************************************************************
//* Display version numbers, then exit.                                          *
//*                                                                              *
//********************************************************************************

static void reportVersion ( short msgWidth )
{
   const char* verTemplate =
            "  TFMTest v:%s  TFMan Class v:%s  gString v:%s" ;
   const char* const freeSoftware = 
   "GNU General Public License (GPL) v:3+. This is free software:\n"
   "You are free to modify and/or redistribute it under the terms\n"
   "set out in the license. There is NO WARRANTY, to the extent\n"
   "permitted by law.  <http://gnu.org/licenses/gpl.html>\n" ;

   gString gs, gsdelim ;
   gs.compose( verTemplate, tfmtVersion, 
               tfm->tfmVersion (), gs.Get_gString_Version () ) ;
   gs.padCols( msgWidth ) ;
   gsdelim.padCols( msgWidth, L'-' ) ;
   gs.append( "\n%S\n%s%S\n", gsdelim.gstr(), freeSoftware,gsdelim.gstr() ) ;
   write ( gs ) ;

}  //* End reportVersion() *

