/*===========================================================================
 *
 * File:	GOutput.CPP
 * Author:	Dave Humphrey (uesp@m0use.net)
 * Created On:	Wednesday, March 28, 2001
 *
 * Implements the CGenOutput class for reading/handling files that contain
 * the format for outputing general data of some sort in the format of 
 * printf() like % codes.
 *
 *=========================================================================*/

	/* Include Files */
#include "goutput.h"
#include "dl_file.h"
#include <ctype.h>


/*===========================================================================
 *
 * Begin Local Variable Definitions
 *
 *=========================================================================*/
  DEFINE_FILE();
/*===========================================================================
 *		End of Local Variable Definitions
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Default Constructor
 *
 *=========================================================================*/
CGenOutput::CGenOutput () {
  //DEFINE_FUNCTION("CGenOutput::CGenOutput()");
  pFileBuffer = NULL;
  pDefaultBuffer = NULL;
  UserFunction = NULL;
 }
/*===========================================================================
 *		End of Class CGenOutput Constructor
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Constructor - CGenOutput(pBuffer);
 *
 *=========================================================================*/
CGenOutput::CGenOutput (const char* pBuffer) {
  //DEFINE_FUNCTION("CGenOutput::CGenOutput()");
  pFileBuffer = NULL;
  pDefaultBuffer = CreateString(pBuffer);
  UserFunction = NULL;
 }
/*===========================================================================
 *		End of Class CGenOutput Constructor
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Constructor - CGenOutput(pFunction);
 *
 *=========================================================================*/
CGenOutput::CGenOutput (PUSER_OUTPUT_FUNC pFunction) {
  //DEFINE_FUNCTION("CGenOutput::CGenOutput()");
  pFileBuffer = NULL;
  pDefaultBuffer = NULL;
  UserFunction = pFunction;
 }
/*===========================================================================
 *		End of Class CGenOutput Constructor
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Constructor - CGenOutput(pBuffer, pFunction);
 *
 *=========================================================================*/
CGenOutput::CGenOutput (const char* pBuffer, PUSER_OUTPUT_FUNC pFunction) {
  //DEFINE_FUNCTION("CGenOutput::CGenOutput()");
  pFileBuffer = NULL;
  pDefaultBuffer = CreateString(pBuffer);
  UserFunction = pFunction;
 }
/*===========================================================================
 *		End of Class CGenOutput Constructor
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Method - void Destroy (void)
 *
 * Class pseudo-destructor
 *
 *=========================================================================*/
void CGenOutput::Destroy (void) {
  //DEFINE_FUNCTION("CGenOutput::Destroy()");
  DestroyPointer(pDefaultBuffer);
  DestroyPointer(pFileBuffer);
 }
/*===========================================================================
 *		End of Class Method CGenOutput::Destroy()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Method - boolean OutputFile (pOutputFile, pUserData)
 *
 * Outputs the current file buffer, parsing any format fields, to the
 * given file stream.  Returns FALSE on any error.  The user data can
 * be a pointer to anything that the user wishes to pass onto the user
 * defined output procedure.  ASSERTS if given an invalid file handle.
 *
 *=========================================================================*/
boolean CGenOutput::OutputFile (FILE* pOutputFile, void* pUserData) {
  DEFINE_FUNCTION("CGenOutput::OutputFile()");
  char* pParse;
  int   Result;

	/* Ensure valid input */
  ASSERT(pOutputFile != NULL);
  pParse = GetBuffer();

	/* Parse and output the entire buffer */
  while (*pParse != NULL_CHAR) {

		/* Check for the format character */
    if (*pParse == '%') {
      Result = ParseFormat((const char**)&pParse, pOutputFile, pUserData);
      if (!Result) return (FALSE);
     }
		/* Output a single, regular character */
    else {
      Result = fputc(*pParse, pOutputFile);

      if (Result == EOF) {
        ErrorHandler.AddError(ERR_SYSTEM, errno, "Error, failed to write character to file!");
        return (FALSE);
       }

      pParse++;
     }

   }

	/* Flush output and return success */
  fflush(pOutputFile);
  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CGenOutput::OutputFile()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Method - boolean ParseFormat (ppParse, pOutputFile, pUserData);
 *
 * Parses a format field starting with the format character '%',
 * outputing any results to the given stream.  Returns FALSE on any error.
 * Contains a printf() like format specifier of the form:
 *
 *	%[Width][.Precision][Type]
 *
 * [Width] is an optional field which can be composed of the numbers
 *	0 thru 9.
 * [.Precision] is optional, starting with the decimal point, and is again
 *	composed of numbers 0 to 9.
 * [Type] is not optional and is a single character, case sensitive.
 *
 * The format fields are parsed and on success, passed onto the custom
 * user output function where the exact use of the fields is entirely
 * up to the user.  The pUserData can be a pointer to any data that the
 * user wants passed onto the user supplied output function.  The presence,
 * or absence, of the various format fields is given by the mask member
 * of the format structure passed onto the user output function.
 *
 * ASSERTs if given any invalid input (except for the user data).
 *
 *=========================================================================*/
boolean CGenOutput::ParseFormat (const char** ppParse, FILE* pOutputFile, void* pUserData) {
  DEFINE_FUNCTION("CGenOutput::ParseFormat()");
  COutputFormat Format = { 0, 0, 0, 0 };

	/* Ensure valid input */
  ASSERT(*ppParse != NULL && **ppParse != NULL && pOutputFile != NULL);
  ASSERT(**ppParse == '%');

	/* Skip the '%' character */
  (*ppParse)++;

	/* Parse out the width/precision fields, if any */
  ParseFormatWidth(ppParse, Format);
  ParseFormatPrecision(ppParse, Format);

	/* Only the format specifying character should be left */
  if (**ppParse == NULL_CHAR) {
    ErrorHandler.AddError(ERR_BADINPUT, "Invalid output format string received, no type character found!");
    return (FALSE);
   }

  Format.Mask |= OUTPUT_MASK_TYPE;
  Format.Type = **ppParse;
  *ppParse += 1;

	/* Call the user supplied format output function, if any */
  if (UserFunction != NULL) return UserFunction(Format, pOutputFile, pUserData);
  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CGenOutput::ParseFormat()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Method - void ParseFormatWidth (ppParse, Format);
 *
 * Parses the format width, if present, from the given buffer position.
 * Protected class method.
 *
 *=========================================================================*/
void CGenOutput::ParseFormatWidth (const char** ppParse, COutputFormat& Format) {
  //DEFINE_FUNCTION("CGenOutput::ParseFormatWidth()");

  if (isdigit(**ppParse)) {
    Format.Mask |= OUTPUT_MASK_WIDTH;

    while (**ppParse != NULL_CHAR && isdigit(**ppParse)) {
      Format.Width *= 10;
      Format.Width += (int) (**ppParse - '0');
      *ppParse += 1;
     }
   }

 }
/*===========================================================================
 *		End of Class Method CGenOutput::ParseFormatWidth()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CGenOutput Method - void ParseFormatPrecision (ppParse, Format);
 *
 * Parses the format precision, if present, from the given buffer position.
 * Protected class method.
 *
 *=========================================================================*/
void CGenOutput::ParseFormatPrecision (const char** ppParse, COutputFormat& Format) {
  //DEFINE_FUNCTION("CGenOutput::ParseFormatPrecision()");
	
  if (**ppParse == '.') {
    *ppParse += 1;
    Format.Mask |= OUTPUT_MASK_PRECISION; 

    while (**ppParse != NULL_CHAR && isdigit(**ppParse)) {
      Format.Precision *= 10;
      Format.Precision += (int) (**ppParse - '0');
      *ppParse += 1;
     }
   }

 }
/*===========================================================================
 *		End of Class Method CGenOutput::ParseFormatPrecision()
 *=========================================================================*/

 
/*============================================================================
 *
 * Class CGenOutput Method - boolean ReadFile (pFilename);
 *
 * Attempts to read in the specified file into the memory for later use.
 * Returns FALSE on any error (file-not-found, etc..).
 *
 *==========================================================================*/
boolean CGenOutput::ReadFile (const char* pFilename) {
  DEFINE_FUNCTION("CGenOutput::ReadFile()");
  size_t  BytesRead;
  boolean Result;

	/* Ensure valid input */
  if (pFilename == NULL) return (FALSE);
  DestroyArrayPointer(pFileBuffer);

	/* Allocate a new file buffer and read file */
  Result = ::ReadFile(&pFileBuffer, BytesRead, pFilename, FILE_BINARY);
  return (Result);
 }
/*============================================================================
 *		End of Class Method CGenOutput::ReadFile()
 *==========================================================================*/



/*===========================================================================
 *
 * Begin Module Test Routines
 *
 * Debug build only functions for testing this module.
 *
 *=========================================================================*/
#if defined(_DEBUG)


/*===========================================================================
 *
 * Function - boolean Test_FormatFunc (Format, pOutputFile, pUserData);
 *
 * Test user output function.
 *
 *=========================================================================*/
boolean Test_FormatFunc (const COutputFormat& Format, FILE* pOutputFile, void* pUserData) {
  DEFINE_FUNCTION("Test_FormatFunc()");
  SystemLog.Printf ("Format (Mask=%d) (Width=%d) (Precision=%d) (Type=%c)", Format.Mask, Format.Width, Format.Precision, Format.Type);
  SystemLog.Printf ("UserData (0x%p) %s", pUserData, (char*)pUserData);
  return (TRUE);
 }
/*===========================================================================
 *		End of Function Test_FormatFunc()
 *=========================================================================*/



/*===========================================================================
 *
 * Function - void Test_goutput (void);
 *
 * Test the CGenOutput class.
 *	1. Test initialization of class
 *	2. Test the SetDefaultBuffer() method
 *	3. Tests the SetUserFunction() method
 *	4. Tests the ReadFile() method
 *	5. Test the OutputFile() method
 *
 *=========================================================================*/
void Test_goutput (void) {
  DEFINE_FUNCTION("Test_goutput()");
  CGenOutput TestOutput1("This is a test buffer");
  CGenOutput TestOutput2;
  CLogFile   TestLog("goutput.log");

  SystemLog.Printf (stdout, "============== Testing the CGenOutput class ===============");

	/* Ensure the output buffers were properly initialized */
  ASSERT(strcmp(TestOutput1.GetBuffer(), "This is a test buffer") == 0);
  ASSERT(strcmp(TestOutput2.GetBuffer(), "") == 0);
	
	/* Test the SetDefaultBuffer() method */
  TestOutput1.SetDefaultBuffer(NULL);  
  TestOutput2.SetDefaultBuffer("");  
  ASSERT(strcmp(TestOutput1.GetBuffer(), "") == 0);
  ASSERT(strcmp(TestOutput2.GetBuffer(), "") == 0);
  TestOutput2.SetDefaultBuffer("123456");  
  ASSERT(strcmp(TestOutput2.GetBuffer(), "123456") == 0);
  TestOutput2.Destroy();
  ASSERT(strcmp(TestOutput2.GetBuffer(), "") == 0);

	/* Test the SetUserFunction() method */
  TestOutput1.SetUserFunction(Test_FormatFunc);
  TestOutput2.SetUserFunction(NULL);

	/* Test the ReadFile() method */
  ASSERT(TestOutput1.ReadFile("test1.out") == TRUE);
  ASSERT(TestOutput2.ReadFile("nofile.123") == FALSE);

	/* Test the OutputFile() method */
  TestLog.Printf("TestOutput1...file buffer");
  ASSERT(TestOutput1.OutputFile(TestLog.GetFileHandle(), "This is some user data") == TRUE);
  TestLog.Printf("TestOutput2...no buffer");
  ASSERT(TestOutput2.OutputFile(TestLog.GetFileHandle(), "More user data") == TRUE);
  TestLog.Printf("TestOutput2...default buffer");
  TestOutput2.SetDefaultBuffer("1.This is a test\n\t2.Format \"%20.3111d\"");
  ASSERT(TestOutput2.OutputFile(TestLog.GetFileHandle(), "More user data") == TRUE);
 }
/*===========================================================================
 *		End of Function Test_goutput()
 *=========================================================================*/



#endif
/*===========================================================================
 *		End of Module Test Routines
 *=========================================================================*/