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

typedef struct cmSsBlk_str
{
  char*               blk;
  unsigned            i;
  struct cmSsBlk_str* link;
} cmSsBlk_t;

typedef struct
{
  cmErr_t     err;
  cmLHeapH_t  lhH;  
  unsigned    blkByteCnt;
  cmSsBlk_t*  blp;
  cmSsBlk_t*  elp;
} cmOss_t;

cmStrStreamH_t cmStrStreamNullHandle = cmSTATIC_NULL_HANDLE;

cmOss_t* _cmOssHandleToPtr( cmStrStreamH_t h )
{
  cmOss_t* p = (cmOss_t*)h.h;
  assert(p != NULL );
  return p;
}

cmSsRC_t _cmOssDestroy( cmOss_t* p )
{
  cmSsRC_t rc = kOkSsRC;
  cmLHeapDestroy(&p->lhH);
  cmMemFree(p);
  return rc;
}

cmSsRC_t cmOStrStreamCreate( cmCtx_t* ctx, cmStrStreamH_t* hp, unsigned dfltBlkByteCnt )
{
  cmSsRC_t rc;
  if((rc = cmOStrStreamDestroy(hp)) != kOkSsRC )
    return rc;

  cmOss_t* p = cmMemAllocZ(cmOss_t,1);

  p->blkByteCnt = dfltBlkByteCnt==0 ? 4096 : dfltBlkByteCnt;

  cmErrSetup(&p->err,&ctx->rpt,"OStrStream");
  
  if( cmLHeapIsValid(p->lhH = cmLHeapCreate(p->blkByteCnt+sizeof(cmSsBlk_t),ctx)) == false )
  {
    rc = cmErrMsg(&p->err,kLHeapMemFailSsRC,"Linked heap allocation failed.");
    goto errLabel;
  }
  
  hp->h = p;

 errLabel:
  if(rc != kOkSsRC )
    _cmOssDestroy(p);

  return rc;
}

cmSsRC_t cmOStrStreamDestroy(cmStrStreamH_t* hp )
{
  cmSsRC_t rc = kOkSsRC;

  if( hp==NULL || cmOStrStreamIsValid(*hp)==false )
    return rc;

  cmOss_t* p = _cmOssHandleToPtr(*hp);

  if((rc = _cmOssDestroy(p)) != kOkSsRC )
    return rc;

  hp->h = NULL;

  return rc;
}

bool     cmOStrStreamIsValid( cmStrStreamH_t h )
{ return h.h != NULL; }


cmSsRC_t cmOStrStreamWrite(     cmStrStreamH_t h, const void* vp, unsigned byteCnt )
{
  cmSsRC_t rc = kOkSsRC;

  if( vp==NULL || byteCnt == 0 )
    return rc;

  cmOss_t* p  = _cmOssHandleToPtr(h);
  char*    cp = (char*)vp;
  unsigned j  = 0;

  do
  {
    // if a blk exists
    if( p->elp != NULL )
    {
      // copy as much of vp[] as possible into the current end block
      unsigned n = cmMin(byteCnt, p->blkByteCnt -  p->elp->i);
      memcpy(p->elp->blk + p->elp->i,cp + j,n);
      byteCnt    -= n;
      p->elp->i  += n;   
      j          += n;
    }
    
    // if all of vp[] has been copied then we are done
    if( byteCnt == 0 )
      break;

    assert( p->elp==NULL || p->elp->i == p->blkByteCnt );

    // allocate a new block
    cmSsBlk_t* nbp = (cmSsBlk_t*)cmLHeapAlloc(p->lhH,p->blkByteCnt+sizeof(cmSsBlk_t));
    nbp->blk  = (char*)(nbp + 1);
    nbp->i    = 0;
    nbp->link = NULL;

    // append the new blk onto the end of the list
    if( p->elp == NULL )
      p->blp = nbp;
    else
      p->elp->link = nbp;

    p->elp = nbp;
    
  }while(1);

  return rc;
}

cmSsRC_t cmOStrStreamWriteStr(  cmStrStreamH_t h, const cmChar_t* str )
{
  if( str == NULL )
    return kOkSsRC;

  return cmOStrStreamWrite(h,str,strlen(str));
}

cmSsRC_t cmOStrStreamWriteStrN( cmStrStreamH_t h, const cmChar_t* str, unsigned n )
{ return cmOStrStreamWrite(h,str,n); }

cmSsRC_t cmOStrStreamVPrintf(   cmStrStreamH_t h, const cmChar_t* fmt, va_list vl )  
{
  cmChar_t* s = cmTsVPrintfP(NULL,fmt,vl);
  cmSsRC_t rc = cmOStrStreamWriteStr(h,s);
  cmMemFree(s);
  return rc;
}

cmSsRC_t cmOStrStreamPrintf(    cmStrStreamH_t h, const cmChar_t* fmt, ... )
{
  va_list vl;
  va_start(vl,fmt);
  cmSsRC_t rc = cmOStrStreamVPrintf(h,fmt,vl);
  va_end(vl);
  return rc;
}

unsigned  cmOStrStreamByteCount( cmStrStreamH_t h )
{
  unsigned   n  = 0;
  cmOss_t*   p  = _cmOssHandleToPtr(h);
  cmSsBlk_t* bp = p->blp;

  for(; bp!=NULL; bp=bp->link)
    n += bp->i;

  return n;
}

unsigned  _cmOssCopyBuf( cmOss_t* p, char* buf, unsigned n )
{
  unsigned i   = 0;
  cmSsBlk_t* bp = p->blp;

  for(; bp!=NULL; bp=bp->link)
  {
    assert( i + bp->i <= n );

    memcpy(buf+i,bp->blk,bp->i);
    i += bp->i;
  }

  return i;
}

void*     cmOStrStreamAllocBuf(  cmStrStreamH_t h )
{
  unsigned   n  = cmOStrStreamByteCount(h);
  cmOss_t*   p  = _cmOssHandleToPtr(h);

  if( n == 0 )
    return NULL;

  char*    buf = cmMemAlloc(char,n);

  unsigned i = _cmOssCopyBuf(p,buf,n);

  assert(i==n);

  return buf;
}

cmChar_t* cmOStrStreamAllocText( cmStrStreamH_t h )
{
  unsigned   n  = cmOStrStreamByteCount(h);
  cmOss_t*   p  = _cmOssHandleToPtr(h);

  if( n == 0 )
    return NULL;

  char*    buf = cmMemAlloc(char,n+1);

  unsigned i = _cmOssCopyBuf(p,buf,n);

  assert(i==n);

  buf[n] = 0;

  return buf;
}
