// ****************************************************************************
//  Project:        libguytools
// ****************************************************************************
//  Programmer:     Guy Voncken
//                  Police Grand-Ducale
//                  Service de Police Judiciaire
//                  Section Nouvelles Technologies
// ****************************************************************************
//  Module:         Configuration utility
// ****************************************************************************

// Copyright 2008, 2009, 2010, 2011, 2012 Guy Voncken
//
// This file is part of libguytools.
//
// libguytools 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 2 of the License, or
// (at your option) any later version.
//
// libguytools 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 libguytools. If not, see <http://www.gnu.org/licenses/>.

/* ------------------------------ */
/*           Includes             */
/* ------------------------------ */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <limits.h>
#include <string.h>
#include <time.h>
#include <locale.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "tooltypes.h"
#include "toolglobalid.h"
#include "toolconstants.h"
#include "toolerror.h"
#include "toolcfg.h"

/* ------------------------------ */
/*           Constants            */
/* ------------------------------ */

const t_char CHAR_LF  = 0x0A;
const t_char CHAR_CR  = 0x0D;
const t_char CHAR_EOF = 0x1A;

const t_char CHAR_APPEND_LINE = '\\';
const t_char STRING_DELIMITER = '\'';

const t_int MAX_CFG_NESTING_LEVEL = 6; /* maximum number of nested INCLUDE directives */

/* reserved keywords */

static t_pcchar KEYWORD_TABLESTART   = "TABLE";
static t_pcchar KEYWORD_TABLEEND     = "ENDTABLE";
static t_pcchar KEYWORD_SECTIONSTART = "SECTION";
static t_pcchar KEYWORD_SECTIONEND   = "ENDSECTION";
static t_pcchar KEYWORD_INCLUDE      = "INCLUDE";
static t_pcchar KEYWORD_INCLUDE_OPT  = "INCLUDE_OPTIONAL";
static t_pcchar KEYWORD_REMARK       = "REM";
static t_pcchar KEYWORD_DEFINE       = "DEFINE";
static t_pcchar KEYWORD_UNDEFINE     = "UNDEFINE";

// static t_pcchar MINUS_LINE = "------------------------------------------------------------------------------"
//                              "------------------------------------------------------------------------------"
//                              "------------------------------------------------------------------------------";

/* ------------ */
/*  Memory IDs  */
/* ------------ */

static const t_int MemIdCfgBuffer           = MEMID_BASE_TOOL_CFG +  0;
static const t_int MemIdCfgErrorHeader      = MEMID_BASE_TOOL_CFG +  1;
static const t_int MemIdCfgContextStack     = MEMID_BASE_TOOL_CFG +  2;
static const t_int MemIdCfgTemplateHeader   = MEMID_BASE_TOOL_CFG +  3;
static const t_int MemIdCfgHelpBuff         = MEMID_BASE_TOOL_CFG +  4;
static const t_int MemIdCfgHelpString       = MEMID_BASE_TOOL_CFG +  5;
static const t_int MemIdCfgHelpInt          = MEMID_BASE_TOOL_CFG +  6;
static const t_int MemIdCfgHelpTable        = MEMID_BASE_TOOL_CFG +  7;
static const t_int MemIdCfgTableType        = MEMID_BASE_TOOL_CFG +  8;
static const t_int MemIdCfgTableName        = MEMID_BASE_TOOL_CFG +  9;
static const t_int MemIdCfgLogConfiguration = MEMID_BASE_TOOL_CFG + 10;

/* ------------------------------ */
/*  Type & structure definitions  */
/* ------------------------------ */

typedef enum
{                                /*! \brief Command line parameter priority */
   PRIORITY_HIGH,                /*!< Command line parameters have high priority */
   PRIORITY_LOW                  /*!< Command line parameters have low priority  */
} t_Priority;

typedef enum                     /*! \brief States for CFG parser state machine */
{
   CFGSTATE_INCLUDENEWFILE,
   CFGSTATE_INCLUDENEWFILE_OPT,
   CFGSTATE_GETNEWLINE,
   CFGSTATE_CHECKINCLUDE,
   CFGSTATE_CHECKSECTION,
   CFGSTATE_LINEREAD,
   CFGSTATE_COMPLETED,
   CFGSTATE_CHECKEOF,
   CFGSTATE_SEARCHSECTIONEND,
   CFGSTATE_CHECKSECTIONEND
} t_ToolCfgState;


#define MAX_SECTIONNAME_LEN     63
#define MAX_FILENAME_LEN      4095
#define MAX_SECTIONNAMES        16
#define MAX_GLOBALSECTIONNAMES  16

typedef char t_ToolCfgSectionName[MAX_SECTIONNAME_LEN+1];

typedef struct
{
   t_int            BufferLen;
   t_int            ActLineLen;
   t_int            ActLineNr;
   t_pchar         pActLine;
   t_pchar         pBuffer;
   t_char           FileName      [MAX_FILENAME_LEN +1];
   t_ToolCfgSectionName SectionNameArr[MAX_SECTIONNAMES];     // These section names are only valid during the the scanning of one include file
   t_int            SectionNesting;
   t_ToolCfgState       State;
} t_ToolCfgContext, *t_pToolCfgContext;


typedef struct
{
   int       argc;
   char    **argv;

   t_Priority       Priority;
   t_pToolCfgUserLogFn pUserLogFn;
   t_pToolCfgContext   pCfgContextStack;
   t_pToolCfgContext   pActCfgContext;
   t_int            IncludeNestingLevel;
   t_int            MaxIncludeNestingLevel;
   t_char           TempFileName[MAX_FILENAME_LEN+1];
   t_ToolCfgSectionName GlobalSectionNameArr[MAX_GLOBALSECTIONNAMES];     // These section names remain defined during the whole cfg sanning time
} t_ToolCfgLocal;


#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))

/* ------------------------------ */
/*           Variables            */
/* ------------------------------ */

t_ToolCfgLocal  ToolCfgLocal;


/* ------------------------------ */
/*           Functions            */
/* ------------------------------ */

/* -------------------------------------- */
/*  Memory access with / without ToolMem  */
/* -------------------------------------- */

#ifdef TOCFG_COMPILE_FOR_USE_WITHOUT_TOOLBOX
   #define MEM_OPT_NONE 0
   #define MEM_REGISTER_MEMID(MemId) NO_ERROR
#endif

static APIRET ToolCfgMemAlloc (t_pvoid *ppMem, t_uint Size, t_uint Options, t_int MemId)
{
   APIRET rc;

   #ifdef TOCFG_COMPILE_FOR_USE_WITHOUT_TOOLBOX
      rc = Options; // To prevent from compiler warning "unused..."
      rc = MemId;   // To prevent from compiler warning "unused..."
      *ppMem = malloc (Size);
      if (*ppMem == NULL)
           rc = TOOLCFG_ERROR_MALLOC_FAILED;
      else rc = NO_ERROR;
   #else
      rc = ToolMemAlloc (ppMem, Size, Options, MemId);
   #endif

   return rc;
}

static APIRET ToolCfgMemFree (t_pvoid pMem, t_int MemId)
{
   APIRET rc;

   #ifdef TOCFG_COMPILE_FOR_USE_WITHOUT_TOOLBOX
      rc = MemId;   // To prevent from compiler warning "unused..."
      free (pMem);
      rc = NO_ERROR;
   #else
      rc = ToolMemFree (pMem, MemId);
   #endif

   return rc;
}

/* -------------------- */
/*   Error management   */
/* -------------------- */

/* Error return values in this module are simply raised to the calling function. */
/* This way, the module CFG does not depend on any log management.               */

#define CFG_CHK_APP(Fn)                                    \
{                                                          \
   APIRET ec;                                              \
                                                           \
   if ((ec=Fn) != NO_ERROR)                                \
   {                                                       \
      if (ec != TOOLCFG_ERROR_CONFIG_ERROR)                \
         (void) ToolCfgLogEntry(__FFL__, "Error %d.", ec); \
      return ec;                                           \
   }                                                       \
}

#define CFG_CHK_FPRINTF(Fn)            \
{                                      \
   if ((Fn) < 1)                       \
      return TOOLCFG_ERROR_FPRINTF_FAILED; \
}

static APIRET ToolCfgLogEntry (t_pcchar pFileName, t_pcchar pFunctionName, t_int LineNr, t_pcchar pFormat, ...)
{
//   APIRET rc=1;
   va_list VaList;

   va_start(VaList, pFormat);               /* VaList points to the argument following pFormat */
   if (ToolCfgLocal.pUserLogFn)
      (*ToolCfgLocal.pUserLogFn)(pFileName, pFunctionName, LineNr, pFormat, VaList);
//   if (rc)
//      (void) vfprintf (stderr, pFormat, VaList);
   va_end(VaList);

   return NO_ERROR;
}

static APIRET ToolCfgStdErrorHeader (t_pcchar pActCursor)
{
   t_pToolCfgContext pCfgContext;
   t_pchar       pEndOfLine;
   t_pchar       pBuff;
   t_int          Cursor;
   t_int          LineLen;

   pCfgContext = ToolCfgLocal.pActCfgContext;
   if (pCfgContext->FileName[0] == '\0')
        CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Configuration error in command line. Parameter %d", pCfgContext->ActLineNr))
   else CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Configuration error in file %s line %d"           , &pCfgContext->FileName[0], pCfgContext->ActLineNr))
   if (pCfgContext->pActLine)
   {
      pEndOfLine = pCfgContext->pActLine + pCfgContext->ActLineLen;
      *pEndOfLine = '\0';
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "[%s]", pCfgContext->pActLine))
   }
   if (pActCursor && pCfgContext->pActLine)
   {
      LineLen = (t_int) strlen (pCfgContext->pActLine);
      Cursor = pActCursor - pCfgContext->pActLine;
      Cursor = max (Cursor, 0        );
      Cursor = min (Cursor, LineLen-1);

      CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pBuff, (t_uint)(LineLen+3), MEM_OPT_NONE, MemIdCfgErrorHeader)) /* +3 for [ ] and '\0' */
      (void) sprintf (pBuff, "[%*s^%*s]", Cursor, "", LineLen-Cursor-1, "");
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pBuff))
      CFG_CHK_APP (ToolCfgMemFree (pBuff, MemIdCfgErrorHeader))
   }

   return NO_ERROR;
}

/* ------------------------------------- */
/*           Utility functions           */
/* ------------------------------------- */

static t_int ToolCfgStrCmpNoCase (const char *pStr1, const char *pStr2)
{
   #ifdef _WIN32
      return strcmpi    (pStr1, pStr2);
   #else
      return strcasecmp (pStr1, pStr2);
   #endif
}

static t_int ToolCfgStrNCmpNoCase (const char *pStr1, const char *pStr2, t_int Len)
{
   #ifdef _WIN32
      return strnicmp    (pStr1, pStr2, Len);
   #else
      return strncasecmp (pStr1, pStr2, (size_t)Len);
   #endif
}

static t_pchar ToolCfgStrMaxCpy (t_pchar pDest, t_pcchar pSrc, t_int DestLen)
{
   if (DestLen == 0)
      return pDest;
   pDest[DestLen-1] = '\0';
   return strncpy (pDest, pSrc, DestLen-1);
}

static APIRET ToolCfgCopyName (t_pcchar pSrc, t_pchar pDst, t_int DstLen)
{
   t_int SrcLen;

   if (pSrc == NULL)
      pDst[0] = '\0';
   else
   {
      SrcLen = (t_int)strlen(pSrc);
      if (SrcLen > DstLen)
      {
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "SrcLen: %d   DstLen: %d", SrcLen, DstLen))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "SrcString: %50.50s", pSrc))

         return TOOLCFG_ERROR_NAME_TOO_LONG;
      }
      else
         (void) strcpy (pDst, pSrc);
   }

   return NO_ERROR;
}

static APIRET ToolCfgFindFirstWord (t_pcchar pActLine, const char **ppFirstWord, t_int LineLen, t_pint pRemLineLen)
{
   for ( *ppFirstWord = pActLine;
       ((**ppFirstWord == ' ') || (**ppFirstWord == '\t')) && (LineLen > 0);
       ( *ppFirstWord)++, LineLen--)
   {
   }
   if ((LineLen <= 0) || (**ppFirstWord == CHAR_LF) || (**ppFirstWord == CHAR_CR))
      *ppFirstWord = NULL;

   *pRemLineLen = LineLen;

   return NO_ERROR;
}

static APIRET ToolCfgCompareKeyWord (t_pcchar pTableKeyWord, t_pchar pCheckKeyWord, t_int LineLen, t_pint pEq, t_pchar *ppNextChar)
{
   t_pchar  pNextChar;
   t_int     TableKeyLen;
   t_int     CheckKeyLen;
   t_pchar  pCh;

   pNextChar = NULL;
   *pEq      = FALSE;

   TableKeyLen = (t_int) strlen (pTableKeyWord);
   if (pCheckKeyWord && (LineLen >= TableKeyLen))
   {
      CheckKeyLen = 0;
      for (pCh = pCheckKeyWord;
           (*pCh != ' ') && (*pCh != CHAR_CR) &&
           (*pCh != '=') && (*pCh != CHAR_LF) && (*pCh != '\t') && (*pCh != '\0');
           pCh++)
         CheckKeyLen++;

      if (CheckKeyLen == TableKeyLen)
      {
         if (ToolCfgStrNCmpNoCase (pTableKeyWord, pCheckKeyWord, CheckKeyLen) == 0)
         {
            pNextChar = &pCheckKeyWord[CheckKeyLen];
            *pEq = TRUE;
         }
      }
   }

   if (ppNextChar)
      *ppNextChar = pNextChar;

   return NO_ERROR;
}

static APIRET ToolCfgGetParamLen (t_pcchar pParam, t_pint pParamLen)
{
   t_int LoopEnd;

   /* count param len */
   *pParamLen = 0;

   LoopEnd = FALSE;
   if (*pParam == STRING_DELIMITER)
   {
      pParam++;
      (*pParamLen)++;
      while (!LoopEnd)
      {
         if (*pParam == '\0')
            LoopEnd = TRUE;
         else if (*pParam == STRING_DELIMITER)
         {
            if (*(pParam+1) == STRING_DELIMITER)
            {
               pParam++;
               (*pParamLen)++;
            }
            else
            {
               LoopEnd = TRUE;
            }
         }
         if (!LoopEnd)
         {
            pParam++;
            (*pParamLen)++;
         }
      }
   }
   else
   {
      while ((*pParam != ' ') && (*pParam != '\t') && (*pParam != '\0') && (*pParam != CHAR_CR) && (*pParam != CHAR_LF))
      {
         pParam++;
         (*pParamLen)++;
      }
   }

   if (*pParam == STRING_DELIMITER) /* is there a string end delimiter? */
   {
      pParam++;
      (*pParamLen)++;
   }

   /* was there a param? */
//   if ((*pParam == '\0') && (*pParamLen == 0))
   if (*pParamLen == 0)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam-1))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Parameter expected."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   return NO_ERROR;
}

static APIRET ToolCfgCheckIfOnlyOneParam (t_pchar pParam, t_pint pParamLen)
{
   CFG_CHK_APP (ToolCfgGetParamLen (pParam, pParamLen))
   pParam += *pParamLen;

   /* skip trailing spaces  */
   while (((*pParam == ' ') || (*pParam == '\t')) && (*pParam != '\0'))
      pParam++;

   /* is there a second param? */
   if (*pParam != '\0')
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "No further parameter expected."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   return NO_ERROR;
}

static APIRET ToolCfgCheckIfNoParam (t_pchar pCh)
{
   while (((*pCh == ' ') || (*pCh == '\t')) && (*pCh != '\0'))
      pCh++;

   if (*pCh != '\0')
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pCh))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "No further parameter expected."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   return NO_ERROR;
}

/* ----------------------------------------------------------------------------------- */
/*  Help text builder. It is used to help the user enter the correct parameter values  */
/* ----------------------------------------------------------------------------------- */

static APIRET ToolCfgChkStrCpy (t_pchar pDstStr, t_pcchar pSrcStr, t_pint pCumulLen)
{
   if (pDstStr)
      (void) strcpy (pDstStr + *pCumulLen, pSrcStr);
   (*pCumulLen) += (t_int)strlen (pSrcStr);

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelpHMS(t_pToolCfgDataDesc pCfgDataDesc, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   t_pchar pTmp;
   t_int    MinVal;
   t_int    MaxVal;

   MinVal = (t_int)pCfgDataDesc->MinValue;
   MaxVal = (t_int)pCfgDataDesc->MaxValue;
   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp, 256, MEM_OPT_NONE, MemIdCfgHelpInt))
   (void) sprintf (pTmp, "[%02d:%02d:%02d..%02d:%02d:%02d]", MinVal/3600, (MinVal/60)%60, MinVal%60,
                                                             MaxVal/3600, (MaxVal/60)%60, MaxVal%60);
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, pTmp, pHelpBuffLen))
   CFG_CHK_APP (ToolCfgMemFree (pTmp, MemIdCfgHelpInt))

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelpPresence (t_pToolCfgDataDesc /*pCfgDataDesc*/, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   t_pcchar pTmp;

   pTmp = "Optional";
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, pTmp, pHelpBuffLen))

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelpInteger (t_pToolCfgDataDesc pCfgDataDesc, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   t_pchar    pTmp;

   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp, 256, MEM_OPT_NONE, MemIdCfgHelpInt))
   (void) sprintf (pTmp, "[%d..%d] | [0x%X..0x%X]",
            (t_int)pCfgDataDesc->MinValue, (t_int)pCfgDataDesc->MaxValue,
            (t_int)pCfgDataDesc->MinValue, (t_int)pCfgDataDesc->MaxValue);
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, pTmp, pHelpBuffLen))
   CFG_CHK_APP (ToolCfgMemFree (pTmp, MemIdCfgHelpInt))

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelpDouble (t_pToolCfgDataDesc pCfgDataDesc, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   t_pchar    pTmp;

   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp, 256, MEM_OPT_NONE, MemIdCfgHelpInt))
   (void) sprintf (pTmp, "[%G..%G]", pCfgDataDesc->MinValue, pCfgDataDesc->MaxValue);
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, pTmp, pHelpBuffLen))
   CFG_CHK_APP (ToolCfgMemFree (pTmp, MemIdCfgHelpInt))

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelpString (t_pToolCfgDataDesc pCfgDataDesc, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   t_pchar    pTmp;

   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp, 256, MEM_OPT_NONE, MemIdCfgHelpString))
   (void) sprintf (pTmp, "'<MaxStringLength=%d>'", (t_int)pCfgDataDesc->DestLen);
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, pTmp, pHelpBuffLen))
   CFG_CHK_APP (ToolCfgMemFree (pTmp, MemIdCfgHelpString))

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelpSet (t_pToolCfgDataDesc pCfgDataDesc, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   t_pToolCfgSet pSetArray;
   t_int      i;

   pSetArray   = pCfgDataDesc->pSetArray;
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, "[", pHelpBuffLen))

   for (i=0; ; i++)
   {
      if (pSetArray[i].pSetString == NULL) /* end of array? */
         break;
      CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, pSetArray[i].pSetString, pHelpBuffLen))
      if (pSetArray[i+1].pSetString)
         CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, ", ", pHelpBuffLen))
   }
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, "]", pHelpBuffLen))

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelpRange (t_pToolCfgDataDesc pCfgDataDesc, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   switch (pCfgDataDesc->CfgType)
   {
      case CFGTYPE_PRESENCE: CFG_CHK_APP (ToolCfgBuildHelpPresence (pCfgDataDesc, pHelpBuff, pHelpBuffLen))  break;
      case CFGTYPE_INTEGER:  CFG_CHK_APP (ToolCfgBuildHelpInteger  (pCfgDataDesc, pHelpBuff, pHelpBuffLen))  break;
      case CFGTYPE_DOUBLE:   CFG_CHK_APP (ToolCfgBuildHelpDouble   (pCfgDataDesc, pHelpBuff, pHelpBuffLen))  break;
      case CFGTYPE_STRING:   CFG_CHK_APP (ToolCfgBuildHelpString   (pCfgDataDesc, pHelpBuff, pHelpBuffLen))  break;
      case CFGTYPE_SET:      CFG_CHK_APP (ToolCfgBuildHelpSet      (pCfgDataDesc, pHelpBuff, pHelpBuffLen))  break;
      case CFGTYPE_HMS:      CFG_CHK_APP (ToolCfgBuildHelpHMS      (pCfgDataDesc, pHelpBuff, pHelpBuffLen))  break;
      case CFGTYPE_NULL:                                                                                     break;
      default:
         return TOOLCFG_ERROR_INVALID_CFGTYPE;
   }

   return NO_ERROR;
}

static APIRET ToolCfgBuildHelp (t_pToolCfgDataDesc pCfgDataDesc, t_pchar pHelpBuff, t_pint pHelpBuffLen)
{
   *pHelpBuffLen = 0;
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, pCfgDataDesc->pName, pHelpBuffLen))
   CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff, " = " , pHelpBuffLen))
   CFG_CHK_APP (ToolCfgBuildHelpRange (pCfgDataDesc, pHelpBuff, pHelpBuffLen))
   (*pHelpBuffLen)++;

   return NO_ERROR;
}

static APIRET ToolCfgBuildTableHelp (t_pToolCfgTableDesc pCfgTableDesc,
                                 t_pchar pHelpBuff1, t_pchar pHelpBuff2, t_pint pHelpBuffLen)
{
   t_pToolCfgDataDesc pCfgDataDesc;
   t_pchar        pTmp;
   t_int           i;
   t_int           NameLen;
   t_int           RangeLen;
   t_int           Spaces;
   t_int           HelpBuffLenDummy;
   t_int           PrevLen;

   *pHelpBuffLen = 0;
   HelpBuffLenDummy = 0;
   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp, 4096, MEM_OPT_NONE, MemIdCfgHelpTable))

   for (i=0;;i++)
   {
      pCfgDataDesc = &pCfgTableDesc->pDataDescArray[i];
      if (pCfgDataDesc->pName == NULL)
         break;
      NameLen  = (t_int)strlen (pCfgDataDesc->pName);
      CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff1, pCfgDataDesc->pName, pHelpBuffLen))
      PrevLen = HelpBuffLenDummy;
      CFG_CHK_APP (ToolCfgBuildHelpRange (pCfgDataDesc, pHelpBuff2, &HelpBuffLenDummy))
      RangeLen = HelpBuffLenDummy - PrevLen;
      if (RangeLen != NameLen)
      {
         Spaces = abs (RangeLen - NameLen);
         memset (pTmp, ' ', Spaces);
         pTmp[Spaces] = '\0';
         if (RangeLen > NameLen)
              CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff1, pTmp, pHelpBuffLen     ))
         else CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff2, pTmp, &HelpBuffLenDummy))
      }
      CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff1, "  ", pHelpBuffLen     ))
      CFG_CHK_APP (ToolCfgChkStrCpy (pHelpBuff2, "  ", &HelpBuffLenDummy))
   }

   CFG_CHK_APP (ToolCfgMemFree (pTmp, MemIdCfgHelpTable))
   (*pHelpBuffLen)++;

   return NO_ERROR;
}

static APIRET ToolCfgQueryFileInfo (t_pchar pFileName, t_pint pYear, t_pint pMonth , t_pint pDay,
                                                       t_pint pHour, t_pint pMinute, t_pint pSize)
{
   // OS/2 code
   // ---------
   // FILEFINDBUF3 FindBuff;
   // HDIR         hDir;
   // ULONG        Count;
   //
   // hDir  = HDIR_SYSTEM;
   // Count = 1;           // Search for only 1 entry
   // CFG_CHK_APP(DosFindFirst(pFileName, &hDir, FILE_NORMAL, &FindBuff, sizeof(FILEFINDBUF3), &Count, FIL_STANDARD))
   // CFG_CHK_APP(DosFindClose(hDir))
   //
   // if (pYear  ) *pYear   = FindBuff.fdateLastWrite.year + OS2DOS_YEAROFFSET;
   // if (pMonth ) *pMonth  = FindBuff.fdateLastWrite.month;
   // if (pDay   ) *pDay    = FindBuff.fdateLastWrite.day;
   // if (pHour  ) *pHour   = FindBuff.ftimeLastWrite.hours;
   // if (pMinute) *pMinute = FindBuff.ftimeLastWrite.minutes;
   // if (pSize  ) *pSize   = (t_int)FindBuff.cbFile;

   // Linux code
   // ----------
   struct stat FileInfo;
   struct tm*  LastModified = NULL;

   memset (&FileInfo, 0, sizeof(FileInfo));

   // Get file statistics
   if (((stat (pFileName, &FileInfo)) == -1) || (!(FileInfo.st_mode & S_IFREG)))
   {
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Could not stat configuration file %s: file not found", pFileName))
      return TOOLCFG_ERROR_FILE_NOT_FOUND;
   }

   LastModified = localtime (&(FileInfo.st_mtime));
   if (pYear  ) *pYear   = LastModified->tm_year + 1900;
   if (pMonth ) *pMonth  = LastModified->tm_mon  +    1;
   if (pDay   ) *pDay    = LastModified->tm_mday;
   if (pHour  ) *pHour   = LastModified->tm_hour;
   if (pMinute) *pMinute = LastModified->tm_min;
   if (pSize  ) *pSize   = FileInfo.st_size;

   return NO_ERROR;
}

static APIRET ToolCfgReadCmdLine (t_pToolCfgContext pCfgContext)
{
   t_pchar pTmp;
   t_int    i;
   t_uint   Alloc;
   t_int    SpaceSplit;

   /* Find size */
   pCfgContext->BufferLen = 0;
   for (i=1; i<ToolCfgLocal.argc; i++)
   {
      pCfgContext->BufferLen += (t_int) strlen (ToolCfgLocal.argv[i]) + 1; /* +1 for CR */
   }
   pCfgContext->BufferLen++; /* ++ for ending '\0' */

   /* Alloc memory */
   Alloc = pCfgContext->BufferLen;
   if (Alloc == sizeof(t_pvoid))
      Alloc++;
   CFG_CHK_APP (ToolCfgMemAlloc((void **)&pCfgContext->pBuffer, Alloc, MEM_OPT_NONE, MemIdCfgBuffer))

   // Read command line into buffer, seperate arguments by a spaces
   pTmp = pCfgContext->pBuffer;
   for (i=1; i<ToolCfgLocal.argc; i++)
   {
      (void) strcpy (pTmp, ToolCfgLocal.argv[i]);
      pTmp += strlen (ToolCfgLocal.argv[i]);
      *pTmp++ = ' ';
   }
   *pTmp = '\0';

   // Split arguments into lines. Attention: Do
   // not split string containing spaces!
   SpaceSplit=TRUE;
   for ( pTmp  = pCfgContext->pBuffer;
        *pTmp != '\0';
         pTmp++)
   {
      if (*pTmp == STRING_DELIMITER)
         SpaceSplit = !SpaceSplit;
      if (SpaceSplit && (*pTmp == ' '))
         *pTmp = CHAR_CR;
   }

//   pTmp = pCfgContext->pBuffer;
//   for (i=1; i<ToolCfgLocal.argc; i++)
//   {
//      (void) strcpy (pTmp, ToolCfgLocal.argv[i]);
//      pTmp += strlen (ToolCfgLocal.argv[i]);
//      *pTmp++ = CHAR_CR;
//   }
//   *pTmp = '\0';

   /* Set initial context */
   pCfgContext->ActLineLen = 0;
   pCfgContext->ActLineNr  = 1;
   pCfgContext->pActLine   = pCfgContext->pBuffer;

   return NO_ERROR;
}

static APIRET ToolCfgReadCfgFile (t_pToolCfgContext pCfgContext)
{
   FILE  *pCfgFile;
   t_int   SizeRead;
   t_int   rc;
   t_int   Year, Month , Day;
   t_int   Hour, Minute;
   t_int   Size;

   /* Expand filename (only possible when using the toolbox) */
   #ifndef TOCFG_COMPILE_FOR_USE_WITHOUT_TOOLBOX
      (void) strcpy (&ToolCfgLocal.TempFileName[0], &pCfgContext->FileName[0]);
      CFG_CHK_APP (ToolEnvExpand (&ToolCfgLocal.TempFileName[0], &pCfgContext->FileName[0], MAX_FILENAME_LEN))
   #endif

   /* Find size */
   pCfgFile = fopen (&pCfgContext->FileName[0], "rb");
   if (pCfgFile == NULL)
   {
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Could not open configuration file %s", &pCfgContext->FileName[0]))
      return TOOLCFG_ERROR_OPEN_FAILED;
   }
   rc = setvbuf (pCfgFile, NULL, _IOFBF, 32768);
   if (rc)
      return TOOLCFG_ERROR_SETBUF_FAILED;

   rc = fseek (pCfgFile, 0, SEEK_END);
   if (rc)
      return TOOLCFG_ERROR_SEEKEND_FAILED;
   pCfgContext->BufferLen = (t_int) ftell (pCfgFile) + 1;  /* +1 for ending '\0' */

   /* Alloc memory */
   CFG_CHK_APP (ToolCfgMemAlloc((void **)&pCfgContext->pBuffer, (t_uint)pCfgContext->BufferLen, MEM_OPT_NONE, MemIdCfgBuffer))

   /* Read command line into buffer */
   rc = fseek (pCfgFile, 0, SEEK_SET);
   if (rc)
      return TOOLCFG_ERROR_SEEKSET_FAILED;
   SizeRead = (t_int) fread (pCfgContext->pBuffer, 1, (size_t)pCfgContext->BufferLen, pCfgFile);
   if (SizeRead > pCfgContext->BufferLen)
      return TOOLCFG_ERROR_READ_FAILED;
   rc = fclose (pCfgFile);
   if (rc)
      return TOOLCFG_ERROR_CLOSE_FAILED;
   pCfgContext->pBuffer[SizeRead] = '\0';

   CFG_CHK_APP (ToolCfgQueryFileInfo (&pCfgContext->FileName[0], &Year, &Month, &Day, &Hour, &Minute, &Size))
   CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Scanning configuration file %s (%d bytes  %02d.%02d.%d  %02d:%02d)",
                &pCfgContext->FileName[0], Size, Day, Month, Year, Hour, Minute))
   /* Set initial context */
   pCfgContext->ActLineLen = 0;
   pCfgContext->ActLineNr  = 1;
   pCfgContext->pActLine   = pCfgContext->pBuffer;

   return NO_ERROR;
}

static APIRET ToolCfgGetLineLen (t_pToolCfgContext pCfgContext)
{
   t_pchar pEol;
   t_pchar pLastBackslash;
   t_int    CopyLen;
   t_int    KeepRunning;

   pLastBackslash = NULL;
   KeepRunning    = TRUE;

   pEol = pCfgContext->pActLine;

   while (KeepRunning)
   {
      switch (*pEol)
      {
         case CHAR_APPEND_LINE:
            pLastBackslash = pEol++;
            break;
         case ' '    :
         case '\t'   : pEol++; break;
         case CHAR_CR:
         case CHAR_LF: if (pLastBackslash)
                       {
                          if (((*pEol == CHAR_CR) && (pEol[1] == CHAR_LF)) ||
                              ((*pEol == CHAR_LF) && (pEol[1] == CHAR_CR)))
                             pEol++;
                          pEol++;

                          CopyLen = pCfgContext->pBuffer + pCfgContext->BufferLen - pEol;
                          memmove (pLastBackslash, pEol, CopyLen);
                          pEol = pLastBackslash;
                          pLastBackslash = NULL;
                          pCfgContext->ActLineNr++;
                       }
                       else
                          KeepRunning = FALSE;
                       break;
         case '\0'   : KeepRunning = FALSE;
                       break;
         default     : pLastBackslash = NULL;
                       pEol++;
      }
   }
   pCfgContext->ActLineLen = pEol - pCfgContext->pActLine;

   return NO_ERROR;
}

static APIRET ToolCfgSearchLine (t_pToolCfgContext pCfgContext)
{
   t_pchar pTmp;

   pTmp = pCfgContext->pActLine + pCfgContext->ActLineLen;
   if (*pTmp != '\0')
   {
      if (((pTmp[0] == CHAR_LF) && (pTmp[1] == CHAR_CR)) ||
          ((pTmp[1] == CHAR_LF) && (pTmp[0] == CHAR_CR)))
           pTmp += 2;
      else pTmp += 1;
   }
   pCfgContext->ActLineNr++;
   pCfgContext->pActLine = pTmp;
   CFG_CHK_APP (ToolCfgGetLineLen (pCfgContext))

   return NO_ERROR;
}

/* CfgNoFurtherParamsExpected reports an error if further   */
/* parameters are found between pStart and the end of line. */
static APIRET ToolCfgNoFurtherParamsExpected (t_pcchar pStart, t_pcchar pCorrectSyntaxText)
{
   while ((*pStart == ' ') || (*pStart == '\t'))
      pStart++;
   if ((*pStart != CHAR_CR) &&
       (*pStart != CHAR_LF) &&
       (*pStart != '\0'))
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pStart))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "No further parameter expected."))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pCorrectSyntaxText))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   return NO_ERROR;
}

//static t_pchar ToolCfgFindEndOfString (t_pchar pString)
//{
//   t_pchar pTmp;
//
//   for ( pTmp = pString;
//        (*pTmp != CHAR_CR) && (*pTmp !=  ' ') &&
//        (*pTmp != CHAR_LF) && (*pTmp != '\t') && (*pTmp != '\0');
//         pTmp++)
//   {
//   }
//   return pTmp-1;
//}

static t_int ToolCfgScanningCmdLine (void)
{
   if (ToolCfgLocal.pActCfgContext->FileName[0] == '\0')
        return TRUE;
   else return FALSE;
}

static APIRET ToolCfgDropContext (t_pToolCfgContext pCfgContext)
{
   CFG_CHK_APP (ToolCfgMemFree (pCfgContext->pBuffer, MemIdCfgBuffer))

   return NO_ERROR;
}

/* CfgGetNextLine is the core function of the configuration scanner. From the calling   */
/* function's point of view, it simply reads the next line (or returns ppLine = NULL if */
/* no more data can be found). Internally it manages the INCLUDE and SECTION statements */
/* as well as trailing backslashes (for joining lines).                                 */
static APIRET ToolCfgGetNextLine (t_pchar *ppLine, t_pint pLineLen)
{
   t_pToolCfgContext pContext;
   t_pchar       pEol, pTmp, pKeyWord, pFirstWord;
   t_int          ParamLen;
   t_int          RemLineLen;
   char           TmpChar;
   t_int          KeyLen;
   t_int          KeyLenSectionStart;
   t_int          KeyLenSectionEnd;
   t_int          SectionNameFound;
   t_int          i;
   t_int          SectionSubNesting;
   bool           ChkInclude;
   bool           ChkIncludeOpt;
   APIRET         rc;
   char           Space[] = " ";

   pTmp = Space;
   pContext = ToolCfgLocal.pActCfgContext;
   while ((pContext->State != CFGSTATE_LINEREAD) &&
          (pContext->State != CFGSTATE_COMPLETED))
   {
      switch (pContext->State)
      {
         case CFGSTATE_INCLUDENEWFILE:
         case CFGSTATE_INCLUDENEWFILE_OPT:
            if (strlen (pContext->FileName) == 0)
                 rc = ToolCfgReadCmdLine (pContext);   /* The cmd line has to be read into pBuffer */
            else rc = ToolCfgReadCfgFile (pContext);   /* The cfg file has to be read into pBuffer */
            if ((rc == TOOLCFG_ERROR_OPEN_FAILED) && (pContext->State == CFGSTATE_INCLUDENEWFILE_OPT))
            {
               ToolCfgLocal.IncludeNestingLevel--;
               ToolCfgLocal.pActCfgContext = &ToolCfgLocal.pCfgContextStack[ToolCfgLocal.IncludeNestingLevel];
               pContext = ToolCfgLocal.pActCfgContext;
               pContext->State = CFGSTATE_GETNEWLINE;
            }
            else
            {
               CFG_CHK_APP (rc)
               pTmp = pContext->pActLine;
               CFG_CHK_APP (ToolCfgGetLineLen (pContext))
               pContext->State = CFGSTATE_CHECKEOF;
            }
            break;

         case CFGSTATE_GETNEWLINE:
            CFG_CHK_APP (ToolCfgSearchLine (pContext))
            pContext->State = CFGSTATE_CHECKEOF;
            break;

         case CFGSTATE_CHECKEOF:
            pTmp = pContext->pActLine;
            if ((*pTmp == '\0') || (*pTmp == CHAR_EOF))
            {
               if (pContext->SectionNesting != 0)
               {
                  pContext->pActLine = NULL;
                  CFG_CHK_APP (ToolCfgStdErrorHeader (NULL))
                  CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Missing statement %s before end of file.", KEYWORD_SECTIONEND))
                  return TOOLCFG_ERROR_CONFIG_ERROR;
               }
               CFG_CHK_APP (ToolCfgDropContext (pContext))
               ToolCfgLocal.IncludeNestingLevel--;
               if (ToolCfgLocal.IncludeNestingLevel == -1) /* did we leave the last nesting level */
               {
                  pTmp = NULL;
                  pContext->pActLine   = NULL;
                  pContext->ActLineLen = 0;
                  pContext->State = CFGSTATE_COMPLETED;
               }
               else
               {
                  ToolCfgLocal.pActCfgContext = &ToolCfgLocal.pCfgContextStack[ToolCfgLocal.IncludeNestingLevel];
                  pContext = ToolCfgLocal.pActCfgContext;
                  pContext->State = CFGSTATE_GETNEWLINE;
               }
            }
            else
               pContext->State = CFGSTATE_CHECKINCLUDE;
            break;

         case CFGSTATE_CHECKINCLUDE:
            for (  pKeyWord = pTmp;
                 (*pKeyWord == ' ') || (*pKeyWord == '\t');
                   pKeyWord++)
            {
            }
            ChkInclude    = ToolCfgStrNCmpNoCase (pKeyWord, KEYWORD_INCLUDE    , (t_int) strlen (KEYWORD_INCLUDE    )) == 0;
            ChkIncludeOpt = ToolCfgStrNCmpNoCase (pKeyWord, KEYWORD_INCLUDE_OPT, (t_int) strlen (KEYWORD_INCLUDE_OPT)) == 0;
            if (ChkInclude || ChkIncludeOpt)
            {
               if (++ToolCfgLocal.IncludeNestingLevel >= ToolCfgLocal.MaxIncludeNestingLevel)
                  return TOOLCFG_ERROR_INCLUDE_NESTING_OVERFLOW;
               pEol = pContext->pActLine + pContext->ActLineLen;
               TmpChar = *pEol;
               *pEol = '\0';     /* set to 0 to have sscanf stop at the end of line */
               ToolCfgLocal.pActCfgContext = &ToolCfgLocal.pCfgContextStack[ToolCfgLocal.IncludeNestingLevel];
               pContext = ToolCfgLocal.pActCfgContext;
               if (ChkIncludeOpt)
               {
                  pContext->State = CFGSTATE_INCLUDENEWFILE_OPT;
                  pFirstWord = pKeyWord + strlen (KEYWORD_INCLUDE_OPT);
               }
               else
               {
                  pContext->State = CFGSTATE_INCLUDENEWFILE;
                  pFirstWord = pKeyWord + strlen (KEYWORD_INCLUDE);
               }

               // Initialize for parameter scanning
               ParamLen   = 0;
               RemLineLen = strlen (pFirstWord);

               // Get the filename
               CFG_CHK_APP (ToolCfgFindFirstWord  (pFirstWord + ParamLen, (const char **) &pFirstWord, RemLineLen, &RemLineLen))
               CFG_CHK_APP (ToolCfgGetParamLen    (pFirstWord, &ParamLen))
               ToolCfgStrMaxCpy (&pContext->FileName[0], pFirstWord, min(MAX_FILENAME_LEN, ParamLen+1));
               RemLineLen -= ParamLen;

               // Get the section names
               for (i=0; ;i++)
               {
                  CFG_CHK_APP (ToolCfgFindFirstWord  (pFirstWord + ParamLen, (const char **) &pFirstWord, RemLineLen, &RemLineLen))
                  if (pFirstWord == NULL)
                     break;
                  if (i >= MAX_SECTIONNAMES)
                  {
                     CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Too many parameters for keyword %s or %s", KEYWORD_INCLUDE, KEYWORD_INCLUDE_OPT))
                     CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "The correct syntax is: %s <FileName> [SectionName1] [SectionName2] ... [SectionName16]", KEYWORD_INCLUDE))
                     return TOOLCFG_ERROR_CONFIG_ERROR;
                  }
                  CFG_CHK_APP (ToolCfgGetParamLen    (pFirstWord, &ParamLen))
                  ToolCfgStrMaxCpy (&(pContext->SectionNameArr[i][0]), pFirstWord, min(MAX_SECTIONNAME_LEN, ParamLen)+1);
                  RemLineLen -= ParamLen;
               }
               *pEol = TmpChar;

               while (i<MAX_SECTIONNAMES)
                  (pContext->SectionNameArr[i++])[0] = '\0';
               pContext->SectionNesting = 0;
            }
            else
               pContext->State = CFGSTATE_CHECKSECTION;
            break;

         case CFGSTATE_CHECKSECTION:
            for (  pKeyWord = pTmp;      /* lint ?????????? */
                 (*pKeyWord == ' ') || (*pKeyWord == '\t');
                   pKeyWord++)
            {
            }
            if (ToolCfgStrNCmpNoCase (pKeyWord, KEYWORD_SECTIONSTART, (t_int) strlen (KEYWORD_SECTIONSTART)) == 0)
            {
//               if (pContext->SectionNesting >0)
//               {
//                  CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
//                  CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "SECTION statements are not allowed to be nested within one file."))
//                  return TOOLCFG_ERROR_CONFIG_ERROR;
//               }

               pFirstWord = pKeyWord + strlen (KEYWORD_SECTIONSTART);
               RemLineLen = strlen (pFirstWord);

               CFG_CHK_APP (ToolCfgFindFirstWord (pFirstWord, (const char **) &pFirstWord, RemLineLen, &RemLineLen))
               if (pFirstWord == NULL)
               {
                  CFG_CHK_APP (ToolCfgStdErrorHeader (pFirstWord))
                  CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Section name expected."))
                  CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "The correct syntax is: SECTION [SectionName1] [SectionName2] ..."))
                  return TOOLCFG_ERROR_CONFIG_ERROR;
               }
               SectionNameFound = FALSE;
               while (pFirstWord != NULL)
               {
                  CFG_CHK_APP (ToolCfgGetParamLen (pFirstWord, &ParamLen))
                  TmpChar = pFirstWord[ParamLen];
                  pFirstWord[ParamLen] = '\0';
                  for (i=0; (i<MAX_SECTIONNAMES) && !SectionNameFound; i++)
                  {
                     SectionNameFound = (ToolCfgStrCmpNoCase (&(pContext->SectionNameArr[i][0]), pFirstWord) == 0); // pFirstWord may have less chars than SectionNameArr[i],
                  }                                                                                               // but in that case, ToolCfgStrCmpNoCase will no return 0 (eq)
                  if (!SectionNameFound)
                  {
                     for (i=0; (i<MAX_GLOBALSECTIONNAMES) && !SectionNameFound; i++)
                        SectionNameFound = (ToolCfgStrCmpNoCase (&(ToolCfgLocal.GlobalSectionNameArr[i][0]), pFirstWord) == 0);
                  }
                  pFirstWord[ParamLen] = TmpChar;
                  if (SectionNameFound)
                     break;
                  RemLineLen -= ParamLen;
                  CFG_CHK_APP (ToolCfgFindFirstWord  (pFirstWord + ParamLen, (const char **) &pFirstWord, RemLineLen, &RemLineLen))
               }
               if (SectionNameFound)
               {
                  pContext->SectionNesting++;
                  pContext->State = CFGSTATE_GETNEWLINE;
               }
               else
               {
                  pContext->State = CFGSTATE_SEARCHSECTIONEND;
               }
            }
            else
            {
               pContext->State = CFGSTATE_CHECKSECTIONEND;
            }
            break;

         case CFGSTATE_CHECKSECTIONEND:
            KeyLen = (t_int) strlen (KEYWORD_SECTIONEND);
            for ( pKeyWord = pTmp;                 /* lint: ???? */
                 (*pKeyWord == ' ') || (*pKeyWord == '\t');
                  pKeyWord++)
            {
            }
            if (ToolCfgStrNCmpNoCase (pKeyWord, KEYWORD_SECTIONEND, KeyLen) == 0)
            {
               CFG_CHK_APP (ToolCfgNoFurtherParamsExpected (pKeyWord + KeyLen, "ENDSECTION requires no parameters."))
               if (pContext->SectionNesting <= 0)
               {
                  CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
                  CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "No corresponding SECTION statement encountered."))
                  return TOOLCFG_ERROR_CONFIG_ERROR;
               }
               else
               {
                  pContext->SectionNesting--;
                  pContext->State = CFGSTATE_GETNEWLINE;
               }
            }
            else
               pContext->State = CFGSTATE_LINEREAD;
            break;

         case CFGSTATE_SEARCHSECTIONEND:

            KeyLenSectionStart = (t_int) strlen (KEYWORD_SECTIONSTART);
            KeyLenSectionEnd   = (t_int) strlen (KEYWORD_SECTIONEND  );
            for (SectionSubNesting = 0;;)
            {
               CFG_CHK_APP (ToolCfgSearchLine (pContext))
               if (*pContext->pActLine == '\0')
               {
                  pContext->pActLine = NULL;
                  CFG_CHK_APP (ToolCfgStdErrorHeader (NULL))
                  CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Missing statement ENDSECTION before end of file."))
                  return TOOLCFG_ERROR_CONFIG_ERROR;
               }
               pTmp = pContext->pActLine;
               while ((*pTmp != '\0') && ((*pTmp == ' ') || (*pTmp == '\t')))
                  pTmp++;

               if (ToolCfgStrNCmpNoCase (pTmp, KEYWORD_SECTIONEND, KeyLenSectionEnd) == 0)
               {
                  CFG_CHK_APP (ToolCfgNoFurtherParamsExpected (pTmp + KeyLenSectionEnd, "ENDSECTION requires no parameters."))
                  if (SectionSubNesting == 0)
                  {
                     pContext->State = CFGSTATE_GETNEWLINE;
                     break;
                  }
                  SectionSubNesting--;
               }
               else if (ToolCfgStrNCmpNoCase (pTmp, KEYWORD_SECTIONSTART, KeyLenSectionStart) == 0)
               {
                  SectionSubNesting++;
               }
            }
            break;

         default:
            return TOOLCFG_ERROR_INVALID_STATE;
      }
   }
   pContext->State = CFGSTATE_GETNEWLINE; /* This will be our task on the next call of this function */
   *ppLine   = pContext->pActLine;
   *pLineLen = pContext->ActLineLen;

   return NO_ERROR;
}

/* ----------------------------------- */
/*  Conversion functions               */
/* ----------------------------------- */

static APIRET ToolCfgConvertpParamToHMS (t_pchar pParam, t_int /*ParamLen*/, t_pint pValue)
{
   t_int rc;
   t_int Hour, Min, Sec;

   if (((pParam[0] >= '0') && (pParam[0] <= '9')))
   {
      rc = sscanf (pParam, "%d:%d:%d", &Hour, &Min, &Sec);
      if (rc!=3)
      {
         Sec = 0;
         rc = sscanf (pParam, "%d:%d", &Hour, &Min);  // Check reduced HH:MM format
         if (rc!=2)
         {
            CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid hh:mm:ss value."))
            return TOOLCFG_ERROR_CONFIG_ERROR;
         }
      }
      *pValue = Hour*3600 + Min*60 + Sec;      // convert to seconds
   }
   else
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid entry, hh:mm:ss expected."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   return NO_ERROR;
}

static APIRET ToolCfgConvertpParamToInteger (t_pchar pParam, t_int ParamLen, t_pint pValue, t_pint pDecConv)
{
   t_int rc;
   t_int i;
   t_int MinLen;

   if ( ((pParam[0] == '0') && ((pParam[1] == 'x') || (pParam[1] == 'X'))) ||
         (pParam[0] == '$'))
   {
      if (pParam[0] == '$')
           MinLen = 1;
      else MinLen = 2;

      for (i=MinLen; i<ParamLen; i++)
      {
         if(!((pParam[i] >= '0') && (pParam[i] <= '9')) &&
            !((pParam[i] >= 'a') && (pParam[i] <= 'f')) &&
            !((pParam[i] >= 'A') && (pParam[i] <= 'F')))
         {
            CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+i))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid hexadecimal value."))
            return TOOLCFG_ERROR_CONFIG_ERROR;
         }
      }

      if (ParamLen > MinLen)
           rc = sscanf (&pParam[MinLen], "%x", pValue);
      else rc = 0;
      if (rc!=1)
      {
         CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid hexadecimal value."))
         return TOOLCFG_ERROR_CONFIG_ERROR;
      }
      *pDecConv=FALSE;
   }
   else if (((pParam[0] >= '0') && (pParam[0] <= '9')) || (pParam[0] == '-'))
   {
      for (i=1; i<ParamLen; i++)
      {
         if ((pParam[i] < '0') || (pParam[i] > '9'))
         {
            CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+i))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid decimal value."))
            return TOOLCFG_ERROR_CONFIG_ERROR;
         }
      }

      rc = sscanf (pParam, "%d", pValue);
      if (rc!=1)
      {
         CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid decimal value."))
         return TOOLCFG_ERROR_CONFIG_ERROR;
      }
      *pDecConv=TRUE;
   }
   else
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid entry, decimal or hexadecimal number expected."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   return NO_ERROR;
}

static APIRET ToolCfgConvertpParamToDouble (t_pchar pParam, t_int ParamLen, t_pdouble pValue)
{
   t_int         rc;
   long double   LongDoubleVal;
   t_pchar     pOldLocale;
   t_pchar     pSavedLocale;
   t_int         i;
   t_int         DecimalPoint;
   t_int         Exponent;

   // Check all the chars in the param to detect entry errors
   // Convert ',' to '.'
   DecimalPoint = FALSE;
   Exponent     = FALSE;
   for (i=0; i<ParamLen; i++)
   {
      if (pParam[i] == ',')
         pParam[i] = '.';
      if (pParam[i] == '.')
      {
         if (i==0)
         {
            CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+i))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid double value, replace '.XXX' by '0.XXX'"))
            return TOOLCFG_ERROR_CONFIG_ERROR;
         }

         if (DecimalPoint)
         {
            CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+i))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid double value, two decimal points have been detected"))
            return TOOLCFG_ERROR_CONFIG_ERROR;
         }
         else
         {
            DecimalPoint = TRUE;
         }
      }
      else if ((pParam[i] == 'E') || (pParam[i] == 'e'))
      {
         if (Exponent)
         {
            CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+i))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid double value, two 'E'xponents have been detected"))
            return TOOLCFG_ERROR_CONFIG_ERROR;
         }
         else
         {
            Exponent = TRUE;
         }
      }
      else if (pParam[i] == '-')
      {
         if (i!=0)
         {
            if ((pParam[i-1] != 'E') &&
                (pParam[i-1] != 'e'))
            {
               CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+i))
               CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid double value, minus sign must be placed at the beginning"))
               return TOOLCFG_ERROR_CONFIG_ERROR;
            }
         }
      }
      else if ((pParam[i] < '0') || (pParam[i] > '9'))
      {
         CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+i))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid character encountered"))
         return TOOLCFG_ERROR_CONFIG_ERROR;
      }
   }

   // switch the 'locale' to accept '.' as decimal point,
   // scan the double value and switch back.
   pOldLocale = setlocale (LC_NUMERIC, NULL);
   pSavedLocale = strdup (pOldLocale);
   (void) setlocale (LC_NUMERIC, "C");
   rc = sscanf (pParam, "%LG", &LongDoubleVal);
   (void) setlocale (LC_NUMERIC, pSavedLocale);
   free (pSavedLocale);

   if (rc!=1)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid double value."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   *pValue = (t_double) LongDoubleVal;

   return NO_ERROR;
}

static APIRET ToolCfgReadParameterHMS (t_pchar pParam, t_int ParamLen, t_pToolCfgDataDesc pCfgDataDesc, long BaseAddr, t_int Assign)
{
   t_pcchar pFormatStr;
   t_int     Value;
   t_int     MinVal, MaxVal;

   CFG_CHK_APP (ToolCfgConvertpParamToHMS (pParam, ParamLen, &Value))

   if ((Value < (t_int)pCfgDataDesc->MinValue) || (Value > (t_int)pCfgDataDesc->MaxValue))
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      MinVal = (t_int)pCfgDataDesc->MinValue;
      MaxVal = (t_int)pCfgDataDesc->MaxValue;

      pFormatStr = "Value out of range: %02d:%02d:%02d <= %s <= %02d:%02d:%02d";
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pFormatStr, MinVal/3600, (MinVal/60)%60, MinVal%60, pCfgDataDesc->pName,
                                                       MaxVal/3600, (MaxVal/60)%60, MaxVal%60))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   if (Assign)
      *((t_pint)((t_pchar)pCfgDataDesc->DestAddr + BaseAddr)) = Value;

   return NO_ERROR;
}

static APIRET ToolCfgReadParameterInteger (t_pchar pParam, t_int ParamLen, t_pToolCfgDataDesc pCfgDataDesc, long BaseAddr, t_int Assign)
{
   t_pcchar pFormatStr;
   t_int     Value;
   t_int     DecConv;  /* To check whether a decimal or hexadecimal conversion had be performed */

   CFG_CHK_APP (ToolCfgConvertpParamToInteger (pParam, ParamLen, &Value, &DecConv))

   if ((Value < (t_int)pCfgDataDesc->MinValue) || (Value > (t_int)pCfgDataDesc->MaxValue))
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      if (DecConv)
           pFormatStr = "Value out of range: %d <= %s <= %d";
      else pFormatStr = "Value out of range: 0x%X <= %s <= 0x%X";
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pFormatStr, (t_int)pCfgDataDesc->MinValue, pCfgDataDesc->pName, (t_int)pCfgDataDesc->MaxValue))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   if (Assign)
      *((t_pint)((t_pchar)pCfgDataDesc->DestAddr + BaseAddr)) = Value;

   return NO_ERROR;
}

static APIRET ToolCfgReadParameterDouble (t_pchar pParam, t_int ParamLen, t_pToolCfgDataDesc pCfgDataDesc, long BaseAddr, t_int Assign)
{
   t_pcchar pFormatStr;
   t_double  Value;

   CFG_CHK_APP (ToolCfgConvertpParamToDouble (pParam, ParamLen, &Value))

   if ((Value < pCfgDataDesc->MinValue) || (Value > pCfgDataDesc->MaxValue))
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      pFormatStr = "Value out of range: %G <= %s <= %G";
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pFormatStr, pCfgDataDesc->MinValue, pCfgDataDesc->pName, pCfgDataDesc->MaxValue))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   if (Assign)
      *((t_pdouble)((t_pchar)pCfgDataDesc->DestAddr + BaseAddr)) = Value;

   return NO_ERROR;
}

static APIRET ToolCfgConvertpParamToString (t_pchar pParam, t_int ParamLen, t_pchar *ppString, t_pint pStringLen)
{
   if (pParam[0] != STRING_DELIMITER)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "String expected. Use %c %c as string delimiters.", STRING_DELIMITER, STRING_DELIMITER))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   if (pParam[ParamLen-1] != STRING_DELIMITER)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam+ParamLen-1))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "String exceeds end of line, use %c %c as string delimiters, or %c to append next line.", STRING_DELIMITER, STRING_DELIMITER, CHAR_APPEND_LINE))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   *ppString   = pParam+1;
   *pStringLen = ParamLen-2;

   return NO_ERROR;
}

static APIRET ToolCfgReadParameterString (t_pchar pParam, t_int ParamLen, t_pToolCfgDataDesc pCfgDataDesc, long BaseAddr, t_int Assign)
{
   t_pchar pString;
   t_int    StringLen;
   t_pchar pDest;
   t_int    Src, Dst;

   CFG_CHK_APP (ToolCfgConvertpParamToString (pParam, ParamLen, &pString, &StringLen))
   if (StringLen > pCfgDataDesc->DestLen)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "String too long. The maximum length of the string is %d", pCfgDataDesc->DestLen))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   if (Assign)
   {
      /* Copy the string, replace duplicate '' by single' */
      pDest = (t_pchar)pCfgDataDesc->DestAddr + BaseAddr;
      for (Src=0, Dst=0; Src<StringLen; Src++, Dst++)
      {
         pDest[Dst] = pString[Src];
         if ((pString[Src] == STRING_DELIMITER) && (Src<(StringLen-1)))
         {
            if (pString[Src+1] == STRING_DELIMITER)
               Src++;
         }
      }
      pDest[Dst] = '\0';

//      (void) memcpy ((t_pchar)pCfgDataDesc->DestAddr + BaseAddr, pString, (size_t) StringLen);
//      ((t_pchar)pCfgDataDesc->DestAddr + BaseAddr)[StringLen] = '\0';
   }

   return NO_ERROR;
}

static APIRET ToolCfgConvertpParamToSet (t_pchar pParam, t_int ParamLen, t_pToolCfgDataDesc pCfgDataDesc, t_pint pSetValue)
{
   t_pchar   pTmp;
   t_int      i;
   t_int      Len;
   t_pToolCfgSet pSetArray;

   pSetArray = pCfgDataDesc->pSetArray;
   for (i=0; ; i++)
   {
      if (pSetArray[i].pSetString == NULL)
         break; /* end of array */
      if (ParamLen == (t_int)strlen (pSetArray[i].pSetString))
         if (ToolCfgStrNCmpNoCase (pSetArray[i].pSetString, pParam, ParamLen) == 0)
            break; /* set entry found */
   }
   if (pSetArray[i].pSetString == NULL)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pParam))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid set value."))
      CFG_CHK_APP (ToolCfgBuildHelp (pCfgDataDesc, NULL, &Len))
      CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp, (t_uint)Len, MEM_OPT_NONE, MemIdCfgHelpBuff))
      CFG_CHK_APP (ToolCfgBuildHelp (pCfgDataDesc, pTmp, &Len))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pTmp))
      CFG_CHK_APP (ToolCfgMemFree (pTmp, MemIdCfgHelpBuff))

      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   *pSetValue = pSetArray[i].SetValue;

   return NO_ERROR;
}

static APIRET ToolCfgReadParameterSet (t_pchar pParam, t_int ParamLen, t_pToolCfgDataDesc pCfgDataDesc, long BaseAddr, t_int Assign)
{
   t_int    SetValue;

   CFG_CHK_APP (ToolCfgConvertpParamToSet (pParam, ParamLen, pCfgDataDesc, &SetValue))
   if (Assign)
      *((t_pint)((t_pchar)pCfgDataDesc->DestAddr + BaseAddr)) = SetValue;

   return NO_ERROR;
}

static APIRET ToolCfgReadParameter0 (t_pchar pFirstParamChar, t_int ParamLen, t_pToolCfgDataDesc pCfgDataDesc, long BaseAddr, t_int Assign)
{
   if (BaseAddr == (long)INT_MIN)
      Assign = FALSE;
   switch (pCfgDataDesc->CfgType)
   {
      case CFGTYPE_INTEGER: CFG_CHK_APP (ToolCfgReadParameterInteger (pFirstParamChar, ParamLen, pCfgDataDesc, BaseAddr, Assign)) break;
      case CFGTYPE_DOUBLE : CFG_CHK_APP (ToolCfgReadParameterDouble  (pFirstParamChar, ParamLen, pCfgDataDesc, BaseAddr, Assign)) break;
      case CFGTYPE_STRING : CFG_CHK_APP (ToolCfgReadParameterString  (pFirstParamChar, ParamLen, pCfgDataDesc, BaseAddr, Assign)) break;
      case CFGTYPE_SET    : CFG_CHK_APP (ToolCfgReadParameterSet     (pFirstParamChar, ParamLen, pCfgDataDesc, BaseAddr, Assign)) break;
      case CFGTYPE_HMS    : CFG_CHK_APP (ToolCfgReadParameterHMS     (pFirstParamChar, ParamLen, pCfgDataDesc, BaseAddr, Assign)) break;
      case CFGTYPE_NULL   : break;
      default:
         return TOOLCFG_ERROR_INVALID_CFGTYPE;
   }

   return NO_ERROR;
}

/* ---------------------------------------------- */
/*           Table scanning functions             */
/* ---------------------------------------------- */

APIRET ToolCfgAddGlobalSectionName (t_pcchar pSectionName)
{
   t_int i;

   if (strlen (pSectionName) > MAX_SECTIONNAME_LEN)
      return TOOLCFG_ERROR_SECTIONNAME_TOO_LONG;

   for (i=0; i<MAX_GLOBALSECTIONNAMES; i++)
   {
      if (ToolCfgLocal.GlobalSectionNameArr[i][0] == '\0')
      {
         strcpy (&(ToolCfgLocal.GlobalSectionNameArr[i][0]), pSectionName);
         break;
      }
   }
   if (i>=MAX_GLOBALSECTIONNAMES)
      return TOOLCFG_ERROR_TOO_MANY_SECTIONNAMES;

   return NO_ERROR;
}

APIRET ToolCfgDelGlobalSectionName (t_pcchar pSectionName)
{
   t_int i;

   if (strlen (pSectionName) > MAX_SECTIONNAME_LEN)
      return TOOLCFG_ERROR_SECTIONNAME_TOO_LONG;

   for (i=0; i<MAX_GLOBALSECTIONNAMES; i++)
   {
      if (ToolCfgStrCmpNoCase (&ToolCfgLocal.GlobalSectionNameArr[i][0], pSectionName) == 0)
      {
         ToolCfgLocal.GlobalSectionNameArr[i][0] = '\0';
         break;
      }
   }
   if (i>=MAX_GLOBALSECTIONNAMES)
      return TOOLCFG_ERROR_SECTIONNAME_NOTFOUND;

   return NO_ERROR;
}

static APIRET ToolCfgScanTableAnalyseLine (t_pToolCfgTableDesc pCfgTableDesc, t_pchar pFirstWord, t_int RemLineLen,
                                       long BaseAddr, t_pint pLineOk)
{
   t_int i;
   t_int ParamLen;
   t_pToolCfgDataDesc pCfgDataDesc;

   *pLineOk = FALSE;
/*   printf ("\r\nTable entry: %*.*s", RemLineLen, RemLineLen, pFirstWord); */
   if (RemLineLen == 0)
      return NO_ERROR;
   ParamLen = 0;
   for (i=0; ;i++)
   {
      pCfgDataDesc = &pCfgTableDesc->pDataDescArray[i];
      if (pCfgDataDesc->pName == NULL)
         break; /* Array ends here */

      CFG_CHK_APP (ToolCfgFindFirstWord (pFirstWord + ParamLen, (const char **) &pFirstWord, RemLineLen, &RemLineLen))
      CFG_CHK_APP (ToolCfgGetParamLen (pFirstWord, &ParamLen))
      CFG_CHK_APP (ToolCfgReadParameter0 (pFirstWord, ParamLen, pCfgDataDesc, BaseAddr, TRUE))
   }
   *pLineOk = TRUE;

   return NO_ERROR;
}

static APIRET ToolCfgScanTable (t_pchar pActLine, t_int LineLen, t_pToolCfgTableDesc pCfgTableDescArray)
{
   t_pchar  pFirstWord;
   t_int     RemLineLen;
   t_int     i, rcs, Eq;
   t_int     LineOk;
   t_int     Found;
   t_pchar  pTableType;
   t_pchar  pTableName;
   long      BaseAddr;
   t_pcchar pErrorText;
   t_char    TmpChar;
   t_pToolCfgTableDesc pCfgTableDesc = NULL;

   TmpChar = pActLine[LineLen];  /* set end of line to '\0' to ease processing */
   pActLine[LineLen] = '\0';
   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTableType, 64, MEM_OPT_NONE, MemIdCfgTableType))
   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTableName, 64, MEM_OPT_NONE, MemIdCfgTableName))
   CFG_CHK_APP (ToolCfgFindFirstWord (pActLine, (const char **) &pFirstWord, LineLen, &RemLineLen))
   rcs = sscanf (pActLine, "%*s %s %s", pTableType, pTableName);
   if (rcs != 2)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pFirstWord))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid parameters for keyword %s", KEYWORD_TABLESTART))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "The correct syntax is: %s <TableType> <TableName>", KEYWORD_TABLESTART))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   Found = FALSE;
   if (pCfgTableDescArray != NULL)
   {
      for (i=0; ;i++)
      {
         pCfgTableDesc = &pCfgTableDescArray[i];
         if (pCfgTableDesc->pTableType == NULL) /* End of array reached? */
            break;
         if (ToolCfgStrCmpNoCase(pCfgTableDesc->pTableType, pTableType) == 0) /* Table type found? */
         {
            Found = TRUE;
            break;
         }
      }
   }
   if (!Found)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pFirstWord))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid table type: %s", pTableType))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   CFG_CHK_APP ((*pCfgTableDesc->pStartFn) (pTableName, &BaseAddr, &pErrorText))
   if (pErrorText)
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pActLine))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pErrorText))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   pActLine[LineLen] = TmpChar;
   for (LineLen=0;;)
   {
      CFG_CHK_APP (ToolCfgGetNextLine (&pActLine, &LineLen))
      TmpChar = pActLine[LineLen];  /* set end of line to '\0' to ease processing */
      pActLine[LineLen] = '\0';
      if (pActLine == NULL)
      {
         CFG_CHK_APP (ToolCfgStdErrorHeader (NULL))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Missing statement %s before end of file.", KEYWORD_TABLEEND))
         return TOOLCFG_ERROR_CONFIG_ERROR;
      }
      CFG_CHK_APP (ToolCfgFindFirstWord (pActLine, (const char **) &pFirstWord, LineLen, &RemLineLen))
      CFG_CHK_APP (ToolCfgCompareKeyWord (KEYWORD_TABLEEND, pFirstWord, RemLineLen, &Eq, NULL))
      if (Eq)
      {
         if (pCfgTableDesc->pEndFn)
         {
            CFG_CHK_APP ((*pCfgTableDesc->pEndFn) (&pErrorText))
            if (pErrorText)
            {
               CFG_CHK_APP (ToolCfgStdErrorHeader (pActLine))
               CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pErrorText))
               return TOOLCFG_ERROR_CONFIG_ERROR;
            }
         }
         pActLine[LineLen] = TmpChar;
         break;
      }
      CFG_CHK_APP (ToolCfgCompareKeyWord (KEYWORD_REMARK, pFirstWord, RemLineLen, &Eq, NULL))
      if (!Eq)
      {
         CFG_CHK_APP (ToolCfgScanTableAnalyseLine (pCfgTableDesc, pFirstWord, RemLineLen, BaseAddr, &LineOk))
         if (LineOk)
         {
            CFG_CHK_APP ((*pCfgTableDesc->pSaveAndNextFn) (&BaseAddr, &pErrorText))
            if (pErrorText)
            {
               CFG_CHK_APP (ToolCfgStdErrorHeader (pActLine))
               CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pErrorText))
               return TOOLCFG_ERROR_CONFIG_ERROR;
            }
         }
      }
      pActLine[LineLen] = TmpChar;
   }
   CFG_CHK_APP (ToolCfgMemFree (pTableType, MemIdCfgTableType))
   CFG_CHK_APP (ToolCfgMemFree (pTableName, MemIdCfgTableName))

   return NO_ERROR;
}

/* ---------------------------------------------- */
/*        Parameter scanning functions            */
/* ---------------------------------------------- */

static APIRET ToolCfgCheckAssignment (t_pchar pKeyWord, t_pToolCfgParamDesc pCfgParamDesc, t_pint pAssign)
{
   t_ToolCfgAssignment AssignSource;
   t_ToolCfgAssignment AssignCount;

   AssignSource = (t_ToolCfgAssignment)(pCfgParamDesc->Assign & CFGASN_SOURCE);
   AssignCount  = (t_ToolCfgAssignment)(pCfgParamDesc->Assign & CFGASN_COUNT );

   if ((AssignSource == CFGASN_CMD) && !ToolCfgScanningCmdLine())
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Assignment for this parameter is not allowed in configuration file (only on command line)."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }
   if ((AssignSource == CFGASN_CFG) && ToolCfgScanningCmdLine())
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Assignment for this parameter is not allowed on command line (only in configuration file)."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   if ((AssignCount == CFGASN_ONCE) && ((pCfgParamDesc->CfgAssignments + pCfgParamDesc->CmdAssignments) >= 1))
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Assignment for this parameter is only allowed once"))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   if (AssignCount == CFGASN_TWICE)
   {
      if (((pCfgParamDesc->CfgAssignments>0) && !ToolCfgScanningCmdLine()) ||
          ((pCfgParamDesc->CmdAssignments>0) &&  ToolCfgScanningCmdLine()))
      {
         CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Assignment for this parameter is only allowed once in configuration file."))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "The assignment in configuration file may be replaced by a command line"))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "assignment"))
         return TOOLCFG_ERROR_CONFIG_ERROR;
      }
   }

   if (ToolCfgScanningCmdLine())
        *pAssign = TRUE;
   else *pAssign = pCfgParamDesc->CmdAssignments ? FALSE : TRUE;

   return NO_ERROR;
}

static APIRET ToolCfgCheckEqualSign (t_pcchar pNextChar, t_pcchar *ppFirstParamChar)
{
   while (((*pNextChar == ' ') || (*pNextChar == '\t')) && (*pNextChar != '\0'))
      pNextChar++;
   if ((*pNextChar == '\0') || (*pNextChar != '='))
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pNextChar-1))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Equal sign '=' expected."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   for ( *ppFirstParamChar = pNextChar+1;
       ((**ppFirstParamChar == ' ') || (**ppFirstParamChar == '\t')) && (**ppFirstParamChar != '\0');
       ( *ppFirstParamChar)++)
   {
   }
   if (**ppFirstParamChar == '\0')
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader ((*ppFirstParamChar)-1))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Parameter expected."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   return NO_ERROR;
}

static APIRET ToolCfgReadParameter (t_pchar pKeyWord, t_pchar pNextChar, t_pToolCfgParamDesc pCfgParamDesc)
{
   t_pToolCfgDataDesc pCfgDataDesc;
   t_pchar        pFirstParamChar;
   t_pchar        pErrorText;
   t_int           Assign;
   t_int           ParamLen;

   if (pCfgParamDesc)
      CFG_CHK_APP (ToolCfgCheckAssignment (pKeyWord, pCfgParamDesc, &Assign))
   pCfgDataDesc = &pCfgParamDesc->DataDesc;
   if (pCfgDataDesc->CfgType == CFGTYPE_PRESENCE)
   {
      CFG_CHK_APP (ToolCfgCheckIfNoParam (pNextChar))
      *((t_pint)(pCfgParamDesc->DataDesc.DestAddr)) = TRUE;
   }
   else
   {
      CFG_CHK_APP (ToolCfgCheckEqualSign  (pNextChar, (const char **) &pFirstParamChar))
      CFG_CHK_APP (ToolCfgCheckIfOnlyOneParam (pFirstParamChar, &ParamLen))
      CFG_CHK_APP (ToolCfgReadParameter0 (pFirstParamChar, ParamLen, pCfgDataDesc, 0, Assign))
   }
   if (Assign)
   {
      if (ToolCfgScanningCmdLine())
           pCfgParamDesc->CmdAssignments++;
      else pCfgParamDesc->CfgAssignments++;
   }

   if (pCfgParamDesc->pCallOnInitFn)
   {
      CFG_CHK_APP ((*pCfgParamDesc->pCallOnInitFn)(pCfgParamDesc, &pErrorText))
      if (pErrorText)
      {
         CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, pErrorText))
         return TOOLCFG_ERROR_CONFIG_ERROR;
      }
   }

   return NO_ERROR;
}

static APIRET ToolCfgAnalyseEntry (t_pchar pActLine, t_int LineLen,
                               t_pToolCfgParamDesc pParamDescArray,
                               t_pToolCfgTableDesc pTableDescArray)
{
   t_pchar        pKeyWord;
   t_pchar        pNextChar;
   t_pToolCfgDataDesc pCfgDataDesc;
   t_int           i;
   t_int           RemLineLen;
   t_int           Eq, EqDef, EqUnDef;
   t_char          TmpChar;
   t_pchar        pSectionName;
   t_int           SectionNameLen;
   APIRET          rc;

   if (LineLen <=0) /* it's an empty line, unnecessary to continue */
      return NO_ERROR;

   TmpChar = pActLine[LineLen];  /* set end of line to '\0' to ease processing */
   pActLine[LineLen] = '\0';

   CFG_CHK_APP (ToolCfgFindFirstWord (pActLine, (const char **) &pKeyWord, LineLen, &RemLineLen))
   if (pKeyWord == NULL)             // Were there just spaces or tabs in this line ?
   {
      pActLine[LineLen] = TmpChar;
      return NO_ERROR;
   }

   CFG_CHK_APP (ToolCfgCompareKeyWord (KEYWORD_TABLESTART, pKeyWord, RemLineLen, &Eq, NULL))
   if (Eq)
   {
      pActLine[LineLen] = TmpChar;
      CFG_CHK_APP (ToolCfgScanTable(pActLine, LineLen, pTableDescArray))
      return NO_ERROR;
   }

   CFG_CHK_APP (ToolCfgCompareKeyWord (KEYWORD_REMARK, pKeyWord, RemLineLen, &Eq, NULL))
   if (Eq)
   {
      pActLine[LineLen] = TmpChar;
      return NO_ERROR;
   }

   CFG_CHK_APP    (ToolCfgCompareKeyWord (KEYWORD_DEFINE  , pKeyWord, RemLineLen, &EqDef  , &pSectionName))
   if (!EqDef)
      CFG_CHK_APP (ToolCfgCompareKeyWord (KEYWORD_UNDEFINE, pKeyWord, RemLineLen, &EqUnDef, &pSectionName))
   if (EqDef || EqUnDef)
   {
      while (((*pSectionName == ' ') || (*pSectionName == '\t')) && (*pSectionName != '\0'))
         pSectionName++;

      CFG_CHK_APP (ToolCfgCheckIfOnlyOneParam (pSectionName, &SectionNameLen))
      pActLine[LineLen] = TmpChar;
      TmpChar = pSectionName[SectionNameLen];
      pSectionName[SectionNameLen] = '\0';
      if (EqDef)
           rc = ToolCfgAddGlobalSectionName (pSectionName);
      else rc = ToolCfgDelGlobalSectionName (pSectionName);
      switch (rc)
      {
         case TOOLCFG_ERROR_SECTIONNAME_TOO_LONG :
            CFG_CHK_APP (ToolCfgStdErrorHeader (pSectionName))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Section name too long (Max. %d characters).", MAX_SECTIONNAME_LEN))
            return TOOLCFG_ERROR_CONFIG_ERROR;

         case TOOLCFG_ERROR_TOO_MANY_SECTIONNAMES:
            CFG_CHK_APP (ToolCfgStdErrorHeader (pSectionName))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Too many global section names defined (Max. is %d).", MAX_GLOBALSECTIONNAMES))
            return TOOLCFG_ERROR_CONFIG_ERROR;

         case TOOLCFG_ERROR_SECTIONNAME_NOTFOUND:
            CFG_CHK_APP (ToolCfgStdErrorHeader (pSectionName))
            CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "The section name %s is not defined.", pSectionName))
            return TOOLCFG_ERROR_CONFIG_ERROR;

         default: CFG_CHK_APP (rc)
      }
      pSectionName[SectionNameLen] = TmpChar;

      return NO_ERROR;
   }

   Eq = FALSE;
   if (pParamDescArray != NULL)
   {
      for (i=0; ; i++)
      {
         pCfgDataDesc = &pParamDescArray[i].DataDesc;
         if (pCfgDataDesc->pName == NULL)
            break;/* end of array */
         CFG_CHK_APP (ToolCfgCompareKeyWord (pCfgDataDesc->pName, pKeyWord, RemLineLen, &Eq, &pNextChar))
         if (Eq)
         {
            CFG_CHK_APP (ToolCfgReadParameter (pKeyWord, pNextChar, &pParamDescArray[i]))
            break;
         }
      }
   }

   if (!Eq) /* keyword not found in table */
   {
      CFG_CHK_APP (ToolCfgStdErrorHeader (pKeyWord))
      CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Invalid keyword."))
      return TOOLCFG_ERROR_CONFIG_ERROR;
   }

   pActLine[LineLen] = TmpChar;

   return NO_ERROR;
}

/* CfgFreeContextBuffers: Free all context buffers that are still in use */
static APIRET ToolCfgFreeContextBuffers (void)
{
   t_pToolCfgContext pCfgContext;
   t_int          i;

   for (i=ToolCfgLocal.IncludeNestingLevel; i>0; i--)
   {
      pCfgContext = &ToolCfgLocal.pCfgContextStack[i];
      if (pCfgContext->pBuffer)
         CFG_CHK_APP (ToolCfgMemFree (pCfgContext->pBuffer, MemIdCfgBuffer))
   }

   return NO_ERROR;
}

static APIRET ToolCfgScanParams (t_pToolCfgParamDesc pParamDescArray, t_pToolCfgTableDesc pTableDescArray)
{
   t_pchar pActLine;
   t_int    LineLen;
   APIRET   rc;

   CFG_CHK_APP (ToolCfgGetNextLine (&pActLine, &LineLen))
   rc = NO_ERROR;
   while (pActLine && !rc)
   {
      rc = ToolCfgAnalyseEntry(pActLine, LineLen, pParamDescArray, pTableDescArray);
      if (!rc)
         CFG_CHK_APP (ToolCfgGetNextLine (&pActLine, &LineLen))
   }
   CFG_CHK_APP (ToolCfgFreeContextBuffers())
   CFG_CHK_APP (rc)

   return NO_ERROR;
}

static APIRET ToolCfgScanSource (t_pcchar pFileName,
                                 t_pcchar pSectionName,
                                 t_pToolCfgParamDesc pParamDescArray,
                                 t_pToolCfgTableDesc pTableDescArray)
{
   APIRET rc;
   t_int  ContextStackSize;
   t_int  i;

   ContextStackSize = ToolCfgLocal.MaxIncludeNestingLevel * (t_int) sizeof (t_ToolCfgContext);
   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&ToolCfgLocal.pCfgContextStack, (t_uint)ContextStackSize, MEM_OPT_NONE, MemIdCfgContextStack))
   ToolCfgLocal.pActCfgContext      = &ToolCfgLocal.pCfgContextStack[0];
   ToolCfgLocal.IncludeNestingLevel = 0;

   /* initialise first context stack entry */
   ToolCfgLocal.pActCfgContext->BufferLen      = 0;
   ToolCfgLocal.pActCfgContext->ActLineNr      = 0;
   ToolCfgLocal.pActCfgContext->pActLine       = NULL;
   ToolCfgLocal.pActCfgContext->pBuffer        = NULL;
   ToolCfgLocal.pActCfgContext->SectionNesting = 0;
   ToolCfgLocal.pActCfgContext->State          = CFGSTATE_INCLUDENEWFILE;
   CFG_CHK_APP (ToolCfgCopyName (pFileName   , & ToolCfgLocal.pActCfgContext->FileName         [0] , MAX_FILENAME_LEN   ))
   CFG_CHK_APP (ToolCfgCopyName (pSectionName, &(ToolCfgLocal.pActCfgContext->SectionNameArr[0][0]), MAX_SECTIONNAME_LEN))
   for (i=1; i<MAX_SECTIONNAMES; i++)
      ToolCfgLocal.pActCfgContext->SectionNameArr[i][0] = '\0';

   rc = ToolCfgScanParams (pParamDescArray, pTableDescArray);
   CFG_CHK_APP (ToolCfgMemFree (ToolCfgLocal.pCfgContextStack, MemIdCfgContextStack))
   CFG_CHK_APP (rc)

   return NO_ERROR;
}

/* CfgCheckInitialisation: Check whether all parameters have been initialized. */
static APIRET ToolCfgCheckInitialisation(t_pToolCfgParamDesc pParamDescArray)
{
   t_int i, Err;
   t_pToolCfgParamDesc pCfgParamDesc;
   t_pToolCfgDataDesc  pCfgDataDesc;

   Err = FALSE;
   for (i=0; ;i++)
   {
      pCfgParamDesc = &pParamDescArray[i];
      pCfgDataDesc  = &pCfgParamDesc->DataDesc;
      if (pCfgDataDesc->pName == NULL)
         break; /* end of array reached */
      if (( pCfgParamDesc->CfgAssignments   == 0               ) &&
          ( pCfgParamDesc->CmdAssignments   == 0               ) &&
          ( pCfgParamDesc->DataDesc.CfgType != CFGTYPE_NULL    ) &&
          ( pCfgParamDesc->DataDesc.CfgType != CFGTYPE_PRESENCE) &&    // Parameters of type 'presence' are always optional
          ((pCfgParamDesc->Assign & CFGASN_OPTIONAL) == 0      ))
      {
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Configuration error: Parameter %s has not been initialized.", pCfgDataDesc->pName))
         Err = TRUE;
      }
   }
   if (Err)
      return TOOLCFG_ERROR_CONFIG_ERROR;

   return NO_ERROR;
}

static APIRET ToolCfgLogGetSetName (t_pToolCfgSet pSetArray, t_int SetValue, t_pcchar *ppSetString)
{
   t_int i;

   *ppSetString = NULL;
   for (i=0; ; i++)
   {
      if (pSetArray[i].pSetString == NULL)
         break; /* end of array */
      if (pSetArray[i].SetValue == SetValue)
      {
         *ppSetString = pSetArray[i].pSetString;
         break;
      }
   }

   return NO_ERROR;
}

static APIRET ToolCfgPrintContents (t_pchar pBuff, t_pToolCfgParamDesc pCfgParamDesc)
{
   t_pToolCfgDataDesc pCfgDataDesc;
   t_pchar        pSetString;
   t_int           Hms;
   t_int           SetValue;

   if ((pCfgParamDesc->CfgAssignments == 0) &&
       (pCfgParamDesc->CmdAssignments == 0))
   {
      sprintf (pBuff, "not initialized");
   }
   else
   {
      pCfgDataDesc = &pCfgParamDesc->DataDesc;
      switch (pCfgDataDesc->CfgType)
      {
         case CFGTYPE_PRESENCE:sprintf (pBuff, "%s"       , *(int   *)pCfgDataDesc->DestAddr ? "present" : "not present");     break;
         case CFGTYPE_INTEGER: sprintf (pBuff, "%d (0x%X)", *(int   *)pCfgDataDesc->DestAddr, *(int *)pCfgDataDesc->DestAddr); break;
         case CFGTYPE_DOUBLE:  sprintf (pBuff, "%G"       , *(double*)pCfgDataDesc->DestAddr);                                 break;
         case CFGTYPE_STRING:  sprintf (pBuff, "%s"       ,  (char  *)pCfgDataDesc->DestAddr);                                 break;

         case CFGTYPE_SET:     SetValue = *(int *)pCfgDataDesc->DestAddr;
                               CFG_CHK_APP (ToolCfgLogGetSetName (pCfgDataDesc->pSetArray, SetValue, (const char **) &pSetString))
                               if (pSetString == NULL)
                                    sprintf (pBuff, "Set str unknown for value %d", SetValue);
                               else sprintf (pBuff, "%s", pSetString);
            break;
         case CFGTYPE_HMS:     Hms = (*(int *)pCfgDataDesc->DestAddr);
                               sprintf (pBuff, "%02d:%02d:%02d", Hms/3600, (Hms/60)%60, Hms%60);
            break;
         case CFGTYPE_NULL:    break;
         default:
            return TOOLCFG_ERROR_INVALID_CFGTYPE;
      }
   }
   return NO_ERROR;
}

APIRET ToolCfgPrintParamContents (t_pToolCfgParamDesc pParamDescArray, t_pchar pName, t_pchar pBuff)
{
   t_pToolCfgParamDesc pCfgParamDesc;
   t_pToolCfgDataDesc  pCfgDataDesc;
   t_int            i;

   if (pBuff)
      pBuff[0] = '\0';
   for (i=0; ;i++)
   {
      pCfgParamDesc = &pParamDescArray[i];
      pCfgDataDesc  = &pCfgParamDesc->DataDesc;
      if (pCfgDataDesc->pName == NULL)
         return TOOLCFG_ERROR_UNKNOWN_PARAMETER;
      if (ToolCfgStrCmpNoCase (pCfgDataDesc->pName, pName) == 0)
      {
         if (pBuff)
            CFG_CHK_APP (ToolCfgPrintContents (pBuff, pCfgParamDesc))
         break;
      }
   }

   return NO_ERROR;
}

/* ToolCfgLogConfiguration: Write all the configuration parameters with their values to the log file */
APIRET ToolCfgLogConfiguration (t_pToolCfgParamDesc pParamDescArray)
{
   t_pToolCfgParamDesc pCfgParamDesc;
   t_pToolCfgDataDesc  pCfgDataDesc;
   t_pchar         pBuff;
   t_int            i, Err;
   t_int            wr;

   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pBuff, 4096, MEM_OPT_NONE, MemIdCfgLogConfiguration))
   Err = FALSE;
   for (i=0; ;i++)
   {
      pCfgParamDesc = &pParamDescArray[i];
      pCfgDataDesc  = &pCfgParamDesc->DataDesc;
      if (pCfgDataDesc->pName == NULL)
         break; /* end of array reached */
      if (( pCfgParamDesc->CfgAssignments   == 0               ) &&
          ( pCfgParamDesc->CmdAssignments   == 0               ) &&
          ( pCfgParamDesc->DataDesc.CfgType != CFGTYPE_NULL    ) &&
          ( pCfgParamDesc->DataDesc.CfgType != CFGTYPE_PRESENCE) &&    // Parameters of type 'presence' are always optional
          ((pCfgParamDesc->Assign & CFGASN_OPTIONAL) == 0      ))
      {
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "Parameter %s has not been initialized.", pCfgDataDesc->pName))
         Err = TRUE;
      }
      else
      {
         wr = sprintf (pBuff, "%s = ", pCfgDataDesc->pName);
         CFG_CHK_APP (ToolCfgPrintContents (&pBuff[wr], pCfgParamDesc))
         CFG_CHK_APP (ToolCfgLogEntry(__FFL__, "%s", pBuff))
      }
   }
   CFG_CHK_APP (ToolCfgMemFree (pBuff, MemIdCfgLogConfiguration))
   if (Err)
      return TOOLCFG_ERROR_CONFIG_ERROR;

   return NO_ERROR;
}

/* ToolCfgResetAssignCounters: Reset all assignment counters for all keywords */
static APIRET ToolCfgResetAssignCounters (t_pToolCfgParamDesc pParamDescArray)
{
   t_int i;

   for (i=0; ; i++)
   {
      if (pParamDescArray[i].DataDesc.pName == NULL)
         break; /* end of array reached */
      pParamDescArray[i].CmdAssignments = 0;
      pParamDescArray[i].CfgAssignments = 0;
   }

   return NO_ERROR;
}

/* CfgGetCmdLineOption: Read an option from the command line. This function is */
/* usually called to get the configuration or log file name.                   */
APIRET ToolCfgGetCmdLineOption (t_pcchar pParamName, t_pcchar *ppValue)
{
   t_int        i, Eq;
   t_int        LineLen  = 0;
   t_pchar     pNextChar = NULL;
   t_ToolCfgContext CfgContext;

   *ppValue = NULL;
   Eq = 0;
   for (i=1; i<ToolCfgLocal.argc; i++)
   {
      LineLen = (t_int) strlen (ToolCfgLocal.argv[i]);
      CFG_CHK_APP (ToolCfgCompareKeyWord (pParamName, ToolCfgLocal.argv[i], LineLen, &Eq, &pNextChar))
      if (Eq)
         break;
   }
   if (!Eq)
      return TOOLCFG_ERROR_CMDLINE_OPTION_NOT_FOUND;
   CfgContext.FileName[0] = '\0';               // build pseudo context, to have CfgStdErrorHeader
   CfgContext.pActLine    = ToolCfgLocal.argv[i];   // (called by ToolCfgCheckEqualSign) work correctly
   CfgContext.ActLineLen  = LineLen;
   CfgContext.ActLineNr   = i;
   ToolCfgLocal.pActCfgContext= &CfgContext;        /*lint !e789 */ /* Info 789: Assigning address of auto (CfgContext) to static */
   CFG_CHK_APP (ToolCfgCheckEqualSign (pNextChar, ppValue))

   return NO_ERROR;
}

/* CfgScanConfiguration: API functions to scan the configuration. Two subcalls */
/* have to be done: Scan the command line and scan the configuration file.     */
APIRET ToolCfgScanConfiguration (t_pcchar pFileName, t_pcchar pSectionName,
                             t_ToolCfgParamDesc *pParamDescArray,
                             t_ToolCfgTableDesc *pTableDescArray)
{
   CFG_CHK_APP (ToolCfgResetAssignCounters (pParamDescArray))
   ToolCfgLocal.Priority = PRIORITY_HIGH;   /* we will now scan the command line params which have high priority */
   CFG_CHK_APP (ToolCfgScanSource (NULL, pSectionName, pParamDescArray, pTableDescArray))
   if (pFileName != NULL)
   {
      ToolCfgLocal.Priority = PRIORITY_LOW;    /* cfg file params with low priority */
      CFG_CHK_APP (ToolCfgScanSource (pFileName, pSectionName, pParamDescArray, pTableDescArray))
   }
   CFG_CHK_APP (ToolCfgCheckInitialisation (pParamDescArray))

   return NO_ERROR;
}

/* ----------------------------------- */
/*  Build configuration file template  */
/* ----------------------------------- */

static APIRET ToolCfgEnterRemark (FILE *pFile, t_pcchar pRemark, t_int Indent, t_int LineFlag)
{
   t_int rcp;
   t_int LineLen;
   t_int i;

   rcp = fprintf (pFile, "\r\n%*s%s %s", Indent, "", KEYWORD_REMARK, pRemark);
   if ((rcp > 0) && (LineFlag))
   {
      LineLen = (t_int) strlen (pRemark);
      rcp = fprintf (pFile, "\r\n%*s%s ", Indent, "", KEYWORD_REMARK);
      CFG_CHK_FPRINTF(rcp)
      for (i=0; i<LineLen; i++)
      {
         rcp = fprintf (pFile, "-");
         CFG_CHK_FPRINTF(rcp)
      }
   }

   return NO_ERROR;
}

static APIRET ToolCfgEnterEmptyLine (FILE *pFile)
{
   CFG_CHK_FPRINTF(fprintf (pFile, "\r\n"))

   return NO_ERROR;
}

static APIRET ToolCfgBuildTemplate0 (FILE *pFile, t_int CfgParams, t_pint pEntries,
                                     t_pToolCfgParamDesc pParamDescArray,
                                     t_pToolCfgTableDesc pTableDescArray)
{
   t_pToolCfgDataDesc  pCfgDataDesc;
   t_pToolCfgTableDesc pCfgTableDesc;
   t_pchar        pTmp1, pTmp2;
   t_int           i, Len;
   t_ToolCfgAssignment Source;

   if (pParamDescArray)
   {
      *pEntries = 0;
      for (i=0; ; i++)
      {
         pCfgDataDesc = &pParamDescArray[i].DataDesc;
         if (pCfgDataDesc->pName == NULL)
            break; /* end of array reached */

         Source = (t_ToolCfgAssignment) (pParamDescArray[i].Assign & CFGASN_SOURCE);

         if (((Source & CFGASN_CMD) && !(Source & CFGASN_CFG) && (CfgParams == FALSE)) ||
             ((Source & CFGASN_CFG) &&                           (CfgParams == TRUE )))
         {
            CFG_CHK_APP (ToolCfgBuildHelp (pCfgDataDesc, NULL, &Len))
            CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp1, (t_uint)Len, MEM_OPT_NONE, MemIdCfgHelpBuff))
            CFG_CHK_APP (ToolCfgBuildHelp (pCfgDataDesc, pTmp1, &Len))
            CFG_CHK_FPRINTF (fprintf (pFile, "\r\n   %s", pTmp1))
            CFG_CHK_APP (ToolCfgMemFree (pTmp1, MemIdCfgHelpBuff))
            (*pEntries)++;
         }
      }
   }
   if (CfgParams && pTableDescArray)
   {
      for (i=0; ; i++)
      {
         pCfgTableDesc = &pTableDescArray[i];
         if (pCfgTableDesc->pTableType == NULL)
            break; /* end of array reached */
         CFG_CHK_APP (ToolCfgBuildTableHelp (pCfgTableDesc, NULL, NULL, &Len))
         CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp1, (t_uint)Len, MEM_OPT_NONE, MemIdCfgHelpBuff))
         CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp2, (t_uint)Len, MEM_OPT_NONE, MemIdCfgHelpBuff))
         CFG_CHK_APP (ToolCfgBuildTableHelp (pCfgTableDesc, pTmp1, pTmp2, &Len))
         CFG_CHK_FPRINTF (fprintf (pFile, "\r\n\r\n   %s %s <TableName>", KEYWORD_TABLESTART, pCfgTableDesc->pTableType))
         CFG_CHK_APP (ToolCfgEnterRemark (pFile, pTmp1, 6, TRUE  ))
         CFG_CHK_APP (ToolCfgEnterRemark (pFile, pTmp2, 6, FALSE))
         CFG_CHK_FPRINTF (fprintf (pFile, "\r\n   %s", KEYWORD_TABLEEND))
         CFG_CHK_APP (ToolCfgMemFree (pTmp1, MemIdCfgHelpBuff))
         CFG_CHK_APP (ToolCfgMemFree (pTmp2, MemIdCfgHelpBuff))
         (*pEntries)++;
      }
   }
   return NO_ERROR;
}

APIRET ToolCfgBuildTemplate (t_pcchar pFileName, t_pcchar pComment,
                             t_pcchar pSectionName,
                             t_ToolCfgParamDesc *pParamDescArray,
                             t_ToolCfgTableDesc *pTableDescArray)
{
   FILE          *pFile;
   t_int           Entries, rc;
   t_pchar        pTmp;
   const t_int     TmpLen = 256;
   struct tm      *NowTM;
   time_t          NowT;

   pFile = fopen(pFileName, "wb");
   if (pFile == NULL)
      return TOOLCFG_ERROR_TEMPLATE_OPEN_FAILED;
   setbuf (pFile, NULL);

   CFG_CHK_APP (ToolCfgMemAlloc ((void **)&pTmp, TmpLen, MEM_OPT_NONE, MemIdCfgTemplateHeader))
   (void) time (&NowT);
   NowTM = localtime (&NowT);
   rc = sprintf  (pTmp, "Configuration file template, created on ");
   (void) strftime (pTmp+rc, (size_t)(TmpLen-rc), "%d/%m/%Y %H:%M:%S", NowTM);
   CFG_CHK_APP (ToolCfgEnterRemark (pFile, pTmp, 0, TRUE))
   CFG_CHK_APP (ToolCfgMemFree (pTmp, MemIdCfgTemplateHeader))

   if(pComment)
      CFG_CHK_APP (ToolCfgEnterRemark (pFile, pComment, 0, TRUE))

   if (pSectionName)
      CFG_CHK_FPRINTF (fprintf (pFile, "\r\n\r\n%s %s\r\n", KEYWORD_SECTIONSTART, pSectionName))
   CFG_CHK_APP (ToolCfgBuildTemplate0 (pFile, TRUE, &Entries, pParamDescArray, pTableDescArray))
   if (pSectionName)
      CFG_CHK_FPRINTF (fprintf (pFile, "\r\n\r\n%s", KEYWORD_SECTIONEND))

   CFG_CHK_APP (ToolCfgEnterEmptyLine (pFile))
   CFG_CHK_APP (ToolCfgEnterRemark (pFile, "Options, that can only be entered on command line", 0, TRUE))
   CFG_CHK_APP (ToolCfgBuildTemplate0 (pFile, FALSE, &Entries, pParamDescArray, pTableDescArray))
   if (Entries == 0)
      CFG_CHK_APP (ToolCfgEnterRemark (pFile, "There are no such options", 0, FALSE))
   CFG_CHK_APP (ToolCfgEnterEmptyLine (pFile))
   CFG_CHK_APP (ToolCfgEnterEmptyLine (pFile))

   rc = fclose(pFile);
   if (rc)
      return TOOLCFG_ERROR_CLOSE_FAILED;

   return NO_ERROR;
}

/* ToolCfgSetErrLogFn: The application can specify the desired logging */
/* function here (for example vprintf, or an own function).            */
APIRET ToolCfgSetLogFn (t_pToolCfgUserLogFn pUserLogFn)
{
   ToolCfgLocal.pUserLogFn = pUserLogFn;

   return NO_ERROR;
}

APIRET ToolCfgGetSetString (t_pToolCfgSet pSetArray, int SetValue, const char **ppSetString)
{
   t_pToolCfgSet pSet;

   *ppSetString = NULL;
   for (pSet = &pSetArray[0]; pSet->pSetString != NULL; pSet++)
   {
      if (pSet->SetValue == SetValue)
      {
         *ppSetString = pSet->pSetString;
         break;
      }
   }

   return NO_ERROR;
}

/* ------------------------------ */
/*     Module initialisation      */
/* ------------------------------ */
static int IsInit = 0;

APIRET ToolCfgInit (int argc, char *argv[])
{
   if(IsInit == 1)
       return NO_ERROR;
   IsInit = 1;

   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (ERROR_BASE_TOOL_CFG                    ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_OPEN_FAILED              ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_READ_FAILED              ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_CLOSE_FAILED             ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_SEEKEND_FAILED           ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_SEEKSET_FAILED           ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_INVALID_STATE            ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_NAME_TOO_LONG            ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_INCLUDE_NESTING_OVERFLOW ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_CONFIG_ERROR             ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_INVALID_ASSIGNMENT_OPTION))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_INVALID_CFGTYPE          ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_TEMPLATE_OPEN_FAILED     ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_FPRINTF_FAILED           ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_CMDLINE_OPTION_NOT_FOUND ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_MALLOC_FAILED            ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_SECTIONNAME_TOO_LONG     ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_TOO_MANY_SECTIONNAMES    ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_SECTIONNAME_NOTFOUND     ))
   CFG_CHK_APP (TOOL_ERROR_REGISTER_CODE (TOOLCFG_ERROR_UNKNOWN_PARAMETER        ))

   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgBuffer          ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgErrorHeader     ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgContextStack    ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgTemplateHeader  ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgHelpBuff        ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgHelpString      ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgHelpInt         ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgHelpTable       ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgTableType       ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgTableName       ))
   CFG_CHK_APP (MEM_REGISTER_MEMID (MemIdCfgLogConfiguration))

   ToolCfgLocal.argc = argc;
   ToolCfgLocal.argv = argv;
   ToolCfgLocal.pUserLogFn = NULL;
   ToolCfgLocal.MaxIncludeNestingLevel = MAX_CFG_NESTING_LEVEL;
   memset (&ToolCfgLocal.GlobalSectionNameArr[0], 0, sizeof (ToolCfgLocal.GlobalSectionNameArr));

   return NO_ERROR;
}

APIRET ToolCfgUseAdjustedCommandLine (int argc, char *argv[])
{
   ToolCfgLocal.argc = argc;
   ToolCfgLocal.argv = argv;
   return NO_ERROR;
}

APIRET ToolCfgDeInit (void)
{
    if(IsInit == 0)
        return NO_ERROR;
    CFG_CHK_APP (ToolCfgFreeContextBuffers ())
    IsInit = 0;
    return NO_ERROR;
}

