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

cmSymTblH_t cmSymTblNullHandle = cmSTATIC_NULL_HANDLE;

enum
{
  kDynStFl = 0x01
};

typedef struct cmSymLabel_str
{
  unsigned        flags;
  const cmChar_t* label;
} cmSymLabel_t;


struct cmSym_str;

typedef struct cmSymAvail_str
{
  unsigned          id;
  struct cmSym_str* linkPtr;
} cmSymAvail_t;

typedef struct cmSym_str
{
  union
  {
    cmSymLabel_t label;
    cmSymAvail_t avail;
  } u;
} cmSym_t;

typedef struct cmSymBlock_t
{
  cmSym_t*             base;   // block base - contains cmSymTbl_t.symPerBlock cmSym_t records
  unsigned             cnt;    // cout of symbols actually used in this block
  struct cmSymBlock_t* link;   // next block
} cmSymBlock_t;

typedef struct
{
  cmSymTblH_t   parentH;        // parent symbols table
  cmLHeapH_t    heapH;          // symbol label storage
  cmSymBlock_t* first;          // pointer to head of symbol block chain
  cmSymBlock_t* last;           // pointer to last symbol block in chain
  unsigned      blkCnt;         // count of blocks on chain
  unsigned      symCnt;         // total count of symbols controlled by this symbol (does not include parent symbols)
  unsigned      baseSymId; 
  unsigned      symPerBlock;
  cmSym_t*      availPtr;
} cmSymTbl_t;

cmSymTbl_t* _cmSymTblHandleToPtr( cmSymTblH_t h )
{
  cmSymTbl_t* stp = (cmSymTbl_t*)h.h;
  assert( stp != NULL );
  return stp;
}

cmSymBlock_t*  _cmSymTblAllocateBlock( cmSymTbl_t* stp )
{
  // allocate the new block
  cmSymBlock_t* sbp = (cmSymBlock_t*)cmMemMallocZ( sizeof(cmSymBlock_t) + (stp->symPerBlock * sizeof(cmSym_t)));

  // initialize the new block control recd
  sbp->base = (cmSym_t*)(sbp+1);
  sbp->cnt  = 0;
  sbp->link = NULL;

  // add the new block control recd to the end of the block chain
  if( stp->last == NULL )
    stp->first = sbp;
  else
    stp->last->link = sbp;

  // the new block control recd always becomes the last recd in the chain
  stp->last = sbp;

  ++stp->blkCnt;

  return sbp;
}

bool _cmSymTblIsSymbolRemoved( cmSymTbl_t* stp, unsigned symId )
{
  const cmSym_t* sp = stp->availPtr;
  for(; sp != NULL; sp=sp->u.avail.linkPtr)
    if( sp->u.avail.id == symId )
      return true;
  return false;
}

unsigned _cmSymTblLabelToId( cmSymTbl_t* stp, const char* label )
{
  cmSymBlock_t* sbp = stp->first;

  unsigned symId = stp->baseSymId;

  while( sbp != NULL )
  {
    cmSym_t* sp = sbp->base;
    cmSym_t* ep = sbp->base + sbp->cnt;

    for(; sp<ep; ++sp,++symId)
    {
      if( _cmSymTblIsSymbolRemoved(stp, symId ) == false )
        if( strcmp( sp->u.label.label, label ) == 0 )
          return symId;
    }
    sbp = sbp->link;
  }

  return cmInvalidId; 
}

cmSym_t* _cmSymTblIdToSymPtr( cmSymTbl_t* stp, unsigned symId )
{
  if( cmSymTblIsValid(stp->parentH) && cmSymTblIsValidId( stp->parentH, symId ) )
    return _cmSymTblIdToSymPtr( _cmSymTblHandleToPtr(stp->parentH), symId );

  symId -= stp->baseSymId;

  unsigned    n   = symId / stp->symPerBlock;
  unsigned    i   = symId % stp->symPerBlock;

  if( n >= stp->blkCnt )
    return NULL;

  cmSymBlock_t* sbp = stp->first;

  unsigned j;
  for(j=0; j<n; ++j)
    sbp = sbp->link;

  if( i >= sbp->cnt )
    return NULL;

  return sbp->base + i;
}

cmSymTblH_t cmSymTblCreate(           cmSymTblH_t parentH, unsigned baseSymId, cmCtx_t* ctx )
{
  cmSymTblH_t h;
  cmSymTbl_t* stp = cmMemAllocZ( cmSymTbl_t, 1 );

  stp->heapH       = cmLHeapCreate( 2048, ctx );
  stp->symPerBlock = 3;
  stp->baseSymId   = baseSymId;
  stp->parentH     = parentH;

  _cmSymTblAllocateBlock( stp );


  h.h = stp;
  return h;
}

void        cmSymTblDestroy( cmSymTblH_t* hp )
{
  if( hp==NULL || hp->h == NULL )
    return;

  cmSymTbl_t*   stp = _cmSymTblHandleToPtr(*hp);

  assert( stp != NULL );

  cmSymBlock_t* sbp = stp->first;
  while( sbp != NULL )
  {
    cmSymBlock_t* t = sbp;
    sbp = sbp->link;
    cmMemFree(t);
  }

  cmLHeapDestroy(&stp->heapH); 

  cmMemFree(hp->h);

  hp->h = NULL;

}

unsigned    cmSymTblRegister( cmSymTblH_t h, const char* label, bool staticFl )
{
  cmSymTbl_t* stp   = _cmSymTblHandleToPtr(h);
  unsigned    symId;
  unsigned    flags = 0;
  cmSym_t*    sp    = NULL;

  // check for the label in the local symbol table
  if((symId = _cmSymTblLabelToId( stp, label )) != cmInvalidId )
    return symId;

  // check for the label in the parent symbol table
  if( cmSymTblIsValid(stp->parentH) )
    if((symId = _cmSymTblLabelToId( _cmSymTblHandleToPtr(stp->parentH), label)) != cmInvalidId )
      return symId;

  // if the label is not static then create a copy of it on the local heap
  if( !staticFl )
  {
    char* cp = (char*)cmLHeapAlloc( stp->heapH, strlen(label) + 1 );
    strcpy(cp,label);
    label = cp;
    flags |= kDynStFl;
  }

  // if there are no previosly removed symbols available
  if( stp->availPtr == NULL )
  {
    cmSymBlock_t*sbp = stp->last;

    // if the last block is full
    if( sbp->cnt == stp->symPerBlock )
      sbp = _cmSymTblAllocateBlock(stp);

    // the last block must now have an empty slot
    assert( sbp->cnt < stp->symPerBlock );


    unsigned idx = sbp->cnt++;
    sp = sbp->base + idx;
    //sbp->base[ idx ].u.label.label = label;
    //sbp->base[ idx ].u.label.flags = flags;

    // calculate the symbol id
    symId = stp->baseSymId + ((stp->blkCnt-1) * stp->symPerBlock) + sbp->cnt - 1;
  }
  else  // there are previously removed symbols available
  {
    sp            = stp->availPtr;        // get the next avail symbol
    stp->availPtr = sp->u.avail.linkPtr;  // take it off the avail list
    symId         = sp->u.avail.id;       // store the new symbol's id
  }

  // setup the symbol record
  sp->u.label.label = label;
  sp->u.label.flags = flags;

  // verify that the new symId does not already belong to the parent
  assert( cmSymTblIsValid(stp->parentH)==false ? 1 : cmSymTblLabel( stp->parentH, symId)==NULL );

  ++stp->symCnt;

  return symId;
  
}

unsigned    cmSymTblRegisterSymbol(       cmSymTblH_t h, const char* label )
{ return cmSymTblRegister( h, label, false ); }

unsigned    cmSymTblRegisterStaticSymbol( cmSymTblH_t h, const char* label )
{ return cmSymTblRegister( h, label, true ); }

unsigned    cmSymTblRegisterVFmt( cmSymTblH_t h, const cmChar_t* fmt, va_list vl )
{
  unsigned n = vsnprintf(NULL,0,fmt,vl);
  cmChar_t b[n+1];
  vsnprintf(b,n,fmt,vl);
  return cmSymTblRegister(h,fmt,vl);

}

unsigned    cmSymTblRegisterFmt( cmSymTblH_t h, const cmChar_t* fmt, ... )
{
  va_list vl;
  va_start(vl,fmt);
  unsigned id = cmSymTblRegisterVFmt(h,fmt,vl);
  va_end(vl);
  return id;
}


bool        cmSymTblRemove( cmSymTblH_t h, unsigned symId )
{
  cmSymTbl_t* stp = _cmSymTblHandleToPtr(h);

  cmSym_t* sp;

  if((sp = _cmSymTblIdToSymPtr(stp,symId)) == NULL )
    return false;

  if( cmIsFlag(sp->u.label.flags,kDynStFl))
    cmLHeapFree(stp->heapH,(void*)sp->u.label.label);

  sp->u.avail.id      = symId;
  sp->u.avail.linkPtr = stp->availPtr;
  stp->availPtr       = sp;
  

  return true;
}


const char* cmSymTblLabel( cmSymTblH_t h, unsigned    symId )
{

  cmSymTbl_t* stp = _cmSymTblHandleToPtr(h);

  cmSym_t* sp;

  if((sp = _cmSymTblIdToSymPtr(stp,symId)) == NULL )
    return NULL;

  return sp->u.label.label;
}

unsigned    cmSymTblId(                   cmSymTblH_t h, const char* label )
{
  cmSymTbl_t* stp = _cmSymTblHandleToPtr(h);
  unsigned     id;
  if((id=_cmSymTblLabelToId(stp,label)) == cmInvalidId )
    if( cmSymTblIsValid(stp->parentH))
      return _cmSymTblLabelToId( _cmSymTblHandleToPtr(stp->parentH), label );
  return id;
}

bool        cmSymTblIsValid(               cmSymTblH_t h )
{ return h.h != NULL; }

bool        cmSymTblIsValidId(            cmSymTblH_t h, unsigned symId )
{
  cmSymTbl_t* stp = _cmSymTblHandleToPtr(h);
  return stp->baseSymId <= symId && symId < (stp->baseSymId + stp->symCnt);
}

void        cmSymTblReport(               cmSymTblH_t h )
{
  cmSymTbl_t*   stp = _cmSymTblHandleToPtr(h);
  cmSymBlock_t* sbp = stp->first;
  unsigned i=0, j=0, symId = stp->baseSymId;

  printf("blks:%i syms:%i\n", stp->blkCnt, stp->symCnt );

  for(; sbp != NULL; sbp=sbp->link,++i)
    for(j=0; j<sbp->cnt; ++j,++symId)
    {
      bool remFl =  _cmSymTblIsSymbolRemoved(stp, symId );
      printf("blk:%i sym:%i id:%i label:%s\n",i,j,symId,remFl ? "<removed>" : sbp->base[j].u.label.label);
    }

}

//{ { label:cmSymTblEx }
//(
//  cmSymTblTest() gives a usage example for the symbol table component.
//)

//[
void cmSymTblTest( cmCtx_t* ctx )
{
  unsigned    baseSymId = 100;
  unsigned    i;
  unsigned    n = 10;
  unsigned    idArray[n];

  // create a symbol table
  cmSymTblH_t h = cmSymTblCreate( cmSymTblNullHandle, baseSymId, ctx );

  if( cmSymTblIsValid(h) == false )
  {
    cmRptPrintf(&ctx->rpt,"Symbol table creation failed.");
    return;
  }

  // generate and register some symbols
  for(i=0; i<n; ++i)
  {
    bool staticFl = false;
    char str[10];
    snprintf(str,9,"sym%i",i);
    idArray[i] = cmSymTblRegister( h, str, staticFl );    
  }

  // remove  the fourth symbol generated
  cmSymTblRemove( h, baseSymId+3 );
  
  // print the symbol table
  cmSymTblReport(h);

  // iterate through the symbol table
  for(i=0; i<n; ++i)
  {
    const cmChar_t* lbl = cmSymTblLabel(h,idArray[i]);

    if( lbl == NULL )
      cmRptPrintf(&ctx->rpt,"%i <removed>\n",i);
    else
      cmRptPrintf(&ctx->rpt,"%i %i==%i %s \n",i,idArray[i],cmSymTblId(h,lbl),lbl);
  }

  // release the symbol table
  cmSymTblDestroy(&h);

  return;
}
//]
//}
