/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-  */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef _MDB_
#include "mdb.h"
#endif

#ifndef _MORK_
#include "mork.h"
#endif

#ifndef _MORKSINK_
#include "morkSink.h"
#endif

#ifndef _MORKENV_
#include "morkEnv.h"
#endif

#ifndef _MORKBLOB_
#include "morkBlob.h"
#endif

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

/*virtual*/ morkSink::~morkSink()
{
  mSink_At = 0;
  mSink_End = 0;
}

/*virtual*/ void
morkSpool::FlushSink(morkEnv* ev) // sync mSpool_Coil->mBuf_Fill
{
  morkCoil* coil = mSpool_Coil;
  if ( coil )
  {
    mork_u1* body = (mork_u1*) coil->mBuf_Body;
    if ( body )
    {
      mork_u1* at = mSink_At;
      mork_u1* end = mSink_End;
      if ( at >= body && at <= end ) // expected cursor order?
      {
        mork_fill fill = (mork_fill) (at - body); // current content size
        if ( fill <= coil->mBlob_Size )
          coil->mBuf_Fill = fill;
        else
        {
          coil->BlobFillOverSizeError(ev);
          coil->mBuf_Fill = coil->mBlob_Size; // make it safe
        }
      }
      else
        this->BadSpoolCursorOrderError(ev);
    }
    else
      coil->NilBufBodyError(ev);
  }
  else
    this->NilSpoolCoilError(ev);
}

/*virtual*/ void
morkSpool::SpillPutc(morkEnv* ev, int c) // grow coil and write byte
{
  morkCoil* coil = mSpool_Coil;
  if ( coil )
  {
    mork_u1* body = (mork_u1*) coil->mBuf_Body;
    if ( body )
    {
      mork_u1* at = mSink_At;
      mork_u1* end = mSink_End;
      if ( at >= body && at <= end ) // expected cursor order?
      {
        mork_size size = coil->mBlob_Size;
        mork_fill fill = (mork_fill) (at - body); // current content size
        if ( fill <= size ) // less content than medium size?
        {
          coil->mBuf_Fill = fill;
          if ( at >= end ) // need to grow the coil?
          {
            if ( size > 2048 ) // grow slower over 2K?
              size += 512;
            else
            {
              mork_size growth = ( size * 4 ) / 3; // grow by 33%
              if ( growth < 64 ) // grow faster under (64 * 3)?
                growth = 64;
              size += growth;
            }
            if ( coil->GrowCoil(ev, size) ) // made coil bigger?
            {
              body = (mork_u1*) coil->mBuf_Body;
              if ( body ) // have a coil body?
              {
                mSink_At = at = body + fill;
                mSink_End = end = body + coil->mBlob_Size;
              }
              else
                coil->NilBufBodyError(ev);
            }
          }
          if ( ev->Good() ) // seem ready to write byte c?
          {
            if ( at < end ) // morkSink::Putc() would succeed?
            {
              *at++ = (mork_u1) c;
              mSink_At = at;
              coil->mBuf_Fill = fill + 1;
            }
            else
              this->BadSpoolCursorOrderError(ev);
          }
        }
        else // fill exceeds size
        {
          coil->BlobFillOverSizeError(ev);
          coil->mBuf_Fill = coil->mBlob_Size; // make it safe
        }
      }
      else
        this->BadSpoolCursorOrderError(ev);
    }
    else
      coil->NilBufBodyError(ev);
  }
  else
    this->NilSpoolCoilError(ev);
}

// ````` ````` ````` `````   ````` ````` ````` `````  
// public: // public non-poly morkSink methods

/*virtual*/
morkSpool::~morkSpool()
// Zero all slots to show this sink is disabled, but destroy no memory.
// Note it is typically unnecessary to flush this coil sink, since all
// content is written directly to the coil without any buffering.
{
  mSink_At = 0;
  mSink_End = 0;
  mSpool_Coil = 0;
}

morkSpool::morkSpool(morkEnv* ev, morkCoil* ioCoil)
// After installing the coil, calls Seek(ev, 0) to prepare for writing.
: morkSink()
, mSpool_Coil( 0 )
{
  mSink_At = 0; // set correctly later in Seek()
  mSink_End = 0; // set correctly later in Seek()
  
  if ( ev->Good() )
  {
    if ( ioCoil )
    {
      mSpool_Coil = ioCoil;
      this->Seek(ev, /*pos*/ 0);
    }
    else
      ev->NilPointerError();
  }
}

// ----- All boolean return values below are equal to ev->Good(): -----

/*static*/ void
morkSpool::BadSpoolCursorOrderError(morkEnv* ev)
{
  ev->NewError("bad morkSpool cursor order");
}

/*static*/ void
morkSpool::NilSpoolCoilError(morkEnv* ev)
{
  ev->NewError("nil mSpool_Coil");
}

mork_bool
morkSpool::Seek(morkEnv* ev, mork_pos inPos)
// Changed the current write position in coil's buffer to inPos.
// For example, to start writing the coil from scratch, use inPos==0.
{
  morkCoil* coil = mSpool_Coil;
  if ( coil )
  {
    mork_size minSize = (mork_size) (inPos + 64);
    
    if ( coil->mBlob_Size < minSize )
      coil->GrowCoil(ev, minSize);
      
    if ( ev->Good() )
    {
      coil->mBuf_Fill = (mork_fill) inPos;
      mork_u1* body = (mork_u1*) coil->mBuf_Body;
      if ( body )
      {
        mSink_At = body + inPos;
        mSink_End = body + coil->mBlob_Size;
      }
      else
        coil->NilBufBodyError(ev);
    }
  }
  else
    this->NilSpoolCoilError(ev);
    
  return ev->Good();
}

mork_bool
morkSpool::Write(morkEnv* ev, const void* inBuf, mork_size inSize)
// write inSize bytes of inBuf to current position inside coil's buffer
{
  // This method is conceptually very similar to morkStream::Write(),
  // and this code was written while looking at that method for clues.
 
  morkCoil* coil = mSpool_Coil;
  if ( coil )
  {
    mork_u1* body = (mork_u1*) coil->mBuf_Body;
    if ( body )
    {
      if ( inBuf && inSize ) // anything to write?
      {
        mork_u1* at = mSink_At;
        mork_u1* end = mSink_End;
        if ( at >= body && at <= end ) // expected cursor order?
        {
          // note coil->mBuf_Fill can be stale after morkSink::Putc():
          mork_pos fill = at - body; // current content size
          mork_num space = (mork_num) (end - at); // space left in body
          if ( space < inSize ) // not enough to hold write?
          {
            mork_size minGrowth = space + 16;
            mork_size minSize = coil->mBlob_Size + minGrowth;
            if ( coil->GrowCoil(ev, minSize) )
            {
              body = (mork_u1*) coil->mBuf_Body;
              if ( body )
              {
                mSink_At = at = body + fill;
                mSink_End = end = body + coil->mBlob_Size;
                space = (mork_num) (end - at); // space left in body
              }
              else
                coil->NilBufBodyError(ev);
            }
          }
          if ( ev->Good() )
          {
            if ( space >= inSize ) // enough room to hold write?
            {
              MORK_MEMCPY(at, inBuf, inSize); // into body
              mSink_At = at + inSize; // advance past written bytes
              coil->mBuf_Fill = fill + inSize; // "flush" to fix fill
            }
            else
              ev->NewError("insufficient morkSpool space");
          }
        }
        else
          this->BadSpoolCursorOrderError(ev);
      }
    }
    else
      coil->NilBufBodyError(ev);
  }
  else
    this->NilSpoolCoilError(ev);
  
  return ev->Good();
}

mork_bool
morkSpool::PutString(morkEnv* ev, const char* inString)
// call Write() with inBuf=inString and inSize=strlen(inString),
// unless inString is null, in which case we then do nothing at all.
{
  if ( inString )
  {
    mork_size size = MORK_STRLEN(inString);
    this->Write(ev, inString, size);
  }
  return ev->Good();
}

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789