/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-  */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#ifndef _MORKSTREAM_
#define _MORKSTREAM_ 1

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

#ifndef _MORKNODE_
#  include "morkNode.h"
#endif

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

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

/*=============================================================================
 * morkStream: buffered file i/o
 */

/*| morkStream exists to define an morkFile subclass that provides buffered
**| i/o for an underlying content file.  Naturally this arrangement only makes
**| sense when the underlying content file is itself not efficiently buffered
**| (especially for character by character i/o).
**|
**|| morkStream is intended for either reading use or writing use, but not
**| both simultaneously or interleaved.  Pick one when the stream is created
**| and don't change your mind.  This restriction is intended to avoid obscure
**| and complex bugs that might arise from interleaved reads and writes -- so
**| just don't do it.  A stream is either a sink or a source, but not both.
**|
**|| (When the underlying content file is intended to support both reading and
**| writing, a developer might use two instances of morkStream where one is for
**| reading and the other is for writing.  In this case, a developer must take
**| care to keep the two streams in sync because each will maintain a separate
**| buffer representing a cache consistency problem for the other.  A simple
**| approach is to invalidate the buffer of one when one uses the other, with
**| the assumption that closely mixed reading and writing is not expected, so
**| that little cost is associated with changing read/write streaming modes.)
**|
**|| Exactly one of mStream_ReadEnd or mStream_WriteEnd must be a null pointer,
**| and this will cause the right thing to occur when inlines use them, because
**| mStream_At < mStream_WriteEnd (for example) will always be false and the
**| else branch of the statement calls a function that raises an appropriate
**| error to complain about either reading a sink or writing a source.
**|
**|| morkStream is a direct clone of ab_Stream from Communicator 4.5's
**| address book code, which in turn was based on the stream class in the
**| public domain Mithril programming language.
|*/

#define morkStream_kPrintBufSize /*i*/ 512 /* buffer size used by printf() */

#define morkStream_kMinBufSize /*i*/ 512         /* buffer no fewer bytes */
#define morkStream_kMaxBufSize /*i*/ (32 * 1024) /* buffer no more bytes */

#define morkDerived_kStream /*i*/ 0x7A74 /* ascii 'zt' */

class morkStream /*d*/ : public morkFile { /* from Mithril's AgStream class */

  // ````` ````` ````` `````   ````` ````` ````` `````
 protected:                   // protected morkStream members
  mork_u1* mStream_At;        // pointer into mStream_Buf
  mork_u1* mStream_ReadEnd;   // null or one byte past last readable byte
  mork_u1* mStream_WriteEnd;  // null or mStream_Buf + mStream_BufSize

  nsIMdbFile* mStream_ContentFile;  // where content is read and written

  mork_u1* mStream_Buf;       // dynamically allocated memory to buffer io
  mork_size mStream_BufSize;  // requested buf size (fixed by min and max)
  mork_pos mStream_BufPos;    // logical position of byte at mStream_Buf
  mork_bool mStream_Dirty;    // does the buffer need to be flushed?
  mork_bool mStream_HitEof;   // has eof been reached? (only frozen streams)

  // { ===== begin morkNode interface =====
 public:  // morkNode virtual methods
  virtual void CloseMorkNode(
      morkEnv* ev) override;  // CloseStream() only if open
  virtual ~morkStream();      // assert that CloseStream() executed earlier

 public:  // morkStream construction & destruction
  morkStream(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
             nsIMdbFile* ioContentFile, mork_size inBufSize,
             mork_bool inFrozen);
  void CloseStream(morkEnv* ev);  // called by CloseMorkNode();

 private:  // copying is not allowed
  morkStream(const morkStream& other);
  morkStream& operator=(const morkStream& other);

 public:  // dynamic type identification
  mork_bool IsStream() const {
    return IsNode() && mNode_Derived == morkDerived_kStream;
  }
  // } ===== end morkNode methods =====

 public:  // typing
  void NonStreamTypeError(morkEnv* ev);

  // ````` ````` ````` `````   ````` ````` ````` `````
 public:  // virtual morkFile methods
  NS_IMETHOD Steal(nsIMdbEnv* ev, nsIMdbFile* ioThief) override;
  // 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()).

  NS_IMETHOD BecomeTrunk(nsIMdbEnv* ev) override;
  // 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.

  NS_IMETHOD AcquireBud(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
                        nsIMdbFile** acqBud) override;
  // 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.

  virtual mork_pos Length(morkEnv* ev) const override;  // eof
  NS_IMETHOD Tell(nsIMdbEnv* ev, mork_pos* aOutPos) const override;
  NS_IMETHOD Read(nsIMdbEnv* ev, void* outBuf, mork_size inSize,
                  mork_size* aOutCount) override;
  NS_IMETHOD Seek(nsIMdbEnv* ev, mork_pos inPos, mork_pos* aOutPos) override;
  NS_IMETHOD Write(nsIMdbEnv* ev, const void* inBuf, mork_size inSize,
                   mork_size* aOutCount) override;
  NS_IMETHOD Flush(nsIMdbEnv* ev) override;

  // ````` ````` ````` `````   ````` ````` ````` `````
 protected:  // protected non-poly morkStream methods (for char io)
  int fill_getc(morkEnv* ev);
  void spill_putc(morkEnv* ev, int c);
  void spill_buf(morkEnv* ev);  // spill/flush from buffer to file

  // ````` ````` ````` `````   ````` ````` ````` `````
 public:  // public non-poly morkStream methods
  void NewBadCursorSlotsError(morkEnv* ev) const;
  void NewBadCursorOrderError(morkEnv* ev) const;
  void NewNullStreamBufferError(morkEnv* ev) const;
  void NewCantReadSinkError(morkEnv* ev) const;
  void NewCantWriteSourceError(morkEnv* ev) const;
  void NewPosBeyondEofError(morkEnv* ev) const;

  nsIMdbFile* GetStreamContentFile() const { return mStream_ContentFile; }
  mork_size GetStreamBufferSize() const { return mStream_BufSize; }

  mork_size PutIndent(morkEnv* ev, mork_count inDepth);
  // PutIndent() puts a linebreak, and then
  // "indents" by inDepth, and returns the line length after indentation.

  mork_size 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 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 PutString(morkEnv* ev, const char* inString);
  // PutString() returns the length of the string written.

  mork_size PutStringThenNewline(morkEnv* ev, const char* inString);
  // PutStringThenNewline() returns total number of bytes written.

  mork_size PutByteThenNewline(morkEnv* ev, int inByte);
  // PutByteThenNewline() returns total number of bytes written.

  // ````` ````` stdio type methods ````` `````
  void Ungetc(int c) /*i*/
  {
    if (mStream_At > mStream_Buf && c > 0) *--mStream_At = (mork_u1)c;
  }

  // Note Getc() returns EOF consistently after any fill_getc() error occurs.
  int Getc(morkEnv* ev) /*i*/
  {
    return (mStream_At < mStream_ReadEnd) ? *mStream_At++ : fill_getc(ev);
  }

  void Putc(morkEnv* ev, int c) /*i*/
  {
    mStream_Dirty = morkBool_kTrue;
    if (mStream_At < mStream_WriteEnd)
      *mStream_At++ = (mork_u1)c;
    else
      spill_putc(ev, c);
  }

  mork_size PutLineBreak(morkEnv* ev);

 public:  // typesafe refcounting inlines calling inherited morkNode methods
  static void SlotWeakStream(morkStream* me, morkEnv* ev, morkStream** ioSlot) {
    morkNode::SlotWeakNode((morkNode*)me, ev, (morkNode**)ioSlot);
  }

  static void SlotStrongStream(morkStream* me, morkEnv* ev,
                               morkStream** ioSlot) {
    morkNode::SlotStrongNode((morkNode*)me, ev, (morkNode**)ioSlot);
  }
};

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

#endif /* _MORKSTREAM_ */
