#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLex.h"
#include "cmLinkedHeap.h"
#include "cmHashTbl.h"
#include "cmCsv.h"
#include "cmText.h"

enum
{
  kCommaLexTId = kUserLexTId+1,

  kMaxLexTId // always last in the lex id list
};

typedef struct
{
  const char* text;
  unsigned    id;  
} cmCsvToken_t;


cmCsvToken_t _cmCsvTokenArray[] = 
{
  { ",",  kCommaLexTId },
  { "",   kErrorLexTId }
};

// The binder linked list contains a list of records which tracks the first 
// (left-most) non-blank column in each row. 
typedef struct cmCsvBind_str
{
  struct cmCsvBind_str* linkPtr; 
  cmCsvCell_t*          rowPtr;
} cmCsvBind_t;

typedef struct cmCsvUdef_str
{
  struct cmCsvUdef_str* linkPtr;
  unsigned              id;
} cmCsvUdef_t;

typedef struct
{
  cmErr_t            err;
  void*              rptDataPtr;   //
  cmLexH             lexH;         // parsing lexer 
  cmHashTblH_t       htH;          // hash table handle
  cmLHeapH_t         heapH;
  cmCsvBind_t*       bindPtr;      // base of the binder linked list
  cmCsvCell_t*       curRowPtr;    // used by the parser to track the row being filled
  cmCsvUdef_t*       udefList;
} cmCsv_t;

cmCsvH_t cmCsvNullHandle = { NULL };

cmCsv_t* _cmCsvHandleToPtr( cmCsvH_t h )
{
  cmCsv_t* p = (cmCsv_t*)h.h;
  assert( p != NULL );
  cmErrClearRC(&p->err);
  return p;
}

cmCsvRC_t _cmCsvVError( cmCsv_t* p, cmCsvRC_t rc, const char* fmt, va_list vl )
{  return cmErrVMsg(&p->err,rc,fmt,vl); }

cmCsvRC_t _cmCsvError( cmCsv_t* p, cmCsvRC_t rc, const char* fmt, ... )
{
  va_list vl;
  va_start(vl,fmt);
  rc = _cmCsvVError(p,rc,fmt,vl);
  va_end(vl);
  return rc;
}


cmCsvRC_t cmCsvInitialize( cmCsvH_t *hp, cmCtx_t* ctx )
{
  cmCsvRC_t rc;
  cmCsv_t* p = NULL;
  unsigned i;

  if((rc = cmCsvFinalize(hp)) != kOkCsvRC )
    return rc;

  // create the base object
  p = cmMemAllocZ( cmCsv_t, 1 );
  assert(p != NULL);

  cmErrSetup(&p->err,&ctx->rpt,"CSV");

  // create the hash table
  if( cmHashTblCreate(ctx,&p->htH,8192) != kOkHtRC )
  {
    rc = _cmCsvError(p,kHashTblErrCsvRC,"Hash table creation failed.");
    goto errLabel;
  }

  // allocate the linked heap mgr
  if( cmLHeapIsValid(p->heapH = cmLHeapCreate(8192,ctx)) == false )
  {
    rc = _cmCsvError(p,kMemAllocErrCsvRC,"Linked heap object allocation failed.");
    goto errLabel;
  }

  // allocate the lexer
  if(cmLexIsValid(p->lexH = cmLexInit(NULL,0,0,&ctx->rpt)) == false )
  {
    rc =  _cmCsvError(p,kLexErrCsvRC,"Lex allocation failed.");
    goto errLabel;
  }

  // register CSV specific tokens with the lexer
  for(i=0; _cmCsvTokenArray[i].id != kErrorLexTId; ++i)
  {
    cmRC_t lexRC;
    if( (lexRC = cmLexRegisterToken(p->lexH, _cmCsvTokenArray[i].id, _cmCsvTokenArray[i].text )) != kOkLexRC )
    {
      rc = _cmCsvError(p,kLexErrCsvRC,"Lex token registration failed for:'%s'.\nLexer Error:%s",_cmCsvTokenArray[i].text, cmLexRcToMsg(lexRC) );
      goto errLabel;
    }
  }

  hp->h = p;

  return kOkCsvRC;
      
 errLabel:
  
  cmMemPtrFree(&p);

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

  if( cmLexIsValid(p->lexH) )
    cmLexFinal(&p->lexH);

  return rc;

}
cmCsvRC_t cmCsvFinalize(   cmCsvH_t *hp )
{

  cmRC_t lexRC;

  if( hp == NULL || hp->h == NULL )
    return kOkCsvRC;

  cmCsv_t* p = _cmCsvHandleToPtr(*hp);
 
  // free the internal heap object
  cmLHeapDestroy( &p->heapH );

  // free the lexer
  if((lexRC = cmLexFinal(&p->lexH)) != kOkLexRC )
    return _cmCsvError(p,kLexErrCsvRC,"Lexer finalization failed.\nLexer Error:%s",cmLexRcToMsg(lexRC));

  // free the hash table
  cmHashTblDestroy(&p->htH);

  // free the handle
  cmMemPtrFree(&hp->h);

  return kOkCsvRC;
}

cmCsvRC_t cmCsvInitializeFromFile( cmCsvH_t *hp, const char* fn, unsigned maxRowCnt, cmCtx_t* ctx )
{
  cmCsvRC_t rc;

  if((rc = cmCsvInitialize(hp,ctx)) != kOkCsvRC )
    return rc;

  return cmCsvParseFile( *hp, fn, maxRowCnt);
}


bool cmCsvIsValid(    cmCsvH_t h)
{ return h.h != NULL; } 

cmCsvRC_t cmCsvLastRC( cmCsvH_t h )
{ 
  cmCsv_t* p = _cmCsvHandleToPtr(h);
  return cmErrLastRC(&p->err);
}

void cmCsvClearRC( cmCsvH_t h )
{
  cmCsv_t* p = _cmCsvHandleToPtr(h);
  cmErrClearRC(&p->err);
}


cmCsvRC_t cmCsvParseFile(  cmCsvH_t h, const char* fn, unsigned maxRowCnt )
{
  cmCsvRC_t rc     = kOkCsvRC;
  FILE*    fp      = NULL;
  cmCsv_t*  p      = _cmCsvHandleToPtr(h);
  unsigned n       = 0;
  char*    textBuf = NULL;

  assert( fn != NULL && p != NULL );
  
  // open the file
  if((fp = fopen(fn,"rb")) == NULL )
    return _cmCsvError(p,kFileOpenErrCsvRC,"Unable to open the file:'%s'.",fn);

  // seek to the end
  if( fseek(fp,0,SEEK_END) != 0 )
  {
    rc= _cmCsvError(p,kFileSeekErrCsvRC,"Unable to seek to the end of '%s'.",fn);
    goto errLabel;
  }

  // get the length of the file
  if( (n=ftell(fp)) == 0 )
  {
    rc = _cmCsvError(p,kFileOpenErrCsvRC,"The file '%s' appears to be empty.",fn);
    goto errLabel;
  }

  // rewind the file
  if( fseek(fp,0,SEEK_SET) != 0 )
  {
    rc = _cmCsvError(p,kFileSeekErrCsvRC,"Unable to seek to the beginning of '%s'.",fn);
    goto errLabel;
  }

  // allocate the text buffer
  if((textBuf = cmMemAllocZ( char, n+1)) == NULL )
  {
    rc = _cmCsvError(p,kMemAllocErrCsvRC,"Unable to allocate the text file buffer for:'%s'.",fn);
    goto errLabel;
  }

  // read the file into the text buffer
  if( fread(textBuf,n,1,fp) != 1 )
  {
    rc = _cmCsvError(p,kFileReadErrCsvRC,"File read failed on:'%s'.",fn);
    goto errLabel;
  }  

  rc = cmCsvParse(h,textBuf,n,maxRowCnt); 

 errLabel:

  // close the file
  if( fclose(fp) != 0 )
  {
    rc =  _cmCsvError(p,kFileCloseErrCsvRC,"File close failed on:'%s'.",fn);
    goto errLabel;
  }

  // free the buffer
  if( textBuf != NULL )
    cmMemFree(textBuf);

  return rc;

}

cmCsvRC_t _cmCsvAppendNewBindRecd( cmCsv_t* p, cmCsvBind_t** newBindPtrPtr, unsigned* newRowIdxPtr  )
{
  cmCsvBind_t* nbp;
  cmCsvBind_t* bp = p->bindPtr;
  unsigned     newRowIdx = 0;

  if( newBindPtrPtr != NULL )
    *newBindPtrPtr = NULL;

  if( newRowIdxPtr != NULL )
    *newRowIdxPtr = cmInvalidIdx;
  
  // allocate the binder record
  if((nbp = cmLHeapAllocZ( p->heapH, sizeof(cmCsvBind_t))) == NULL )
    return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed.");

  // if this is the first binder record
  if( p->bindPtr == NULL )
  {
    p->bindPtr = nbp;
    bp         = nbp;
  }
  else
  {
    newRowIdx = 1;

    // iterate to the bottom of the binding
    while( bp->linkPtr != NULL )
    {
      bp = bp->linkPtr;

      ++newRowIdx;
    }

    bp->linkPtr = nbp;
  }

  if( newBindPtrPtr != NULL )
    *newBindPtrPtr = nbp;

  if( newRowIdxPtr != NULL )
    *newRowIdxPtr = newRowIdx;
  

  return kOkCsvRC;
}

cmCsvRC_t _cmCsvCreateBind( cmCsv_t* p, cmCsvCell_t* cp, const char* tokenText, unsigned lexRow, unsigned lexCol )
{
  cmCsvRC_t rc;
  cmCsvBind_t* nbp;
  if((rc = _cmCsvAppendNewBindRecd(p,&nbp,NULL)) != kOkCsvRC )
    return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed for '%s' on line %i column %i.",tokenText,lexRow,lexCol);
  
  nbp->rowPtr  = cp;  

  return rc;
}

cmCsvRC_t  _cmCsvAllocCell( cmCsv_t* p, unsigned symId, unsigned flags, unsigned cellRow, unsigned cellCol, cmCsvCell_t** cpp, unsigned lexTId )
{
  cmCsvCell_t* cp;

  // allocate cell memory
  if(( cp = cmLHeapAllocZ(p->heapH, sizeof(cmCsvCell_t) )) == NULL )
    return _cmCsvError(p,kMemAllocErrCsvRC,"Cell allocation failed. for row: %i column %i.",cellRow+1,cellCol+1);

  cp->row   = cellRow;
  cp->col   = cellCol;
  cp->symId = symId;
  cp->flags = flags;
  cp->lexTId= lexTId;

  *cpp = cp;

  return kOkCsvRC;
}

cmCsvRC_t _cmCsvCreateCell( cmCsv_t* p, const char* tokenText, unsigned flags, unsigned cellRow, unsigned cellCol, unsigned lexTId )
{
  unsigned     symId  = cmInvalidId;
  cmCsvCell_t* cp     = NULL;
  unsigned     lexRow = cmLexCurrentLineNumber(p->lexH);
  unsigned     lexCol = cmLexCurrentColumnNumber(p->lexH);
  cmCsvRC_t    rc     = kOkCsvRC;

  // register the token text as a symbol
  if((symId = cmHashTblStoreStr(p->htH,tokenText)) == cmInvalidId )
    return _cmCsvError(p,kHashTblErrCsvRC,"Symbol registration failed. for '%s' on line %i column %i.",tokenText,lexRow,lexCol);

  // allocate a cell
  if((rc = _cmCsvAllocCell(p,symId,flags,cellRow,cellCol,&cp,lexTId)) != kOkCsvRC )
    return rc;
  
  // if this is the first cell in the row
  if( p->curRowPtr == NULL )
  {    
    // link in a new binding record
    if((rc = _cmCsvCreateBind( p, cp,tokenText,lexRow,lexCol)) != kOkCsvRC )
      return rc; // this return will result in a leak from the lheap but it is a fatal error so ignore it

    // update the current row ptr
    p->curRowPtr  = cp;
  }
  else
  {
    // iterate to the end of the row
    cmCsvCell_t* rp = p->curRowPtr;
    while( rp->rowPtr != NULL )
      rp = rp->rowPtr;
    
    rp->rowPtr = cp;
  }

  return rc;
  
}

const cmCsvUdef_t* _cmCsvFindUdef( cmCsv_t* p, unsigned id )
{
  const cmCsvUdef_t* u = p->udefList;
  while( u != NULL )
  {
    if( u->id == id )
      return u;

    u = u->linkPtr;
  }

  return NULL;
}

cmCsvRC_t _cmCsvRegUdef( cmCsv_t* p, unsigned id )
{
  if( _cmCsvFindUdef(p,id) != NULL )
    return _cmCsvError(p,kDuplicateLexCsvId,"%i has already been used as a user defined id.",id);

  cmCsvUdef_t* u = cmLhAllocZ(p->heapH,cmCsvUdef_t,1);
  u->id       = id;
  u->linkPtr  = p->udefList;
  p->udefList = u;
  return kOkCsvRC;
}

cmCsvRC_t    cmCsvLexRegisterToken(   cmCsvH_t h, unsigned id, const cmChar_t* token )
{
  cmCsv_t*    p   = _cmCsvHandleToPtr(h);
  if(cmLexRegisterToken(p->lexH,id,token) != kOkLexRC )
    return _cmCsvError(p, kLexErrCsvRC,"Error registering user defined token '%s'.",cmStringNullGuard(token));

  return _cmCsvRegUdef(p,id);
}

cmCsvRC_t    cmCsvLexRegisterMatcher( cmCsvH_t h, unsigned id, cmLexUserMatcherPtr_t matchFunc )
{
  cmCsv_t*    p   = _cmCsvHandleToPtr(h);
  if( cmLexRegisterMatcher(p->lexH,id,matchFunc) != kOkLexRC )
    return _cmCsvError(p, kLexErrCsvRC,"Error registering user defined matching function.");

  return _cmCsvRegUdef(p,id);
}

unsigned  cmCsvLexNextAvailId(     cmCsvH_t h )
{  return kMaxLexTId; }

cmCsvRC_t cmCsvParse(      cmCsvH_t h, const char* buf, unsigned bufCharCnt, unsigned maxRowCnt )
{
  cmCsvRC_t   rc            = kOkCsvRC;
  unsigned    prvLexRow     = 1;
  unsigned    csvRowIdx     = 0;
  unsigned    csvColIdx     = 0;
  unsigned    lexTId        = kErrorLexTId; // cur lex token type id
  cmCsv_t*    p             = _cmCsvHandleToPtr(h);

  // assign the text buffer and reset the lexer
  if((rc = cmLexSetTextBuffer( p->lexH, buf, bufCharCnt )) != kOkLexRC )
    return _cmCsvError( p, kLexErrCsvRC, "Error setting lexer buffer.\nLexer Error:%s",cmLexRcToMsg(rc));
  
  // get the next token
  while( (lexTId = cmLexGetNextToken( p->lexH )) != kErrorLexTId && (lexTId != kEofLexTId ) && (rc==kOkCsvRC) && (maxRowCnt==0 || csvRowIdx<maxRowCnt))
  {
    unsigned flags     = 0;
    unsigned curLexRow = cmLexCurrentLineNumber(p->lexH);

    // if we are starting a new row
    if( curLexRow != prvLexRow )
    {
      prvLexRow = curLexRow;
      ++csvRowIdx;
      csvColIdx = 0;
      p->curRowPtr = NULL;  // force a new binding record
    }


    // copy the token text in a buffer
    unsigned n = cmLexTokenCharCount(p->lexH);
    char buf[n+1];
    strncpy(buf, cmLexTokenText(p->lexH), n );
    buf[n]=0;

    // do cell data type specific processing
    switch( lexTId )
    {
      case kCommaLexTId:
        ++csvColIdx;
        break;

      case kRealLexTId: flags = kRealCsvTFl;  break;
      case kIntLexTId:  flags = kIntCsvTFl;   break;
      case kHexLexTId:  flags = kHexCsvTFl;   break;
      case kIdentLexTId:flags = kIdentCsvTFl; break; 
      case kQStrLexTId: flags = kStrCsvTFl;   break;

      default:
        {
          const cmCsvUdef_t* u;
          if((u = _cmCsvFindUdef(p,lexTId)) == NULL )
            rc = _cmCsvError(p, kSyntaxErrCsvRC, "Unrecognized token type: '%s' for token: '%s' row:%i col:%i.",cmLexIdToLabel(p->lexH,lexTId),buf,csvRowIdx+1,csvColIdx+1);
          else
            flags = kStrCsvTFl | kUdefCsvTFl;            
        }
    }

    // if no error occurred and the token is not a comma  and the cell is not empty
    if( rc == kOkCsvRC && lexTId != kCommaLexTId && strlen(buf) > 0 )
      if((rc = _cmCsvCreateCell( p, buf,flags, csvRowIdx, csvColIdx, lexTId )) != kOkCsvRC )
        rc = _cmCsvError(p,rc,"CSV cell create fail on row:%i col:%i\n",csvRowIdx+1,csvColIdx+1);
  }

  if( lexTId == kErrorLexTId )
    rc = _cmCsvError(p, kSyntaxErrCsvRC,"The lexer encountered an unrecognized token row:%i col:%i.",csvRowIdx+1,csvColIdx+1);

  return rc;
}

unsigned  cmCsvRowCount( cmCsvH_t h )
{
  cmCsv_t* p  = _cmCsvHandleToPtr(h);

  unsigned     rowCnt = 0;
  cmCsvBind_t* bp     = p->bindPtr;

  for(; bp != NULL; ++rowCnt )
    bp = bp->linkPtr;

  return rowCnt;
}

cmCsvCell_t* cmCsvRowPtr( cmCsvH_t h, unsigned row )
{
  cmCsv_t*     p  = _cmCsvHandleToPtr(h);
  cmCsvBind_t* bp = p->bindPtr;
  
  while( bp != NULL )
  {
    // note: bp->rowPtr of blank rows will be NULL
    if( bp->rowPtr!=NULL && bp->rowPtr->row == row )
      return bp->rowPtr;

    bp = bp->linkPtr;
  }  

  return NULL;
}

cmCsvCell_t* cmCsvCellPtr( cmCsvH_t h, unsigned row, unsigned col )
{
  cmCsvCell_t* cp;

  if((cp = cmCsvRowPtr(h,row)) == NULL )
    return NULL;

  while( cp != NULL )
  {
    if( cp->col == col )
      return cp;

    cp = cp->rowPtr;
  }

  return NULL;
}

cmCsvCell_t* _cmCsvCellPtr( cmCsvH_t h, unsigned row, unsigned col )
{
  cmCsvCell_t* cp;
  if((cp = cmCsvCellPtr(h,row,col)) == NULL )
    _cmCsvError(_cmCsvHandleToPtr(h),kCellNotFoundCsvRC,"Cell at row:%i col:%i not found.",row,col);
  return cp;
}

const char* cmCsvCellSymText(   cmCsvH_t h, unsigned symId )
{
  cmCsv_t*    p = _cmCsvHandleToPtr(h);
  const char* cp;

  if((cp =  cmHashTblStr(p->htH,symId)) == NULL )
    _cmCsvError(p,kHashTblErrCsvRC,"The text associated with the symbol '%i' was not found.",symId);

  return cp;
}

cmCsvRC_t   cmCsvCellSymInt(    cmCsvH_t h, unsigned symId, int* vp )
{
  const char* cp;
  cmCsv_t*    p  = _cmCsvHandleToPtr(h);

  if((cp = cmCsvCellSymText(h,symId)) == NULL )
    return kHashTblErrCsvRC;

  if( cmTextToInt(cp,vp,&p->err) != kOkTxRC )
    return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to int value failed.");

  return kOkCsvRC;
}

cmCsvRC_t  cmCsvCellSymUInt(   cmCsvH_t h, unsigned symId, unsigned* vp )
{
  const char* cp;
  cmCsv_t*    p = _cmCsvHandleToPtr(h);

  if((cp = cmCsvCellSymText(h,symId)) == NULL )
    return kHashTblErrCsvRC;

  if( cmTextToUInt(cp,vp,&p->err) != kOkTxRC )
    return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to uint value failed.");

  return kOkCsvRC;
}

cmCsvRC_t   cmCsvCellSymFloat(  cmCsvH_t h, unsigned symId, float* vp )
{
  const char* cp;
  cmCsv_t*    p  = _cmCsvHandleToPtr(h);

  if((cp = cmCsvCellSymText(h,symId)) == NULL )
    return kHashTblErrCsvRC;

  if( cmTextToFloat(cp,vp,&p->err) != kOkTxRC )
    return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to float value failed.");
  
  return kOkCsvRC;
}

cmCsvRC_t    cmCsvCellSymDouble( cmCsvH_t h, unsigned symId, double* vp )
{
  const char* cp;
  cmCsv_t*    p  = _cmCsvHandleToPtr(h);

  if((cp = cmCsvCellSymText(h,symId)) == NULL )
    return kHashTblErrCsvRC;

  if( cmTextToDouble(cp,vp,&p->err) != kOkTxRC )
    return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to double value failed.");

  return kOkCsvRC;
}

const char* cmCsvCellText(  cmCsvH_t h, unsigned row, unsigned col )
{
  cmCsvCell_t* cp;
  if((cp = cmCsvCellPtr(h,row,col)) == NULL )
    return NULL;

  return cmCsvCellSymText(h,cp->symId);
}

int         cmCsvCellInt(   cmCsvH_t h, unsigned row, unsigned col )
{
  cmCsvCell_t* cp;
  int          v;

  if((cp = cmCsvCellPtr(h,row,col)) == NULL )
    return INT_MAX;

  if(cmCsvCellSymInt(h,cp->symId,&v) != kOkCsvRC )
    return INT_MAX;

  return v;
}

unsigned    cmCsvCellUInt(  cmCsvH_t h, unsigned row, unsigned col )
{
  cmCsvCell_t* cp;
  unsigned      v;

  if((cp = cmCsvCellPtr(h,row,col)) == NULL )
    return UINT_MAX;

  if(cmCsvCellSymUInt(h,cp->symId,&v) != kOkCsvRC )
    return UINT_MAX;

  return v;
}

float       cmCsvCellFloat( cmCsvH_t h, unsigned row, unsigned col )
{
  cmCsvCell_t* cp;
  float         v;

  if((cp = cmCsvCellPtr(h,row,col)) == NULL )
    return FLT_MAX;

  if(cmCsvCellSymFloat(h,cp->symId,&v) != kOkCsvRC )
    return FLT_MAX;

  return v;
}

double      cmCsvCellDouble(cmCsvH_t h, unsigned row, unsigned col )
{
  cmCsvCell_t* cp;
  double       v;

  if((cp = cmCsvCellPtr(h,row,col)) == NULL )
    return DBL_MAX;

  if(cmCsvCellSymDouble(h,cp->symId,&v) != kOkCsvRC )
    return DBL_MAX;

  return v;
}



unsigned   cmCsvInsertSymText(   cmCsvH_t h, const char* text )
{
  cmCsv_t*    p  = _cmCsvHandleToPtr(h);
  unsigned    symId;

  if((symId = cmHashTblStoreStr(p->htH,text)) == cmInvalidId )
    _cmCsvError(p,kHashTblErrCsvRC,"'%s' could not be inserted into the symbol table.",text);

  return symId;
}

unsigned   cmCsvInsertSymInt(    cmCsvH_t h, int v )
{
  const char* fmt = "%i";
  unsigned    n   = snprintf(NULL,0,fmt,v)+1;
  char        buf[n];

  buf[0]= 0;
  if( snprintf(buf,n,fmt,v) == n-1 )
    return cmCsvInsertSymText(h,buf);
  
  _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The integer %i could not be converted to text.",v);
  return cmInvalidId;
}

unsigned   cmCsvInsertSymUInt(   cmCsvH_t h, unsigned v )
{
  const char* fmt = "%i";
  unsigned    n   = snprintf(NULL,0,fmt,v)+1;
  char        buf[n];

  buf[0]= 0;
  if( snprintf(buf,n,fmt,v) == n-1 )
    return cmCsvInsertSymText(h,buf);
  
  _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The unsigned int %i could not be converted to text.",v);
  return cmInvalidId;
}

unsigned   cmCsvInsertSymHex(   cmCsvH_t h, unsigned v )
{
  const char* fmt = "0x%x";
  unsigned    n   = snprintf(NULL,0,fmt,v)+1;
  char        buf[n];

  buf[0]= 0;
  if( snprintf(buf,n,fmt,v) == n-1 )
    return cmCsvInsertSymText(h,buf);
  
  _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The unsigned int 0x%x could not be converted to text.",v);
  return cmInvalidId;
}

unsigned   cmCsvInsertSymFloat(  cmCsvH_t h, float v )
{
  const char* fmt = "%f";
  unsigned    n   = snprintf(NULL,0,fmt,v)+1;
  char        buf[n];

  buf[0]                     = 0;
  if( snprintf(buf,n,fmt,v) == n-1 )
    return cmCsvInsertSymText(h,buf);
  
  _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The float %f could not be converted to text.",v);
  return cmInvalidId;
}

unsigned   cmCsvInsertSymDouble( cmCsvH_t h, double v )  
{
  const char* fmt = "%f";
  unsigned    n   = snprintf(NULL,0,fmt,v)+1;
  char        buf[n];

  buf[0]= 0;
  if( snprintf(buf,n,fmt,v) == n-1 )
    return cmCsvInsertSymText(h,buf);
  
  _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The double %f could not be converted to text.",v);
  return cmInvalidId;

}

cmCsvRC_t  cmCsvSetCellText(   cmCsvH_t h, unsigned row, unsigned col, const char* text )
{
  cmCsvCell_t* cp;
  unsigned     symId;

  if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);

  if((symId = cmCsvInsertSymText(h,text)) == cmInvalidId )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  
  cp->symId = symId;
  cp->flags &= !kTypeTMask;
  cp->flags |= kStrCsvTFl;

  return kOkCsvRC;  
}

cmCsvRC_t  cmCsvSetCellInt(    cmCsvH_t h, unsigned row, unsigned col, int v )
{
  cmCsvCell_t* cp;
  unsigned     symId;

  if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);

  if((symId = cmCsvInsertSymInt(h,v)) == cmInvalidId )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  
  cp->symId = symId;
  cp->flags &= !kTypeTMask;
  cp->flags |= kIntCsvTFl;

  return kOkCsvRC;  
}

cmCsvRC_t  cmCsvSetCellUInt(   cmCsvH_t h, unsigned row, unsigned col, unsigned v )
{
  cmCsvCell_t* cp;
  unsigned     symId;

  if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);

  if((symId = cmCsvInsertSymUInt(h,v)) == cmInvalidId )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  
  cp->symId = symId;
  cp->flags &= !kTypeTMask;
  cp->flags |= kIntCsvTFl;

  return kOkCsvRC;  
}

cmCsvRC_t  cmCsvSetCellHex(   cmCsvH_t h, unsigned row, unsigned col, unsigned v )
{
  cmCsvCell_t* cp;
  unsigned     symId;

  if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);

  if((symId = cmCsvInsertSymHex(h,v)) == cmInvalidId )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  
  cp->symId = symId;
  cp->flags &= !kTypeTMask;
  cp->flags |= kIntCsvTFl;

  return kOkCsvRC;  
}

cmCsvRC_t  cmCsvSetCellFloat(  cmCsvH_t h, unsigned row, unsigned col, float v )
{
  cmCsvCell_t* cp;
  unsigned     symId;

  if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);

  if((symId = cmCsvInsertSymFloat(h,v)) == cmInvalidId )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  
  cp->symId = symId;
  cp->flags &= !kTypeTMask;
  cp->flags |= kRealCsvTFl;

  return kOkCsvRC;  
}

cmCsvRC_t  cmCsvSetCellDouble( cmCsvH_t h, unsigned row, unsigned col, double v )
{
  cmCsvCell_t* cp;
  unsigned     symId;

  if((cp = _cmCsvCellPtr(h,row,col)) == NULL )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);

  if((symId = cmCsvInsertSymDouble(h,v)) == cmInvalidId )
    return cmErrLastRC(&_cmCsvHandleToPtr(h)->err);
  
  cp->symId = symId;
  cp->flags &= !kTypeTMask;
  cp->flags |= kRealCsvTFl;

  return kOkCsvRC;  
}


cmCsvRC_t cmCsvInsertRowBefore(  cmCsvH_t h, unsigned row, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId )
{
  cmCsvRC_t    rc  = kOkCsvRC;
  cmCsv_t*     p   = _cmCsvHandleToPtr(h);
  cmCsvBind_t* bp  = p->bindPtr;
  cmCsvBind_t* nbp = NULL;
  unsigned     i   = 0;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  // allocate the binder record
  if((nbp = cmLHeapAllocZ( p->heapH, sizeof(cmCsvBind_t))) == NULL )
    return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed row %i column %i.",row,0);

  // if a new first row is being inserted
  if( row == 0 )
  {
    bp           = p->bindPtr;
    nbp->linkPtr = p->bindPtr;
    p->bindPtr   = nbp;
  }
  else 
  {
    bp = p->bindPtr;

    // iterate to the row before the new row
    for(i=0; bp != NULL; ++i )
    {
      if( i == (row-1) || bp->linkPtr == NULL )
        break;

      bp = bp->linkPtr;
    }

    assert( bp != NULL );

    nbp->linkPtr = bp->linkPtr; 
    bp->linkPtr  = nbp;
    bp           = bp->linkPtr;
  }
  
  // update the row numbers in all cells below the new row
  while( bp != NULL )
  {
    cmCsvCell_t* cp = bp->rowPtr;
    while( cp != NULL )
    {
      cp->row += 1;
      cp = cp->rowPtr;
    }
    bp = bp->linkPtr;
  }

  // allocate the first cell in the new row
  if(cellPtrPtr != NULL && symId != cmInvalidId )
  {
    if((rc = _cmCsvAllocCell(p, symId, flags, row, 0, cellPtrPtr, lexTId )) != kOkCsvRC )
      return rc;
    
    nbp->rowPtr = *cellPtrPtr;
  }
  return rc;
}

cmCsvRC_t  cmCsvAppendRow( cmCsvH_t h, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId )
{
  cmCsvRC_t    rc  = kOkCsvRC;
  cmCsv_t*     p   = _cmCsvHandleToPtr(h);
  cmCsvBind_t* nbp = NULL;
  unsigned     newRowIdx = cmInvalidIdx;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  if((rc =  _cmCsvAppendNewBindRecd(p,&nbp,&newRowIdx)) != kOkCsvRC )
    return rc;

  
  // allocate the first cell in the new row
  if(cellPtrPtr != NULL && symId != cmInvalidId )
  {
    if((rc = _cmCsvAllocCell(p, symId, flags, newRowIdx, 0, cellPtrPtr, lexTId )) != kOkCsvRC )
      return rc;

    nbp->rowPtr = *cellPtrPtr;
  }
  
  return rc;
}

cmCsvRC_t cmCsvInsertColAfter(  cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId )
{
  cmCsvRC_t   rc   = kOkCsvRC;
  cmCsvCell_t* ncp = NULL;
  cmCsvCell_t* cp  = NULL;
  cmCsv_t*     p   = _cmCsvHandleToPtr(h);
  unsigned     col; 

  if(cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  // allocate the new cell
  if((rc =  _cmCsvAllocCell(p, symId, flags, leftCellPtr->row, leftCellPtr->col+1, &ncp, lexTId )) != kOkCsvRC )
    return rc;

  // update the col values of cells to right of new cell
  cp  = leftCellPtr->rowPtr;
  col = leftCellPtr->col + 1;

  for(; cp != NULL; ++col )
  {
    // don't update any col numbers after a blank (missing) column
    if( cp->col == col )
      cp->col += 1;

    cp = cp->rowPtr;
  }

  // link in the new cell
  ncp->rowPtr         = leftCellPtr->rowPtr;
  leftCellPtr->rowPtr = ncp;

  if(cellPtrPtr != NULL )
    *cellPtrPtr = ncp;
  
  return rc;
  
}

cmCsvRC_t  cmCsvInsertTextColAfter(   cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, const char* text, unsigned lexTId )
{
  cmCsvRC_t    rc;
  cmCsvCell_t* ncp;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
    if((rc = cmCsvSetCellText(h, ncp->row, ncp->col, text )) == kOkCsvRC )
      if( cellPtrPtr != NULL )
        *cellPtrPtr = ncp;
  
  return rc;
}

cmCsvRC_t  cmCsvInsertIntColAfter(    cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, int val, unsigned lexTId )
{
  cmCsvRC_t    rc;
  cmCsvCell_t* ncp;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
    if((rc = cmCsvSetCellInt(h, ncp->row, ncp->col, val )) == kOkCsvRC )
      if( cellPtrPtr != NULL )
        *cellPtrPtr = ncp;
  
  return rc;
}

cmCsvRC_t  cmCsvInsertUIntColAfter(   cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned val, unsigned lexTId )
{
  cmCsvRC_t    rc;
  cmCsvCell_t* ncp;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
    if((rc = cmCsvSetCellUInt(h, ncp->row, ncp->col, val )) == kOkCsvRC )
      if( cellPtrPtr != NULL )
        *cellPtrPtr = ncp;
  
  return rc;
}

cmCsvRC_t  cmCsvInsertHexColAfter(   cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned val, unsigned lexTId )
{
  cmCsvRC_t    rc;
  cmCsvCell_t* ncp;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
    if((rc = cmCsvSetCellHex(h, ncp->row, ncp->col, val )) == kOkCsvRC )
      if( cellPtrPtr != NULL )
        *cellPtrPtr = ncp;
  
  return rc;
}

cmCsvRC_t  cmCsvInsertFloatColAfter(  cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, float val, unsigned lexTId )
{
  cmCsvRC_t    rc;
  cmCsvCell_t* ncp;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
    if((rc = cmCsvSetCellFloat(h, ncp->row, ncp->col, val )) == kOkCsvRC )
      if( cellPtrPtr != NULL )
        *cellPtrPtr = ncp;
  
  return rc;
}

cmCsvRC_t  cmCsvInsertDoubleColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, double val, unsigned lexTId )
{
  cmCsvRC_t    rc;
  cmCsvCell_t* ncp;

  if( cellPtrPtr != NULL )
    *cellPtrPtr = NULL;

  if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC )
    if((rc = cmCsvSetCellDouble(h, ncp->row, ncp->col, val )) == kOkCsvRC )
      if( cellPtrPtr != NULL )
        *cellPtrPtr = ncp;
  
  return rc;
}


cmCsvRC_t  cmCsvWrite( cmCsvH_t h, const char* fn )
{
  FILE*        fp = NULL;
  cmCsvRC_t    rc = kOkCsvRC;
  cmCsv_t*     p  = _cmCsvHandleToPtr(h);
  cmCsvBind_t* bp = p->bindPtr;

  if((fp = fopen(fn,"wt")) == NULL )
    return _cmCsvError(p,kFileCreateErrCsvRC,"Unable to create the output CSV file:'%s'.",fn);

  bp = p->bindPtr;

  // for each row
  while( bp != NULL )
  {
    cmCsvCell_t* cp  = bp->rowPtr;
    unsigned     col = 0;

    // for each column
    for(; cp != NULL; ++col )
    {
      // skip blank columns
      if( cp->col == col )
      {
        const char* tp;

        if((tp = cmHashTblStr(p->htH,cp->symId)) == NULL )
          return _cmCsvError(p,kHashTblErrCsvRC,"Unable to locate the symbol text for cell at row:%i col:%i.",cp->row,cp->col);

        if( cmIsFlag(cp->flags,kTextTMask) )
          fprintf(fp,"\"");

        fputs(tp,fp);

        if( cmIsFlag(cp->flags,kTextTMask) )
          fprintf(fp,"\"");

        cp = cp->rowPtr;
      }
      
      if( cp == NULL )
        fprintf(fp,"\n");  // end of row
      else 
        fprintf(fp,",");   // between columns
    }
    
    bp = bp->linkPtr;
  }

  fclose(fp);
  
  return rc;
}

cmCsvRC_t  cmCsvPrint( cmCsvH_t h, unsigned rowCnt )
{
  cmCsv_t*     p  = _cmCsvHandleToPtr(h);
  cmCsvBind_t* bp = p->bindPtr;
  unsigned     i;

  for(i=0; bp!=NULL && i<rowCnt; ++i,bp=bp->linkPtr)
  {
    cmCsvCell_t* cp = bp->rowPtr;
    unsigned     j;

    for(j=0; cp!=NULL; ++j)
    {
      if( cp->col == j )
      {
        const char* tp;

        if((tp =  cmHashTblStr(p->htH,cp->symId)) == NULL )
          _cmCsvError(p,kHashTblErrCsvRC,"The text associated with the symbol '%i' was not found.",cp->symId);

        fputs(tp,stdin);
      }

      cp=cp->rowPtr;
      if( cp == NULL )
        printf("\n");
      else
        printf(",");

    }
  }
  return kOkCsvRC;
}
