/* -*- 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 _MORKNODE_
#include "morkNode.h"
#endif

#ifndef _MORKFILE_
#include "morkFile.h"
#endif

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

#ifndef _MORKSTREAM_
#include "morkStream.h"
#endif

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

// ````` ````` ````` ````` ````` 
// { ===== begin morkNode interface =====

/*public virtual*/ void
morkStream::CloseMorkNode(morkEnv* ev) // CloseStream() only if open
{
  if ( this->IsOpenNode() )
  {
    this->MarkClosing();
    this->CloseStream(ev);
    this->MarkShut();
  }
}

/*public virtual*/
morkStream::~morkStream() // assert CloseStream() executed earlier
{
  MORK_ASSERT(mStream_ContentFile==0);
  MORK_ASSERT(mStream_Buf==0);
}

/*public non-poly*/
morkStream::morkStream(morkEnv* ev, const morkUsage& inUsage,
  nsIMdbHeap* ioHeap,
  nsIMdbFile* ioContentFile, mork_size inBufSize, mork_bool inFrozen)
: morkFile(ev, inUsage, ioHeap, ioHeap)
, mStream_At( 0 )
, mStream_ReadEnd( 0 )
, mStream_WriteEnd( 0 )

, mStream_ContentFile( 0 )

, mStream_Buf( 0 )
, mStream_BufSize( inBufSize )
, mStream_BufPos( 0 )
, mStream_Dirty( morkBool_kFalse )
, mStream_HitEof( morkBool_kFalse )
{
  if ( ev->Good() )
  {
    if ( inBufSize < morkStream_kMinBufSize )
      mStream_BufSize = inBufSize = morkStream_kMinBufSize;
    else if ( inBufSize > morkStream_kMaxBufSize )
      mStream_BufSize = inBufSize = morkStream_kMaxBufSize;
    
    if ( ioContentFile && ioHeap )
    {
      // if ( ioContentFile->FileFrozen() ) // forced to be readonly?
      //   inFrozen = morkBool_kTrue; // override the input value
        
      nsIMdbFile_SlotStrongFile(ioContentFile, ev, &mStream_ContentFile);
      if ( ev->Good() )
      {
        mork_u1* buf = 0;
        ioHeap->Alloc(ev->AsMdbEnv(), inBufSize, (void**) &buf);
        if ( buf )
        {
          mStream_At = mStream_Buf = buf;
          
          if ( !inFrozen )
          {
            // physical buffer end never moves:
            mStream_WriteEnd = buf + inBufSize;
          }
          else
            mStream_WriteEnd = 0; // no writing is allowed
          
          if ( inFrozen )
          {
            // logical buffer end starts at Buf with no content:
            mStream_ReadEnd = buf;
            this->SetFileFrozen(inFrozen);
          }
          else
            mStream_ReadEnd = 0; // no reading is allowed
          
          this->SetFileActive(morkBool_kTrue);
          this->SetFileIoOpen(morkBool_kTrue);
        }
        if ( ev->Good() )
          mNode_Derived = morkDerived_kStream;
      }
    }
    else ev->NilPointerError();
  }
}

/*public non-poly*/ void
morkStream::CloseStream(morkEnv* ev) // called by CloseMorkNode();
{
    if ( this->IsNode() )
    {
      nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev, &mStream_ContentFile);
      nsIMdbHeap* heap = mFile_SlotHeap;
      mork_u1* buf = mStream_Buf;
      mStream_Buf = 0;
      
      if ( heap && buf )
        heap->Free(ev->AsMdbEnv(), buf);

      this->CloseFile(ev);
      this->MarkShut();
    }
    else
      this->NonNodeError(ev);
}

// } ===== end morkNode methods =====
// ````` ````` ````` ````` ````` 
  
#define morkStream_kSpacesPerIndent 1 /* one space per indent */
#define morkStream_kMaxIndentDepth 70 /* max indent of 70 space bytes */
static const char morkStream_kSpaces[] // next line to ease length perception
= "                                                                        ";
// 123456789_123456789_123456789_123456789_123456789_123456789_123456789_
// morkStream_kSpaces above must contain (at least) 70 spaces (ASCII 0x20)
 
mork_size
morkStream::PutIndent(morkEnv* ev, mork_count inDepth)
  // PutIndent() puts a linebreak, and then
  // "indents" by inDepth, and returns the line length after indentation.
{
  mork_size outLength = 0;
  nsIMdbEnv *mev = ev->AsMdbEnv();
  if ( ev->Good() )
  {
    this->PutLineBreak(ev);
    if ( ev->Good() )
    {
      outLength = inDepth;
      mdb_size bytesWritten;
      if ( inDepth )
        this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten);
    }
  }
  return outLength;
}

mork_size
morkStream::PutByteThenIndent(morkEnv* ev, int inByte, mork_count inDepth)
  // PutByteThenIndent() puts the byte, then a linebreak, and then
  // "indents" by inDepth, and returns the line length after indentation.
{
  mork_size outLength = 0;
  nsIMdbEnv *mev = ev->AsMdbEnv();
  
  if ( inDepth > morkStream_kMaxIndentDepth )
    inDepth = morkStream_kMaxIndentDepth;
  
  this->Putc(ev, inByte);
  if ( ev->Good() )
  {
    this->PutLineBreak(ev);
    if ( ev->Good() )
    {
      outLength = inDepth;
      mdb_size bytesWritten;
      if ( inDepth )
        this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten);
    }
  }
  return outLength;
}
  
mork_size
morkStream::PutStringThenIndent(morkEnv* ev,
  const char* inString, mork_count inDepth)
// PutStringThenIndent() puts the string, then a linebreak, and then
// "indents" by inDepth, and returns the line length after indentation.
{
  mork_size outLength = 0;
  mdb_size bytesWritten;
  nsIMdbEnv *mev = ev->AsMdbEnv();
  
  if ( inDepth > morkStream_kMaxIndentDepth )
    inDepth = morkStream_kMaxIndentDepth;
  
  if ( inString )
  {
    mork_size length = MORK_STRLEN(inString);
    if ( length && ev->Good() ) // any bytes to write?
      this->Write(mev, inString, length, &bytesWritten);
  }
  
  if ( ev->Good() )
  {
    this->PutLineBreak(ev);
    if ( ev->Good() )
    {
      outLength = inDepth;
      if ( inDepth )
        this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten);
    }
  }
  return outLength;
}

mork_size
morkStream::PutString(morkEnv* ev, const char* inString)
{
  nsIMdbEnv *mev = ev->AsMdbEnv();
  mork_size outSize = 0;
  mdb_size bytesWritten;
  if ( inString )
  {
    outSize = MORK_STRLEN(inString);
    if ( outSize && ev->Good() ) // any bytes to write?
    {
      this->Write(mev, inString, outSize, &bytesWritten);
    }
  }
  return outSize;
}

mork_size
morkStream::PutStringThenNewline(morkEnv* ev, const char* inString)
  // PutStringThenNewline() returns total number of bytes written.
{
  nsIMdbEnv *mev = ev->AsMdbEnv();
  mork_size outSize = 0;
  mdb_size bytesWritten;
  if ( inString )
  {
    outSize = MORK_STRLEN(inString);
    if ( outSize && ev->Good() ) // any bytes to write?
    {
      this->Write(mev, inString, outSize, &bytesWritten);
      if ( ev->Good() )
        outSize += this->PutLineBreak(ev);
    }
  }
  return outSize;
}

mork_size
morkStream::PutByteThenNewline(morkEnv* ev, int inByte)
  // PutByteThenNewline() returns total number of bytes written.
{
  mork_size outSize = 1; // one for the following byte
  this->Putc(ev, inByte);
  if ( ev->Good() )
    outSize += this->PutLineBreak(ev);
  return outSize;
}

mork_size
morkStream::PutLineBreak(morkEnv* ev)
{
#if defined(MORK_MAC)

  this->Putc(ev, mork_kCR);
  return 1;
  
#else
#  if defined(MORK_WIN)
  
  this->Putc(ev, mork_kCR);
  this->Putc(ev, mork_kLF);
  return 2;
  
#  else
#    ifdef MORK_UNIX
  
  this->Putc(ev, mork_kLF);
  return 1;
  
#    endif /* MORK_UNIX */
#  endif /* MORK_WIN */
#endif /* MORK_MAC */
}
// ````` ````` ````` `````   ````` ````` ````` `````  
// public: // virtual morkFile methods


NS_IMETHODIMP
morkStream::Steal(nsIMdbEnv* mev, nsIMdbFile* ioThief)
  // Steal: tell this file to close any associated i/o stream in the file
  // system, because the file ioThief intends to reopen the file in order
  // to provide the MDB implementation with more exotic file access than is
  // offered by the nsIMdbFile alone.  Presumably the thief knows enough
  // from Path() in order to know which file to reopen.  If Steal() is
  // successful, this file should probably delegate all future calls to
  // the nsIMdbFile interface down to the thief files, so that even after
  // the file has been stolen, it can still be read, written, or forcibly
  // closed (by a call to CloseMdbObject()).
{
  MORK_USED_1(ioThief);
  morkEnv *ev = morkEnv::FromMdbEnv(mev);
  ev->StubMethodOnlyError();
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
morkStream::BecomeTrunk(nsIMdbEnv* mev)
  // If this file is a file version branch created by calling AcquireBud(),
  // BecomeTrunk() causes this file's content to replace the original
  // file's content, typically by assuming the original file's identity.
{
  morkEnv *ev = morkEnv::FromMdbEnv(mev);
  ev->StubMethodOnlyError();
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
morkStream::AcquireBud(nsIMdbEnv* mev, nsIMdbHeap* ioHeap, nsIMdbFile **acqBud)
  // AcquireBud() starts a new "branch" version of the file, empty of content,
  // so that a new version of the file can be written.  This new file
  // can later be told to BecomeTrunk() the original file, so the branch
  // created by budding the file will replace the original file.  Some
  // file subclasses might initially take the unsafe but expedient
  // approach of simply truncating this file down to zero length, and
  // then returning the same morkFile pointer as this, with an extra
  // reference count increment.  Note that the caller of AcquireBud() is
  // expected to eventually call CutStrongRef() on the returned file
  // in order to release the strong reference.  High quality versions
  // of morkFile subclasses will create entirely new files which later
  // are renamed to become the old file, so that better transactional
  // behavior is exhibited by the file, so crashes protect old files.
  // Note that AcquireBud() is an illegal operation on readonly files.
{
  MORK_USED_1(ioHeap);
  morkFile* outFile = 0;
  nsIMdbFile* file = mStream_ContentFile;
  morkEnv *ev = morkEnv::FromMdbEnv(mev);
  if ( this->IsOpenAndActiveFile() && file )
  {
    // figure out how this interacts with buffering and mStream_WriteEnd:
    ev->StubMethodOnlyError();
  }
  else this->NewFileDownError(ev);
  
  *acqBud = outFile;
  return NS_ERROR_NOT_IMPLEMENTED;
}

mork_pos 
morkStream::Length(morkEnv* ev) const // eof
{
  mork_pos outPos = 0;

  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenAndActiveFile() && file )
  {
    mork_pos contentEof = 0;
    file->Eof(ev->AsMdbEnv(), &contentEof);
    if ( ev->Good() )
    {
      if ( mStream_WriteEnd ) // this stream supports writing?
      {
        // the local buffer might have buffered content past content eof
        if ( ev->Good() ) // no error happened during Length() above?
        {
          mork_u1* at = mStream_At;
          mork_u1* buf = mStream_Buf;
          if ( at >= buf ) // expected cursor order?
          {
            mork_pos localContent = mStream_BufPos + (at - buf);
            if ( localContent > contentEof ) // buffered past eof?
              contentEof = localContent; // return new logical eof

            outPos = contentEof;
          }
          else this->NewBadCursorOrderError(ev);
        }
      }
      else
        outPos = contentEof; // frozen files get length from content file
    }
  }
  else this->NewFileDownError(ev);

  return outPos;
}

void morkStream::NewBadCursorSlotsError(morkEnv* ev) const
{ ev->NewError("bad stream cursor slots"); }

void morkStream::NewNullStreamBufferError(morkEnv* ev) const
{ ev->NewError("null stream buffer"); }

void morkStream::NewCantReadSinkError(morkEnv* ev) const
{ ev->NewError("can not read stream sink"); }

void morkStream::NewCantWriteSourceError(morkEnv* ev) const
{ ev->NewError("can not write stream source"); }

void morkStream::NewPosBeyondEofError(morkEnv* ev) const
{ ev->NewError("stream pos beyond eof"); }

void morkStream::NewBadCursorOrderError(morkEnv* ev) const
{ ev->NewError("bad stream cursor order"); }

NS_IMETHODIMP 
morkStream::Tell(nsIMdbEnv* mdbev, mork_pos *aOutPos) const
{
  nsresult rv = NS_OK;
  morkEnv *ev = morkEnv::FromMdbEnv(mdbev);

  NS_ENSURE_ARG_POINTER(aOutPos);
  
  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenAndActiveFile() && file )
  {
    mork_u1* buf = mStream_Buf;
    mork_u1* at = mStream_At;
    
    mork_u1* readEnd = mStream_ReadEnd;   // nonzero only if readonly
    mork_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly
    
    if ( writeEnd )
    {
      if ( buf && at >= buf && at <= writeEnd ) 
      {
        *aOutPos = mStream_BufPos + (at - buf);
      }
      else this->NewBadCursorOrderError(ev);
    }
    else if ( readEnd )
    {
      if ( buf && at >= buf && at <= readEnd ) 
      {
        *aOutPos = mStream_BufPos + (at - buf);
      }
      else this->NewBadCursorOrderError(ev);
    }
  }
  else this->NewFileDownError(ev);

  return rv;
}

NS_IMETHODIMP 
morkStream::Read(nsIMdbEnv* mdbev, void* outBuf, mork_size inSize, mork_size *aOutSize)
{
  NS_ENSURE_ARG_POINTER(aOutSize);
  // First we satisfy the request from buffered bytes, if any.  Then
  // if additional bytes are needed, we satisfy these by direct reads
  // from the content file without any local buffering (but we still need
  // to adjust the buffer position to reflect the current i/o point).

  morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
  nsresult rv = NS_OK;

  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenAndActiveFile() && file )
  {
    mork_u1* end = mStream_ReadEnd; // byte after last buffered byte
    if ( end ) // file is open for read access?
    {
      if ( inSize ) // caller wants any output?
      {
        mork_u1* sink = (mork_u1*) outBuf; // where we plan to write bytes
        if ( sink ) // caller passed good buffer address?
        {
          mork_u1* at = mStream_At;
          mork_u1* buf = mStream_Buf;
          if ( at >= buf && at <= end ) // expected cursor order?
          {
            mork_num remaining = (mork_num) (end - at); // bytes left in buffer
            
            mork_num quantum = inSize; // number of bytes to copy
            if ( quantum > remaining ) // more than buffer content?
              quantum = remaining; // restrict to buffered bytes
              
            if ( quantum ) // any bytes left in the buffer?
            {
              MORK_MEMCPY(sink, at, quantum); // from buffer bytes
              
              at += quantum; // advance past read bytes
              mStream_At = at;
              *aOutSize += quantum;  // this much copied so far

              sink += quantum;   // in case we need to copy more
              inSize -= quantum; // filled this much of request
              mStream_HitEof = morkBool_kFalse;
            }
            
            if ( inSize ) // we still need to read more content?
            {
              // We need to read more bytes directly from the
              // content file, without local buffering.  We have
              // exhausted the local buffer, so we need to show
              // it is now empty, and adjust the current buf pos.
              
              mork_num posDelta = (mork_num) (at - buf); // old buf content
              mStream_BufPos += posDelta;   // past now empty buf
              
              mStream_At = mStream_ReadEnd = buf; // empty buffer
              
              // file->Seek(ev, mStream_BufPos); // set file pos
              // if ( ev->Good() ) // no seek error?
              // {
              // }
              
              mork_num actual = 0;
              nsIMdbEnv* menv = ev->AsMdbEnv();
              file->Get(menv, sink, inSize, mStream_BufPos, &actual);
              if ( ev->Good() ) // no read error?
              {
                if ( actual )
                {
                  *aOutSize += actual;
                  mStream_BufPos += actual;
                  mStream_HitEof = morkBool_kFalse;
                }
                else if ( !*aOutSize )
                  mStream_HitEof = morkBool_kTrue;
              }
            }
          }
          else this->NewBadCursorOrderError(ev);
        }
        else this->NewNullStreamBufferError(ev);
      }
    }
    else this->NewCantReadSinkError(ev);
  }
  else this->NewFileDownError(ev);
  
  if ( ev->Bad() )
    *aOutSize = 0;

  return rv;
}

NS_IMETHODIMP 
morkStream::Seek(nsIMdbEnv * mdbev, mork_pos inPos, mork_pos *aOutPos)
{
  NS_ENSURE_ARG_POINTER(aOutPos);
  morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
  *aOutPos = 0;
  nsresult rv = NS_OK;
  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenOrClosingNode() && this->FileActive() && file )
  {
    mork_u1* at = mStream_At;             // current position in buffer
    mork_u1* buf = mStream_Buf;           // beginning of buffer 
    mork_u1* readEnd = mStream_ReadEnd;   // nonzero only if readonly
    mork_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly
    
    if ( writeEnd ) // file is mutable/writeonly?
    {
      if ( mStream_Dirty ) // need to commit buffer changes?
        this->Flush(mdbev);

      if ( ev->Good() ) // no errors during flush or earlier?
      {
        if ( at == buf ) // expected post flush cursor value?
        {
          if ( mStream_BufPos != inPos ) // need to change pos?
          {
            mork_pos eof = 0;
            nsIMdbEnv* menv = ev->AsMdbEnv();
            file->Eof(menv, &eof);
            if ( ev->Good() ) // no errors getting length?
            {
              if ( inPos <= eof ) // acceptable new position?
              {
                mStream_BufPos = inPos; // new stream position
                *aOutPos = inPos;
              }
              else this->NewPosBeyondEofError(ev);
            }
          }
        }
        else this->NewBadCursorOrderError(ev);
      }
    }
    else if ( readEnd ) // file is frozen/readonly?
    {
      if ( at >= buf && at <= readEnd ) // expected cursor order?
      {
        mork_pos eof = 0;
        nsIMdbEnv* menv = ev->AsMdbEnv();
        file->Eof(menv, &eof);
        if ( ev->Good() ) // no errors getting length?
        {
          if ( inPos <= eof ) // acceptable new position?
          {
            *aOutPos = inPos;
            mStream_BufPos = inPos; // new stream position
            mStream_At = mStream_ReadEnd = buf; // empty buffer
            if ( inPos == eof ) // notice eof reached?
              mStream_HitEof = morkBool_kTrue;
          }
          else this->NewPosBeyondEofError(ev);
        }
      }
      else this->NewBadCursorOrderError(ev);
    }
      
  }
  else this->NewFileDownError(ev);

  return rv;
}

NS_IMETHODIMP 
morkStream::Write(nsIMdbEnv* menv, const void* inBuf, mork_size inSize, mork_size  *aOutSize)
{
  mork_num outActual = 0;
  morkEnv *ev = morkEnv::FromMdbEnv(menv);

  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenActiveAndMutableFile() && file )
  {
    mork_u1* end = mStream_WriteEnd; // byte after last buffered byte
    if ( end ) // file is open for write access?
    {
      if ( inSize ) // caller provided any input?
      {
        const mork_u1* source = (const mork_u1*) inBuf; // from where
        if ( source ) // caller passed good buffer address?
        {
          mork_u1* at = mStream_At;
          mork_u1* buf = mStream_Buf;
          if ( at >= buf && at <= end ) // expected cursor order?
          {
            mork_num space = (mork_num) (end - at); // space left in buffer
            
            mork_num quantum = inSize; // number of bytes to write
            if ( quantum > space ) // more than buffer size?
              quantum = space; // restrict to avail space
              
            if ( quantum ) // any space left in the buffer?
            {
              mStream_Dirty = morkBool_kTrue; // to ensure later flush
              MORK_MEMCPY(at, source, quantum); // into buffer
              
              mStream_At += quantum; // advance past written bytes
              outActual += quantum;  // this much written so far

              source += quantum; // in case we need to write more
              inSize -= quantum; // filled this much of request
            }
            
            if ( inSize ) // we still need to write more content?
            {
              // We need to write more bytes directly to the
              // content file, without local buffering.  We have
              // exhausted the local buffer, so we need to flush
              // it and empty it, and adjust the current buf pos.
              // After flushing, if the rest of the write fits
              // inside the buffer, we will put bytes into the
              // buffer rather than write them to content file.
              
              if ( mStream_Dirty )
                this->Flush(menv); // will update mStream_BufPos

              at = mStream_At;
              if ( at < buf || at > end ) // bad cursor?
                this->NewBadCursorOrderError(ev);
                
              if ( ev->Good() ) // no errors?
              {
                space = (mork_num) (end - at); // space left in buffer
                if ( space > inSize ) // write to buffer?
                {
                  mStream_Dirty = morkBool_kTrue; // ensure flush
                  MORK_MEMCPY(at, source, inSize); // copy
                  
                  mStream_At += inSize; // past written bytes
                  outActual += inSize;  // this much written
                }
                else // directly to content file instead
                {
                  // file->Seek(ev, mStream_BufPos); // set pos
                  // if ( ev->Good() ) // no seek error?
                  // {
                  // }

                  mork_num actual = 0;
                  file->Put(menv, source, inSize, mStream_BufPos, &actual);
                  if ( ev->Good() ) // no write error?
                  {
                    outActual += actual;
                    mStream_BufPos += actual;
                  }
                }
              }
            }
          }
          else this->NewBadCursorOrderError(ev);
        }
        else this->NewNullStreamBufferError(ev);
      }
    }
    else this->NewCantWriteSourceError(ev);
  }
  else this->NewFileDownError(ev);
  
  if ( ev->Bad() )
    outActual = 0;

  *aOutSize = outActual;
  return ev->AsErr();
}

NS_IMETHODIMP     
morkStream::Flush(nsIMdbEnv* ev)
{
  morkEnv *mev = morkEnv::FromMdbEnv(ev);
  nsresult rv = NS_ERROR_FAILURE;
  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenOrClosingNode() && this->FileActive() && file )
  {
    if ( mStream_Dirty )
      this->spill_buf(mev);

    rv = file->Flush(ev);
  }
  else this->NewFileDownError(mev);
  return rv;
}

// ````` ````` ````` `````   ````` ````` ````` `````  
// protected: // protected non-poly morkStream methods (for char io)

int
morkStream::fill_getc(morkEnv* ev)
{
  int c = EOF;
  
  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenAndActiveFile() && file )
  {
    mork_u1* buf = mStream_Buf;
    mork_u1* end = mStream_ReadEnd; // beyond buf after earlier read
    if ( end > buf ) // any earlier read bytes buffered?
    {
      mStream_BufPos += ( end - buf ); // advance past old read
    }
      
    if ( ev->Good() ) // no errors yet?
    {
      // file->Seek(ev, mStream_BufPos); // set file pos
      // if ( ev->Good() ) // no seek error?
      // {
      // }

      nsIMdbEnv* menv = ev->AsMdbEnv();
      mork_num actual = 0;
      file->Get(menv, buf, mStream_BufSize, mStream_BufPos, &actual);
      if ( ev->Good() ) // no read errors?
      {
        if ( actual > mStream_BufSize ) // more than asked for??
          actual = mStream_BufSize;
        
        mStream_At = buf;
        mStream_ReadEnd = buf + actual;
        if ( actual ) // any bytes actually read?
        {
          c = *mStream_At++; // return first byte from buffer
          mStream_HitEof = morkBool_kFalse;
        }
        else
          mStream_HitEof = morkBool_kTrue;
      }
    }
  }
  else this->NewFileDownError(ev);
  
  return c;
}

void
morkStream::spill_putc(morkEnv* ev, int c)
{
  this->spill_buf(ev);
  if ( ev->Good() && mStream_At < mStream_WriteEnd )
    this->Putc(ev, c);
}

void
morkStream::spill_buf(morkEnv* ev) // spill/flush from buffer to file
{
  nsIMdbFile* file = mStream_ContentFile;
  if ( this->IsOpenOrClosingNode() && this->FileActive() && file )
  {
    mork_u1* buf = mStream_Buf;
    if ( mStream_Dirty )
    {
      mork_u1* at = mStream_At;
      if ( at >= buf && at <= mStream_WriteEnd ) // order?
      {
        mork_num count = (mork_num) (at - buf); // bytes buffered
        if ( count ) // anything to write to the string?
        {
          if ( count > mStream_BufSize ) // no more than max?
          {
            count = mStream_BufSize;
            mStream_WriteEnd = buf + mStream_BufSize;
            this->NewBadCursorSlotsError(ev);
          }
          if ( ev->Good() )
          {
            // file->Seek(ev, mStream_BufPos);
            // if ( ev->Good() )
            // {
            // }
            nsIMdbEnv* menv = ev->AsMdbEnv();
            mork_num actual = 0;
            
            file->Put(menv, buf, count, mStream_BufPos, &actual);
            if ( ev->Good() )
            {
              mStream_BufPos += actual; // past bytes written
              mStream_At = buf; // reset buffer cursor
              mStream_Dirty = morkBool_kFalse;
            }
          }
        }
      }
      else this->NewBadCursorOrderError(ev);
    }
    else
    {
#ifdef MORK_DEBUG
      ev->NewWarning("stream:spill:not:dirty");
#endif /*MORK_DEBUG*/
    }
  }
  else this->NewFileDownError(ev);

}


//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789