#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLinkedHeap.h"
#include "cmFileSys.h"
#include "cmText.h"

#include <sys/stat.h>
#include <errno.h>
#include <libgen.h> // basename(), dirname()
#include <dirent.h> // opendir()/readdir()
#include <limits.h> // PATH_MAX
#include <sys/types.h> // mkdir

#ifdef OS_OSX
#include "osx/cmFileSysOsx.h"
#endif

#ifdef OS_LINUX
#include "linux/cmFileSysLinux.h"
#endif

cmFileSysH_t cmFileSysNullHandle = {NULL};

typedef struct
{
  cmErr_t    err;
  cmLHeapH_t heapH;
  const cmChar_t* appNameStr;
#ifdef OS_OSX
  _cmFsOsx_t* p;
  cmChar_t* prefDir;
#endif
#ifdef OS_LINUX
  _cmFsLinux_t* p;
#endif
} cmFs_t;

cmFsRC_t _cmFileSysError( cmFs_t* p, cmFsRC_t rc, int sysErr, const cmChar_t* fmt, ... )
{
  va_list vl;
  va_start(vl,fmt);
  
  if( sysErr == 0 )
    rc = cmErrVMsg(&p->err,rc,fmt,vl);
  else
  {
    const unsigned bufCharCnt = 511;
    cmChar_t buf[bufCharCnt+1];
    vsnprintf(buf,bufCharCnt,fmt,vl);
    rc = cmErrMsg(&p->err,rc,"%s\nSystem Msg:%s",buf,strerror(sysErr));
  }

  va_end(vl);
  return rc;
}


cmFs_t* _cmFileSysHandleToPtr( cmFileSysH_t h )
{
  cmFs_t* p = (cmFs_t*)h.h;
  assert( p != NULL);
  return p;
}

cmFsRC_t _cmFileSysFinalize( cmFs_t* p )
{
  cmFsRC_t rc = kOkFsRC;

  if( cmLHeapIsValid(p->heapH) )
    cmLHeapDestroy(&p->heapH);

#ifdef OS_OSX
  if( p->p != NULL )
    if((rc = _cmOsxFileSysFinalize(p->p) ) != kOkFsRC )
    {
      _cmFileSysError(p,kOsxFailFsRC,0,"The OSX file system finalization failed.");
      return rc;
    }
#endif

#ifdef OS_LINUX
  if( p->p != NULL )
    if((rc = _cmLinuxFileSysFinalize(p->p) ) != kOkFsRC )
    {
      _cmFileSysError(p,kLinuxFailFsRC,0,"The Linux file system finalization failed.");
      return rc;
    }
#endif

  cmMemPtrFree(&p);

  return rc;
}
 
cmFsRC_t cmFileSysInitialize( cmFileSysH_t* hp, cmCtx_t* ctx, const cmChar_t* appNameStr )
{
  cmFs_t* p;
  cmFsRC_t rc;
  cmErr_t err;

  if((rc = cmFileSysFinalize(hp)) != kOkFsRC )
    return rc;

  cmErrSetup(&err,&ctx->rpt,"File System");

  if((p = cmMemAllocZ( cmFs_t, 1 )) == NULL )
    return cmErrMsg(&err,kMemAllocErrFsRC,"Unable to allocate the file system object.");

  cmErrClone(&p->err,&err);

  if(cmLHeapIsValid( p->heapH = cmLHeapCreate(1024,ctx)) == false )
  {
    rc =  _cmFileSysError(p,kLHeapAllocErrFsRC,0,"Unable to allocate the linked heap.");
    goto errLabel;
  }

  p->appNameStr =  cmLhAllocStr(p->heapH,appNameStr);

  hp->h = p;

#ifdef OS_OSX
  if( (rc = _cmOsxFileSysInit(&p->p, p->heapH, &p->err)) != kOkFsRC )
  {
    rc = _cmFileSysError(p,kOsxFailFsRC,0,"OSX file system initialization failed.");
    goto errLabel;
  }

  const cmChar_t* dir = cmFsMakeFn(p->p->prefDir,appNameStr,NULL,NULL);

  // BUG?
  // we reuse p->p->prefDir here because the one returned by the platform
  // specific code is never released  ... which isn't quite right either
  // See osx/cmFileSysOsx.c.
  p->p->prefDir = cmLhAllocStr(p->heapH,dir);
  cmFsFreeFn(dir);
#endif

#ifdef OS_LINUX
  if( (rc = _cmLinuxFileSysInit(&p->p, p->heapH, &p->err)) != kOkFsRC )
  {
    rc = _cmFileSysError(p,kLinuxFailFsRC,0,"Linux file system initialization failed.");
    goto errLabel;
  }
  else
  {
#endif


#ifdef OS_LINUX

  cmChar_t hidAppNameStr[ strlen(appNameStr) + 2 ];

  strcpy(hidAppNameStr,".");
  strcat(hidAppNameStr,appNameStr);

  p->p->prefDir = cmFileSysMakeFn( *hp, p->p->prefDir, hidAppNameStr, NULL, NULL  );

  // the resource directory must exist before the program can start
  p->p->rsrcDir = cmFileSysMakeFn( *hp, p->p->rsrcDir, appNameStr, NULL, NULL );
  }
#endif

 errLabel:
  if( rc != kOkFsRC )
    return _cmFileSysFinalize(p);

  return kOkFsRC;
}

cmFsRC_t cmFileSysFinalize(   cmFileSysH_t* hp )
{
  cmFsRC_t rc;

  if( hp==NULL || cmFileSysIsValid(*hp) == false )
    return kOkFsRC;

  cmFs_t* p = _cmFileSysHandleToPtr(*hp);

  if((rc = _cmFileSysFinalize(p)) != kOkFsRC )
    return rc;

  hp->h = NULL;

  return rc;
}

const cmChar_t* cmFileSysAppName( cmFileSysH_t h )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);
  return p->appNameStr;
}

const cmChar_t* cmFileSysPrefsDir( cmFileSysH_t h )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);
#if defined OS_OSX || defined OS_LINUX
  return p->p->prefDir;
#else
  return NULL;
#endif
}

const cmChar_t* cmFileSysRsrcDir( cmFileSysH_t h )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);
#if defined OS_OSX || defined OS_LINUX
  return p->p->rsrcDir;
#else
  return NULL;
#endif
}

const cmChar_t* cmFileSysUserDir( cmFileSysH_t h )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);
#if defined OS_OSX || defined OS_LINUX
  return p->p->userDir;
#else
  return NULL;
#endif
}


bool     cmFileSysIsValid(    cmFileSysH_t h )
{ return h.h != NULL; }

bool _cmFileSysIsDir( cmFs_t* p, const cmChar_t* dirStr )
{
  struct stat s;

  errno = 0;

  if( stat(dirStr,&s)  != 0 )
  {
    // if the dir does not exist
    if( errno == ENOENT )
      return false;

    _cmFileSysError( p, kStatFailFsRC, errno, "'stat' failed on '%s'",dirStr);
    return false;
  }
 
  return S_ISDIR(s.st_mode);
}

bool cmFileSysCanWriteToDir( cmFileSysH_t h, const cmChar_t* dirStr )
{
  cmFs_t*     p = _cmFileSysHandleToPtr(h);
  int result;

  errno = 0;

  if((result = access(dirStr,W_OK)) == 0 )
    return true;

  if( result == EACCES || result==EROFS )
    return false;

  _cmFileSysError( p, kAccessFailFsRC, errno, "'access' failed on '%s'.",dirStr);

  return false;  
}

bool cmFileSysIsDir( cmFileSysH_t h, const cmChar_t* dirStr )
{
  cmFs_t*     p = _cmFileSysHandleToPtr(h);
  return _cmFileSysIsDir(p,dirStr);
}

bool _cmFileSysIsFile( cmFs_t* p, const cmChar_t* fnStr )
{
  struct stat s;
  errno = 0;

  if( stat(fnStr,&s)  != 0 )
  {

    // if the file does not exist
    if( errno == ENOENT )
      return false;

    _cmFileSysError( p, kStatFailFsRC, errno, "'stat' failed on '%s'.",fnStr);
    return false;
  }
 
  return S_ISREG(s.st_mode);
}

bool cmFileSysIsFile( cmFileSysH_t h, const cmChar_t* fnStr )
{
  cmFs_t*     p = _cmFileSysHandleToPtr(h);

  return _cmFileSysIsFile(p,fnStr);
}

bool _cmFileSysIsLink( cmFs_t* p, const cmChar_t* fnStr )
{
  struct stat s;
  errno = 0;

  if( lstat(fnStr,&s)  != 0 )
  {

    // if the file does not exist
    if( errno == ENOENT )
      return false;

    _cmFileSysError( p, kStatFailFsRC, errno, "'stat' failed on '%s'.",fnStr);
    return false;
  }
 
  return S_ISLNK(s.st_mode);
}

bool cmFileSysIsLink( cmFileSysH_t h, const cmChar_t* fnStr )
{
  cmFs_t*     p = _cmFileSysHandleToPtr(h);

  return _cmFileSysIsLink(p,fnStr);
}

bool _cmFileSysIsSocket( cmFs_t* p, const cmChar_t* fnStr )
{
  struct stat s;
  errno = 0;

  if( stat(fnStr,&s)  != 0 )
  {

    // if the file does not exist
    if( errno == ENOENT )
      return false;

    _cmFileSysError( p, kStatFailFsRC, errno, "'stat' failed on '%s'.",fnStr);
    return false;
  }
 
  return S_ISSOCK(s.st_mode);
}


bool _cmFileSysConcat( cmChar_t* rp, unsigned rn, char sepChar, const cmChar_t* suffixStr )
{
  unsigned m = strlen(rp);

  // m==0 if no sep needs to be inserted or removed

  //if( m == 0 )
  //  return false;

  if( m != 0 )
  {

    // if a sep char needs to be inserted
    if( rp[m-1] != sepChar && suffixStr[0] != sepChar )
    {
      assert((m+1)<rn);

      if((m+1)>=rn)
        return false;

      rp[m]  = sepChar;
      rp[m+1]= 0;
      ++m;
    }  
    else
      // if a sep char needs to be removed
      if( rp[m-1] == sepChar && suffixStr[0] == sepChar )
      {
        rp[m-1] = 0;
        --m;
      }

  }

  assert( rn>=m && strlen(rp)+strlen(suffixStr) <= rn );
  strncat(rp,suffixStr,rn-m);

  return true;
}

const cmChar_t* cmFileSysVMakeFn( cmFileSysH_t h, const cmChar_t* dir, const cmChar_t* fn, const cmChar_t* ext, va_list vl )
{
  cmFsRC_t        rc      = kOkFsRC;
  cmChar_t*       rp      = NULL;
  const cmChar_t* dp      = NULL;
  unsigned        n       = 0;
  cmFs_t*         p       = _cmFileSysHandleToPtr(h);
  char            pathSep = cmPathSeparatorChar[0];
  char            extSep  = '.';
  va_list         vl_t;
  va_copy(vl_t,vl);

  // get prefix directory length
  if( dir != NULL )
    n += strlen(dir) + 1;  // add 1 for ending sep

  // get file name length
  if( fn != NULL )
    n += strlen(fn);

  // get extension length
  if( ext != NULL )
    n += strlen(ext) + 1;  // add 1 for period

  // get length of all var args dir's
  while( (dp = va_arg(vl,const cmChar_t*)) != NULL )
    n += strlen(dp) + 1;  // add 1 for ending sep

   // add 1 for terminating zero and allocate memory

  if((rp = cmLHeapAllocZ( p->heapH, n+1 )) == NULL )
  {
    rc = _cmFileSysError(p,kMemAllocErrFsRC,0,"Unable to allocate file name memory.");
    goto errLabel;
  }

  va_copy(vl,vl_t);

  rp[n] = 0;
  rp[0] = 0;

  // copy out the prefix dir
  if( dir != NULL )
    strncat(rp,dir,n-strlen(rp));

  // copy out ecmh of the var arg's directories
  while((dp = va_arg(vl,const cmChar_t*)) != NULL )
    if(!_cmFileSysConcat(rp,n,pathSep,dp) )
    {
      assert(0);
      rc = _cmFileSysError(p,kAssertFailFsRC,0,"Assert failed.");
      goto errLabel;
    }


  // copy out the file name
  if( fn != NULL )
    if(!_cmFileSysConcat(rp,n,pathSep,fn))
    {
      assert(0);
      rc = _cmFileSysError(p,kAssertFailFsRC,0,"Assert failed.");
      goto errLabel;
    }
  
  // copy out the extension
  if( ext != NULL )
    if(!_cmFileSysConcat(rp,n,extSep,ext))
    {
      assert(0);
      rc = _cmFileSysError(p,kAssertFailFsRC,0,"Assert failed.");
      goto errLabel;
    }

  assert(strlen(rp)<=n);
  
 errLabel:

  if( rc != kOkFsRC && rp != NULL )
    cmLHeapFree(p->heapH, rp );

  return rp;
}

const cmChar_t* cmFileSysMakeFn( cmFileSysH_t h, const cmChar_t* dir, const cmChar_t* fn, const cmChar_t* ext, ... )
{
  va_list vl;
  va_start(vl,ext);
  const cmChar_t* retPtr = cmFileSysVMakeFn(h,dir,fn,ext,vl);
  va_end(vl);
  return retPtr;
}

void cmFileSysFreeFn( cmFileSysH_t h, const cmChar_t* fn )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);

  if( fn == NULL )
    return;

  cmLHeapFree(p->heapH, (void*)fn); 
}

cmFsRC_t cmFileSysGenFn( cmFileSysH_t h, const cmChar_t* dir, const cmChar_t* prefixStr, const cmChar_t* extStr, const cmChar_t** fnPtr )
{
  cmFsRC_t rc            = kOkFsRC;
  cmFs_t*  p             = _cmFileSysHandleToPtr(h);
  unsigned maxAttemptCnt = 0xffff;

  *fnPtr = NULL;

  assert(dir != NULL);

  if( prefixStr == NULL )
    prefixStr = "";

  if( extStr == NULL )
    extStr = "";

  if( !cmFileSysIsDir(h,dir) )
    return cmErrMsg(&p->err,kOpenDirFailFsRC,"File name generation failed because the directory '%s' does not exist.",cmStringNullGuard(dir));

  unsigned i;
  for(i=0; *fnPtr==NULL; ++i)
  {
    cmChar_t* fn = cmTsPrintfP(NULL,"%s%i",prefixStr,i);

    const cmChar_t* path = cmFileSysMakeFn(h,dir,fn,extStr,NULL );
    
    if( !cmFileSysIsFile(h,path) )
      *fnPtr = cmMemAllocStr(path);

    cmFileSysFreeFn(h,path);
    cmMemFree(fn);

    if( i == maxAttemptCnt )
      return cmErrMsg(&p->err,kGenFileFailFsRC,"File name generation failed because a suitable file name could not be found after %i attempts.",maxAttemptCnt);
  };

  return rc;
}

cmFsRC_t    cmFileSysMkDir( cmFileSysH_t h, const cmChar_t* dir )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);

  if( mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0 )
    return _cmFileSysError( p, kMkDirFailFsRC, errno, "The attempt to create the directory '%s' failed.",dir);

  return kOkFsRC;
}

cmFsRC_t    cmFileSysMkDirAll( cmFileSysH_t h, const cmChar_t* dir )
{
  cmFsRC_t   rc = kOkFsRC;
  cmFs_t*    p  = _cmFileSysHandleToPtr(h);
  cmChar_t** a  = NULL;
  unsigned   i;

  if((a = cmFileSysDirParts(h,dir)) == NULL )
    return _cmFileSysError(p, kInvalidDirFsRC, 0, "The directory '%s' could not be parsed.",cmStringNullGuard(dir));

  for(i=0; rc==kOkFsRC && a[i]!=NULL; ++i)
  {
    cmChar_t* d = cmFileSysFormDir(h,a,i+1);

    if( cmFileSysIsDir(h,d)==false )
      if((rc = cmFileSysMkDir(h,d)) != kOkFsRC )
        break;

    cmFileSysFreeDir(h,d);
  }

  cmFileSysFreeDirParts(h,a);

  return rc;
}

cmFileSysPathPart_t* cmFileSysPathParts( cmFileSysH_t h, const cmChar_t* pathStr )
{

  int                  n  = 0;       // char's in pathStr
  int                  dn = 0;       // char's in the dir part
  int                  fn = 0;       // char's in the name part
  int                  en = 0;       // char's in the ext part
  cmChar_t*                cp = NULL;
  cmFileSysPathPart_t* rp = NULL;

  cmFs_t* p = _cmFileSysHandleToPtr(h);

  if( pathStr==NULL )
    return NULL;

  // skip leading white space
  while( *pathStr )
  {
    if( isspace(*pathStr ) )
      ++pathStr;
    else
      break;
  }

  // get the length of pathStr
  if((n = strlen(pathStr)) == 0 )
    return NULL;

  // remove trailing spaces
  for(; n>0; --n)
  {
    if( isspace(pathStr[n-1]) )
      --n;
    else
      break;
  }

  // 
  if( n == 0 )
    return NULL;
  

  char buf[n+1];
  buf[n] = 0;

  // Get the last word (which may be a file name) from pathStr.
  // (pathStr must be copied into a buf because basename()
  // is allowed to change the values in its arg.)
  strncpy(buf,pathStr,n);
  cp = basename(buf);
  
  if( cp != NULL )
  {
    cmChar_t* ep;
    // does the last word have a period in it
    if( (ep = strchr(cp,'.')) != NULL )
    {
      *ep = 0;         // end the file name at the period
      ++ep;            // set the ext marker
      en = strlen(ep); // get the length of the ext
    }  

    fn = strlen(cp); //get the length of the file name
  }

  // Get the directory part.
  // ( pathStr must be copied into a buf because dirname()
  // is allowed to change the values in its arg.)
  strncpy(buf,pathStr,n);
  
  // if the last char in pathStr[] is '/' then the whole string is a dir.
  // (this is not the answer that dirname() and basename() would give).
  if( pathStr[n-1] == cmPathSeparatorChar[0] )
  {
    fn = 0;
    en = 0;
    dn = n;
    cp = buf;    
  }
  else
  {    
    cp = dirname(buf);
  }


  if( cp != NULL  )
    dn = strlen(cp);

  // get the total size of the returned memory. (add 3 for ecmh possible terminating zero)
  n = sizeof(cmFileSysPathPart_t) + dn + fn + en + 3;

  // alloc memory
  if((rp = (cmFileSysPathPart_t*)cmLHeapAllocZ( p->heapH, n )) == NULL )
  {
    _cmFileSysError( p, kLHeapAllocErrFsRC, 0, "Unable to allocate the file system path part record for '%s'.",pathStr);
    return NULL;
  }
  
  // set the return pointers for ecmh of the parts
  rp->dirStr = (const cmChar_t* )(rp + 1);
  rp->fnStr  = rp->dirStr + dn + 1;
  rp->extStr = rp->fnStr  + fn + 1;


  // if there is a directory part
  if( dn>0 )
    strcpy((cmChar_t*)rp->dirStr,cp);
  else
    rp->dirStr = NULL;
  
  if( fn || en )
  {
    // Get the trailing word again.
    // pathStr must be copied into a buf because basename() may
    // is allowed to change the values in its arg.
    strncpy(buf,pathStr,n);
    cp = basename(buf);

    
    if( cp != NULL )
    {
      
      cmChar_t* ep;
      if( (ep = strchr(cp,'.')) != NULL )
      {
        *ep = 0;
        ++ep;

        assert( strlen(ep) == en );
        strcpy((cmChar_t*)rp->extStr,ep);
      }
      

      assert( strlen(cp) == fn );
      if(fn)
        strcpy((cmChar_t*)rp->fnStr,cp);
    }
  }

  if( fn == 0 )
    rp->fnStr = NULL;

  if( en == 0 )
    rp->extStr = NULL;

  return rp;
  
}

void                 cmFileSysFreePathParts( cmFileSysH_t h, cmFileSysPathPart_t* pp )
{
  if( pp == NULL )
    return;

  cmFs_t* p = _cmFileSysHandleToPtr(h);

  cmLHeapFree(p->heapH, (void*)pp );
}

cmChar_t** cmFileSysDirParts( cmFileSysH_t h, const cmChar_t* dirStr )
{
  cmFs_t*         p       = _cmFileSysHandleToPtr(h);
  const cmChar_t* s       = dirStr;
  const cmChar_t* ep      = dirStr + strlen(dirStr);
  char            pathSep = cmPathSeparatorChar[0];
  unsigned        n       = 2;
  unsigned        i       = 0;

  // skip leading white space or pathsep
  while( isspace(*s) && s < ep )
    ++s;

  // if the directory string is empty
  if( s >= ep )
    return NULL;

  // set the beginning of the input dirStr past any leading white space
  dirStr = s;

  // count the number of dir segments - this might overcount the
  // number of segments if there are multiple repeated path seperator
  // char's - but this is ok because 'n' is only used to determine
  // the size of the returned array - which will simply have some
  // NULL entries at the end.
  for(; s < ep; ++s )
    if( *s == pathSep )
      ++n;

  // allocate the array
  cmChar_t** a = cmLhAllocZ(p->heapH,cmChar_t*,n);

  // reset the cur location to the begin of the buf
  s = dirStr;

  // if the path starts at the root
  if( *s == pathSep )
  {
    a[0] = cmPathSeparatorChar; // cmPathSeparatorChar is a static string in cmGlobal.h
    i = 1;
    ++s;
  }

  for(; i<n && s<ep; ++i)
  {
    const cmChar_t* s1;
    if(( s1 = strchr(s,pathSep)) == NULL )
      s1 = ep;

    if( s1!=s )
    {
    
      unsigned sn = (s1 - s)+1;
      a[i] = cmLhAlloc(p->heapH,cmChar_t,sn);
      strncpy(a[i],s,sn-1);
      a[i][sn-1] = 0;
    }

    s = s1+1;
  }
  
  return a;  
}

void  cmFileSysFreeDirParts( cmFileSysH_t h, cmChar_t** dirStrArray )
{
  if( dirStrArray == NULL )
    return;

  cmFs_t* p = _cmFileSysHandleToPtr(h);

  unsigned i;
  for(i=0; dirStrArray[i]!=NULL; ++i)
  {
    // cmPathSeparatorChar is statically alloc'd in cmGlobal.h
    if( dirStrArray[i] != cmPathSeparatorChar )
      cmLHeapFree(p->heapH,dirStrArray[i]);
  }

  cmLHeapFree(p->heapH, (void*)dirStrArray );
}

unsigned         cmFileSysDirPartsCount(cmFileSysH_t h,  cmChar_t** dirStrArray )
{
  unsigned i = 0;
  if( dirStrArray == NULL )
    return 0;
  
  while(dirStrArray[i] != NULL )
    ++i;

  return i;
}

cmChar_t* cmFileSysFormDir( cmFileSysH_t h, cmChar_t** a, unsigned m )
{
  cmFs_t*   p = _cmFileSysHandleToPtr(h);
  unsigned  n;
  unsigned  i;

  // determine the length of the return string
  for(i=0,n=0; a[i]!=NULL && i<m; ++i)
    n += strlen(a[i]) + strlen(cmPathSeparatorChar);

  if( i<m && a[i] == NULL )
  {
    _cmFileSysError(p,kInvalidDirFsRC,0,"cmFileSysFormDir() cannot form a directory string from %i parts when only %i exist.",m,i);
     return NULL;
  }

  n += 1;

  // allocate the return string
  cmChar_t* r = cmLhAllocZ(p->heapH,cmChar_t,n);
  const cmChar_t* ep = r + n;

  // form the return string
  for(i=0; a[i]!=NULL && i<m; ++i)
  {
    strcat(r,a[i]);

    if( strcmp(a[i],cmPathSeparatorChar)!=0 && a[i+1]!=NULL )
      strcat(r,cmPathSeparatorChar);

    assert( r + strlen(r) <= ep );
  }
  
  return r;
}

void   cmFileSysFreeDir( cmFileSysH_t h, const cmChar_t* dir )
{
  if( dir == NULL )
    return;

  cmFs_t*   p = _cmFileSysHandleToPtr(h);

  cmLHeapFree(p->heapH,(void*)dir);
}


typedef struct 
{
  cmFs_t*              p;
  unsigned             filterFlags;
  cmFileSysDirEntry_t* rp;
  cmChar_t*            dataPtr;
  cmChar_t*            endPtr;
  unsigned             entryCnt;
  unsigned             entryIdx;
  unsigned             dataByteCnt;
  unsigned             passIdx;

}  cmFileSysDeRecd_t;

cmFsRC_t _cmFileSysDirGetEntries(  cmFileSysDeRecd_t* drp, const cmChar_t* dirStr )
{
  cmFsRC_t             rc          = kOkFsRC;
  DIR*                 dirp        = NULL;
  struct dirent*       dp          = NULL;
  char                 curDirPtr[] = "./";  
  unsigned             dn          = 0;


  if( dirStr == NULL || strlen(dirStr) == 0 )
    dirStr = curDirPtr;

  if( _cmFileSysIsDir(drp->p,dirStr) == false )
    return rc;

  unsigned       fnCharCnt= strlen(dirStr) + PATH_MAX;
  char           fn[ fnCharCnt + 1 ];


  // copy the directory into fn[] ...
  fn[0]          =0;
  fn[fnCharCnt] = 0;

  strcpy(fn,dirStr);

  assert( strlen(fn)+2 < fnCharCnt );
  
  // ... and be sure that it is terminated with a path sep char
  if( fn[ strlen(fn)-1 ] != cmPathSeparatorChar[0] )
    strcat(fn,cmPathSeparatorChar);

  // file names will be appended to the path at this location
  unsigned fni = strlen(fn);

  // open the directory
  if((dirp = opendir(dirStr)) == NULL)
  {
    rc = _cmFileSysError(drp->p,kOpenDirFailFsRC,errno,"Unable to open the directory:'%s'.",dirStr);

    goto errLabel;
  }

  // get the next directory entry
  while((dp = readdir(dirp)) != NULL )
  {
    // validate d_name 
    if( (dp->d_name != NULL) && ((dn = strlen(dp->d_name)) > 0) )
    {
      unsigned flags = 0;
        
      // handle cases where d_name begins with '.'
      if( dp->d_name[0] == '.' )
      {

        if( strcmp(dp->d_name,".") == 0 )
        {
          if( cmIsFlag(drp->filterFlags,kCurDirFsFl) == false )
            continue;

          flags |= kCurDirFsFl;
        }

        if( strcmp(dp->d_name,"..") == 0 )
        {
          if( cmIsFlag(drp->filterFlags,kParentDirFsFl) == false )
            continue;

          flags |= kParentDirFsFl;
        }

        if( flags == 0 )
        {
          if( cmIsFlag(drp->filterFlags,kInvisibleFsFl) == false )
            continue;

          flags |= kInvisibleFsFl;
        }
      }

      fn[fni] = 0;
      strncat( fn, dp->d_name, fnCharCnt-fni );
      unsigned fnN = strlen(fn);

      // if the filename is too long for the buffer
      if( fnN > fnCharCnt )
      {
        rc = _cmFileSysError(drp->p, kFnTooLongFsRC, errno, "The directory entry:'%s' was too long to be processed.",dp->d_name);
        goto errLabel;
      }

      // is a link
      if( _cmFileSysIsLink(drp->p,fn) )
      {
        if( cmIsFlag(drp->filterFlags,kLinkFsFl) == false )
          continue;

        flags |= kLinkFsFl;

        if( cmIsFlag(drp->filterFlags,kRecurseLinksFsFl) )
          if((rc = _cmFileSysDirGetEntries(drp,fn)) != kOkFsRC )
            goto errLabel;
      }
      else
      {

        // is the entry a file
        if( _cmFileSysIsFile(drp->p,fn) )
        {
          if( cmIsFlag(drp->filterFlags,kFileFsFl)==false )
            continue;

          flags |= kFileFsFl;
        }
        else
        {
          // is the entry a dir
          if( _cmFileSysIsDir(drp->p,fn) )
          {
            if( cmIsFlag(drp->filterFlags,kDirFsFl) == false)
              continue;

            flags |= kDirFsFl;

            if( cmIsFlag(drp->filterFlags,kRecurseFsFl) )
              if((rc = _cmFileSysDirGetEntries(drp,fn)) != kOkFsRC )
                goto errLabel;
          }
          else
          {
            continue;
          }
        }
      }

      //assert(flags != 0);

      if( drp->passIdx == 0 )
      {
        ++drp->entryCnt;
        
        // add 1 for the name terminating zero
        drp->dataByteCnt += sizeof(cmFileSysDirEntry_t) + 1;

        if( cmIsFlag(drp->filterFlags,kFullPathFsFl) )
          drp->dataByteCnt += fnN;
        else
          drp->dataByteCnt += dn;

      }
      else
      {
        assert( drp->passIdx == 1 );
        assert( drp->entryIdx < drp->entryCnt );

        unsigned n = 0;
        if( cmIsFlag(drp->filterFlags,kFullPathFsFl) )
        {
          n = fnN+1;
          assert( drp->dataPtr + n <= drp->endPtr );
          strcpy(drp->dataPtr,fn);
        }
        else
        {
          n = dn+1;
          assert( drp->dataPtr + n <= drp->endPtr );
          strcpy(drp->dataPtr,dp->d_name);
        }

        drp->rp[ drp->entryIdx ].flags = flags;
        drp->rp[ drp->entryIdx ].name  = drp->dataPtr;
        drp->dataPtr += n;
        assert( drp->dataPtr <= drp->endPtr);
        ++drp->entryIdx;
      }
    }  
  }

 errLabel:
  if( dirp != NULL )
    closedir(dirp);

  return rc;
}


cmFileSysDirEntry_t* cmFileSysDirEntries(  cmFileSysH_t h, const cmChar_t* dirStr, unsigned filterFlags, unsigned* dirEntryCntPtr )
{
  cmFsRC_t          rc = kOkFsRC;
  cmFileSysDeRecd_t r;

  memset(&r,0,sizeof(r));
  r.p           = _cmFileSysHandleToPtr(h);
  r.filterFlags = filterFlags;

  assert( dirEntryCntPtr != NULL );
  *dirEntryCntPtr = 0;
  
  for(r.passIdx=0; r.passIdx<2; ++r.passIdx)
  {
    if((rc = _cmFileSysDirGetEntries( &r, dirStr )) != kOkFsRC )
      goto errLabel;

    if( r.passIdx == 0 && r.dataByteCnt>0 )
    {
      // allocate memory to hold the return values
      if(( r.rp = (cmFileSysDirEntry_t *)cmLHeapAllocZ( r.p->heapH, r.dataByteCnt )) == NULL )
      {
        rc= _cmFileSysError( r.p, kMemAllocErrFsRC, 0, "Unable to allocate %i bytes of dir entry memory.",r.dataByteCnt);
        goto errLabel;
      }

      r.dataPtr = (cmChar_t*)(r.rp + r.entryCnt);
      r.endPtr  = ((cmChar_t*)r.rp) + r.dataByteCnt; 
    }
  }

 errLabel:
  
  if( rc == kOkFsRC )
  {
    assert( r.entryIdx == r.entryCnt );
    *dirEntryCntPtr = r.entryCnt;
  }
  else
  {
    if( r.rp != NULL )
      cmLHeapFree(r.p->heapH,r.rp);    

    r.rp = NULL;
  }

  return r.rp;
}

void                cmFileSysDirFreeEntries( cmFileSysH_t h, cmFileSysDirEntry_t* dp )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);
  if( dp != NULL )
    cmLHeapFree(p->heapH,dp);
}

cmFsRC_t             cmFileSysErrorCode( cmFileSysH_t h )
{
  cmFs_t* p = _cmFileSysHandleToPtr(h);
  return cmErrLastRC(&p->err);
}

//
//======================================================================================================
// Begin global versions
//

cmFileSysH_t _cmFsH = { NULL };

cmFsRC_t             cmFsInitialize( cmCtx_t* ctx, const cmChar_t* appNameStr )
{ return cmFileSysInitialize(&_cmFsH,ctx,appNameStr); }

cmFsRC_t             cmFsFinalize()
{ return cmFileSysFinalize(&_cmFsH); }

const cmChar_t*      cmFsAppName() 
{ return cmFileSysAppName(_cmFsH); }

const cmChar_t*      cmFsPrefsDir()
{ return cmFileSysPrefsDir(_cmFsH); }

const cmChar_t*      cmFsRsrcDir()
{ return cmFileSysRsrcDir(_cmFsH); }

const cmChar_t*      cmFsUserDir()
{ return cmFileSysUserDir(_cmFsH); }

bool                cmFsCanWriteToDir( const cmChar_t* dirStr )
{ return cmFileSysCanWriteToDir(_cmFsH,dirStr); }

bool                 cmFsIsDir(  const cmChar_t* dirStr )
{ return cmFileSysIsDir(_cmFsH,dirStr); }

bool                 cmFsIsFile( const cmChar_t* fnStr ) 
{ return cmFileSysIsFile(_cmFsH,fnStr); }

bool                 cmFsIsLink( const cmChar_t* fnStr ) 
{ return cmFileSysIsLink(_cmFsH,fnStr); }

const cmChar_t*      cmFsVMakeFn( const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, va_list vl )
{ return cmFileSysVMakeFn(_cmFsH,dirPrefix,fn,ext,vl); }

const cmChar_t*      cmFsMakeFn(  const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, ... )
{
  va_list vl;
  va_start(vl,ext);
  const cmChar_t* retPtr = cmFsVMakeFn(dirPrefix,fn,ext,vl);
  va_end(vl);
  return retPtr;
}

void                 cmFsFreeFn(  const cmChar_t* fn )
{ cmFileSysFreeFn(_cmFsH, fn); }

cmFsRC_t             cmFsGenFn( const cmChar_t* dir, const cmChar_t* prefixStr, const cmChar_t* extStr, const cmChar_t** fnPtr )
{ return cmFileSysGenFn(_cmFsH,dir,prefixStr,extStr,fnPtr); }

cmFsRC_t             cmFsMkDir( const cmChar_t* dir )
{ return cmFileSysMkDir(_cmFsH,dir); }

cmFsRC_t             cmFsMkDirAll( const cmChar_t* dir )
{ return cmFileSysMkDirAll(_cmFsH,dir); }

cmFileSysPathPart_t* cmFsPathParts(     const cmChar_t* pathNameStr )
{ return cmFileSysPathParts(_cmFsH,pathNameStr); }

void                 cmFsFreePathParts( cmFileSysPathPart_t* p )
{ cmFileSysFreePathParts(_cmFsH,p); }

cmChar_t**           cmFsDirParts(      const cmChar_t* dirStr )
{ return cmFileSysDirParts(_cmFsH,dirStr); }

void                 cmFsFreeDirParts(  cmChar_t** dirStrArray )
{ cmFileSysFreeDirParts(_cmFsH,dirStrArray); }

unsigned             cmFsDirPartsCount( cmChar_t** dirStrArray )
{ return cmFileSysDirPartsCount(_cmFsH, dirStrArray); }

cmChar_t*            cmFsFormDir(       cmChar_t** dirStrArray, unsigned n )
{ return cmFileSysFormDir(_cmFsH,dirStrArray,n); }
 
void                 cmFsFreeDir(       const cmChar_t* dir )
{ cmFileSysFreeDir(_cmFsH,dir); }

cmFileSysDirEntry_t* cmFsDirEntries(     const cmChar_t* dirStr, unsigned includeFlags, unsigned* dirEntryCntPtr )
{ return cmFileSysDirEntries(_cmFsH,dirStr,includeFlags,dirEntryCntPtr); }

void                 cmFsDirFreeEntries( cmFileSysDirEntry_t* p )
{ cmFileSysDirFreeEntries(_cmFsH,p); }

cmFsRC_t             cmFsErrorCode()
{ return cmFileSysErrorCode(_cmFsH); }

// end global version
//======================================================================================================
//


//{ { label:cmFileSysEx }
//(
//
// cmFileSysTest() function gives usage and testing examples 
// for some of the cmFileSys functions.
// Note that the HOME_DIR macro should be set to your true
// $HOME directroy and SRC_DIR should be set to any existing
// and accessible directory.
//
//)
//(

#if defined(OS_OSX)
#define HOME_DIR "/Users/kevin"
#else
#define HOME_DIR "/home/kevin"
#endif

#define SRC_DIR  HOME_DIR"/src"

void _cmFileSysTestFnParser( 
  cmFileSysH_t    h, 
  cmRpt_t*        rpt, 
  const cmChar_t* fn );

cmFsRC_t cmFileSysTest( cmCtx_t* ctx )
{
  // The global heap manager must have been initialized 
  // via cmMdInitialize() prior to running this function.

  cmFsRC_t        rc      = kOkFsRC;
  cmFileSysH_t    h       = cmFileSysNullHandle;
  const char      dir0[]  = SRC_DIR; 
  const char      dir1[]  = HOME_DIR"/blah";
  const char      file0[] = HOME_DIR"/.emacs";
  const char      file1[] = HOME_DIR"/blah.txt";
  const char      not[]   = " not ";
  const char      e[]     = " ";
  bool            fl      = false;
  const cmChar_t* fn      = NULL;

  // Initialize the file system.
  if((rc = cmFileSysInitialize(&h,ctx,"fs_test")) != kOkFsRC )
    return rc;

  //----------------------------------------------------------
  // Print the standard directories
  //
  printf("Prefs Dir:%s\n",cmFsPrefsDir());
  printf("Rsrc Dir: %s\n",cmFsRsrcDir());
  printf("User Dir: %s\n",cmFsUserDir());

  //----------------------------------------------------------
  // Run the file system type checker
  //
  fl = cmFileSysIsDir(h,dir0);
  printf("'%s' is%sa directory.\n",dir0, (fl ? e : not));  

  fl = cmFileSysIsDir(h,dir1);
  printf("'%s' is%sa directory.\n",dir1, (fl ? e : not));  

  fl = cmFileSysIsFile(h,file0);
  printf("'%s' is%sa file.\n",file0, (fl ? e : not));  

  fl = cmFileSysIsFile(h,file1);
  printf("'%s' is%sa file.\n",file1, (fl ? e : not));  

  //----------------------------------------------------------
  // Test the file name creation functions
  //
  if((fn = cmFileSysMakeFn(h,HOME_DIR,"cmFileSys",
        "c","src","cm","src",NULL)) != NULL)
  {
    printf("File:'%s'\n",fn);
  }
  cmFileSysFreeFn(h,fn);

  if((fn = cmFileSysMakeFn(h,HOME_DIR,"cmFileSys",
        ".c","/src/","/cm/","/src/",NULL)) != NULL )
  {
    printf("File:'%s'\n",fn);
  }
  cmFileSysFreeFn(h,fn);

  //----------------------------------------------------------
  // Test the file name parsing functions
  //
  _cmFileSysTestFnParser(h,&ctx->rpt,
                         HOME_DIR"/src/cm/src/cmFileSys.c");

  _cmFileSysTestFnParser(h,&ctx->rpt,
                         HOME_DIR"/src/cm/src/cmFileSys");

  _cmFileSysTestFnParser(h,&ctx->rpt,
                         HOME_DIR"/src/cm/src/cmFileSys/");

  _cmFileSysTestFnParser(h,&ctx->rpt,"cmFileSys.c");
  _cmFileSysTestFnParser(h,&ctx->rpt,"/");
  _cmFileSysTestFnParser(h,&ctx->rpt," ");

  //----------------------------------------------------------
  // Test the directory tree walking routines.
  //
  cmRptPrintf(&ctx->rpt,"Dir Entry Test:\n");
  cmFileSysDirEntry_t* dep;
  unsigned             dirEntCnt;
  unsigned             filterFlags = kDirFsFl 
                                   | kFileFsFl 
                                   | kRecurseFsFl 
                                   | kFullPathFsFl;

  if((dep = cmFileSysDirEntries(h,SRC_DIR"/doc",filterFlags,
                                &dirEntCnt)) != NULL)
  {
    unsigned i;
    for(i=0; i<dirEntCnt; ++i)
      cmRptPrintf(&ctx->rpt,"%s\n",dep[i].name);

    cmFileSysDirFreeEntries(h,dep);
  }

  //----------------------------------------------------------
  // Test the directory parsing/building routines.
  //
  cmRptPrintf(&ctx->rpt,"Dir Parsing routings:\n");
  cmChar_t**      a;
  unsigned        j;
  for(j=0; j<2; ++j)
  {
    const cmChar_t* dstr = dir0 + j;
    if((a = cmFileSysDirParts(h,dstr)) == NULL)
      cmRptPrint(&ctx->rpt,"cmFileSysDirParts() failed.\n");
    else
    {
      unsigned i;

      cmRptPrintf(&ctx->rpt,"Input:%s\n",dstr);
      for(i=0; a[i]!=NULL; ++i)
        cmRptPrintf(&ctx->rpt,"%i : %s\n",i,a[i]);

      cmChar_t* d;
      if((d = cmFileSysFormDir(h,a,
            cmFileSysDirPartsCount(h,a))) != NULL )
      {
        cmRptPrintf(&ctx->rpt,"Reformed:%s\n",d);
      }

      cmFileSysFreeDirParts(h,a);
    }
  }

  //----------------------------------------------------------
  // Test the extended mkdir routine.
  //
  if( cmFileSysMkDirAll(h, HOME_DIR"/temp/doc/doc" )!=kOkFsRC )
  {
    cmRptPrint(&ctx->rpt,"cmFileSysMkDirAll() failed.\n");
  }

  // finalize the file system
  if((rc = cmFileSysFinalize(&h)) != kOkFsRC )
    return rc;

  cmRptPrintf(&ctx->rpt,"File Test done\n");

  return rc;    
}

// Parse a file name and print the results. 
// Called by cmFileSysTest().
void _cmFileSysTestFnParser( 
  cmFileSysH_t    h, 
  cmRpt_t*        rpt, 
  const cmChar_t* fn )
{
  cmFileSysPathPart_t* pp;

  cmRptPrintf(rpt,"Fn Parse Test:%s\n",fn);

  if((pp = cmFileSysPathParts(h,fn)) != NULL )
  {
    if(pp->dirStr != NULL)
      cmRptPrintf(rpt,"Dir:%s\n",pp->dirStr);

    if(pp->fnStr != NULL)
      cmRptPrintf(rpt,"Fn:%s\n",pp->fnStr);

    if(pp->extStr != NULL )
      cmRptPrintf(rpt,"Ext:%s\n",pp->extStr);

    cmFileSysFreePathParts(h,pp);
  }

  cmRptPrintf(rpt,"\n");

}
//)
//}
