// Written in the D programming language

/**
 * $(RED Warning: This module is considered out-dated and not up to Phobos'
 *       current standards. It will remain until we have a suitable replacement,
 *       but be aware that it will not remain long term.)
 *
 * Source:    $(PHOBOSSRC std/_stream.d)
 * Macros:
 *      WIKI = Phobos/StdStream
 */

/*
 * Copyright (c) 2001-2005
 * Pavel "EvilOne" Minayev
 *  with buffering and endian support added by Ben Hinkle
 *  with buffered readLine performance improvements by Dave Fladebo
 *  with opApply inspired by (and mostly copied from) Regan Heath
 *  with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Author makes no representations about
 * the suitability of this software for any purpose. It is provided
 * "as is" without express or implied warranty.
 */

/* NOTE: This file has been patched from the original DMD distribution to
   work with the GDC compiler.
 */

module std.stream;


/* Class structure:
 *  InputStream       interface for reading
 *  OutputStream      interface for writing
 *  Stream            abstract base of stream implementations
 *    File            an OS file stream
 *    FilterStream    a base-class for wrappers around another stream
 *      BufferedStream  a buffered stream wrapping another stream
 *        BufferedFile  a buffered File
 *      EndianStream    a wrapper stream for swapping byte order and BOMs
 *      SliceStream     a portion of another stream
 *    MemoryStream    a stream entirely stored in main memory
 *    TArrayStream    a stream wrapping an array-like buffer
 */

/// A base class for stream exceptions.
class StreamException: Exception {
  /// Construct a StreamException with given error message.
  this(string msg) { super(msg); }
}

/// Thrown when unable to read data from Stream.
class ReadException: StreamException {
  /// Construct a ReadException with given error message.
  this(string msg) { super(msg); }
}

/// Thrown when unable to write data to Stream.
class WriteException: StreamException {
  /// Construct a WriteException with given error message.
  this(string msg) { super(msg); }
}

/// Thrown when unable to move Stream pointer.
class SeekException: StreamException {
  /// Construct a SeekException with given error message.
  this(string msg) { super(msg); }
}

// seek whence...
enum SeekPos {
  Set,
  Current,
  End
}

private {
  import std.conv;
  import std.algorithm;
  import std.ascii;
  import std.format;
  import std.system;    // for Endian enumeration
  import std.utf;
  import core.bitop; // for bswap
  import core.vararg;
  import std.file;
}

/// InputStream is the interface for readable streams.

interface InputStream {

  /***
   * Read exactly size bytes into the buffer.
   *
   * Throws a ReadException if it is not correct.
   */
  void readExact(void* buffer, size_t size);

  /***
   * Read a block of data big enough to fill the given array buffer.
   *
   * Returns: the actual number of bytes read. Unfilled bytes are not modified.
   */
  size_t read(ubyte[] buffer);

  /***
   * Read a basic type or counted string.
   *
   * Throw a ReadException if it could not be read.
   * Outside of byte, ubyte, and char, the format is
   * implementation-specific and should not be used except as opposite actions
   * to write.
   */
  void read(out byte x);
  void read(out ubyte x);       /// ditto
  void read(out short x);       /// ditto
  void read(out ushort x);      /// ditto
  void read(out int x);         /// ditto
  void read(out uint x);        /// ditto
  void read(out long x);        /// ditto
  void read(out ulong x);       /// ditto
  void read(out float x);       /// ditto
  void read(out double x);      /// ditto
  void read(out real x);        /// ditto
  void read(out ifloat x);      /// ditto
  void read(out idouble x);     /// ditto
  void read(out ireal x);       /// ditto
  void read(out cfloat x);      /// ditto
  void read(out cdouble x);     /// ditto
  void read(out creal x);       /// ditto
  void read(out char x);        /// ditto
  void read(out wchar x);       /// ditto
  void read(out dchar x);       /// ditto

  // reads a string, written earlier by write()
  void read(out char[] s);      /// ditto

  // reads a Unicode string, written earlier by write()
  void read(out wchar[] s);     /// ditto

  /***
   * Read a line that is terminated with some combination of carriage return and
   * line feed or end-of-file.
   *
   * The terminators are not included. The wchar version
   * is identical. The optional buffer parameter is filled (reallocating
   * it if necessary) and a slice of the result is returned.
   */
  char[] readLine();
  char[] readLine(char[] result);       /// ditto
  wchar[] readLineW();                  /// ditto
  wchar[] readLineW(wchar[] result);    /// ditto

  /***
   * Overload foreach statements to read the stream line by line and call the
   * supplied delegate with each line or with each line with line number.
   *
   * The string passed in line may be reused between calls to the delegate.
   * Line numbering starts at 1.
   * Breaking out of the foreach will leave the stream
   * position at the beginning of the next line to be read.
   * For example, to echo a file line-by-line with line numbers run:
   * ------------------------------------
   * Stream file = new BufferedFile("sample.txt");
   * foreach(ulong n, char[] line; file)
   * {
   *     writefln("line %d: %s", n, line);
   * }
   * file.close();
   * ------------------------------------
   */

  // iterate through the stream line-by-line
  int opApply(scope int delegate(ref char[] line) dg);
  int opApply(scope int delegate(ref ulong n, ref char[] line) dg);  /// ditto
  int opApply(scope int delegate(ref wchar[] line) dg);            /// ditto
  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto

  /// Read a string of the given length,
  /// throwing ReadException if there was a problem.
  char[] readString(size_t length);

  /***
   * Read a string of the given length, throwing ReadException if there was a
   * problem.
   *
   * The file format is implementation-specific and should not be used
   * except as opposite actions to <b>write</b>.
   */

  wchar[] readStringW(size_t length);


  /***
   * Read and return the next character in the stream.
   *
   * This is the only method that will handle ungetc properly.
   * getcw's format is implementation-specific.
   * If EOF is reached then getc returns char.init and getcw returns wchar.init.
   */

  char getc();
  wchar getcw(); /// ditto

  /***
   * Push a character back onto the stream.
   *
   * They will be returned in first-in last-out order from getc/getcw.
   * Only has effect on further calls to getc() and getcw().
   */
  char ungetc(char c);
  wchar ungetcw(wchar c); /// ditto

  /***
   * Scan a string from the input using a similar form to C's scanf
   * and <a href="std_format.html">std.format</a>.
   *
   * An argument of type string is interpreted as a format string.
   * All other arguments must be pointer types.
   * If a format string is not present a default will be supplied computed from
   * the base type of the pointer type. An argument of type string* is filled
   * (possibly with appending characters) and a slice of the result is assigned
   * back into the argument. For example the following readf statements
   * are equivalent:
   * --------------------------
   * int x;
   * double y;
   * string s;
   * file.readf(&x, " hello ", &y, &s);
   * file.readf("%d hello %f %s", &x, &y, &s);
   * file.readf("%d hello %f", &x, &y, "%s", &s);
   * --------------------------
   */
  int vreadf(TypeInfo[] arguments, va_list args);
  int readf(...); /// ditto

  /// Retrieve the number of bytes available for immediate reading.
  @property size_t available();

  /***
   * Return whether the current file position is the same as the end of the
   * file.
   *
   * This does not require actually reading past the end, as with stdio. For
   * non-seekable streams this might only return true after attempting to read
   * past the end.
   */

  @property bool eof();

  @property bool isOpen();        /// Return true if the stream is currently open.
}

/// Interface for writable streams.
interface OutputStream {

  /***
   * Write exactly size bytes from buffer, or throw a WriteException if that
   * could not be done.
   */
  void writeExact(const void* buffer, size_t size);

  /***
   * Write as much of the buffer as possible,
   * returning the number of bytes written.
   */
  size_t write(const(ubyte)[] buffer);

  /***
   * Write a basic type.
   *
   * Outside of byte, ubyte, and char, the format is implementation-specific
   * and should only be used in conjunction with read.
   * Throw WriteException on error.
   */
  void write(byte x);
  void write(ubyte x);          /// ditto
  void write(short x);          /// ditto
  void write(ushort x);         /// ditto
  void write(int x);            /// ditto
  void write(uint x);           /// ditto
  void write(long x);           /// ditto
  void write(ulong x);          /// ditto
  void write(float x);          /// ditto
  void write(double x);         /// ditto
  void write(real x);           /// ditto
  void write(ifloat x);         /// ditto
  void write(idouble x);        /// ditto
  void write(ireal x);          /// ditto
  void write(cfloat x);         /// ditto
  void write(cdouble x);        /// ditto
  void write(creal x);          /// ditto
  void write(char x);           /// ditto
  void write(wchar x);          /// ditto
  void write(dchar x);          /// ditto

  /***
   * Writes a string, together with its length.
   *
   * The format is implementation-specific
   * and should only be used in conjunction with read.
   * Throw WriteException on error.
   */
    void write(const(char)[] s);
    void write(const(wchar)[] s); /// ditto

  /***
   * Write a line of text,
   * appending the line with an operating-system-specific line ending.
   *
   * Throws WriteException on error.
   */
  void writeLine(const(char)[] s);

  /***
   * Write a line of text,
   * appending the line with an operating-system-specific line ending.
   *
   * The format is implementation-specific.
   * Throws WriteException on error.
   */
    void writeLineW(const(wchar)[] s);

  /***
   * Write a string of text.
   *
   * Throws WriteException if it could not be fully written.
   */
    void writeString(const(char)[] s);

  /***
   * Write a string of text.
   *
   * The format is implementation-specific.
   * Throws WriteException if it could not be fully written.
   */
  void writeStringW(const(wchar)[] s);

  /***
   * Print a formatted string into the stream using printf-style syntax,
   * returning the number of bytes written.
   */
  size_t vprintf(const(char)[] format, va_list args);
  size_t printf(const(char)[] format, ...);    /// ditto

  /***
   * Print a formatted string into the stream using writef-style syntax.
   * References: <a href="std_format.html">std.format</a>.
   * Returns: self to chain with other stream commands like flush.
   */
  OutputStream writef(...);
  OutputStream writefln(...); /// ditto
  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false);  /// ditto

  void flush(); /// Flush pending output if appropriate.
  void close(); /// Close the stream, flushing output if appropriate.
  @property bool isOpen(); /// Return true if the stream is currently open.
}


/***
 * Stream is the base abstract class from which the other stream classes derive.
 *
 * Stream's byte order is the format native to the computer.
 *
 * Reading:
 * These methods require that the readable flag be set.
 * Problems with reading result in a ReadException being thrown.
 * Stream implements the InputStream interface in addition to the
 * readBlock method.
 *
 * Writing:
 * These methods require that the writeable flag be set. Problems with writing
 * result in a WriteException being thrown. Stream implements the OutputStream
 * interface in addition to the following methods:
 * writeBlock
 * copyFrom
 * copyFrom
 *
 * Seeking:
 * These methods require that the seekable flag be set.
 * Problems with seeking result in a SeekException being thrown.
 * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash
 */

// not really abstract, but its instances will do nothing useful
class Stream : InputStream, OutputStream {
  private import std.string, std.digest.crc, std.c.stdlib, std.c.stdio;

  // stream abilities
  bool readable = false;        /// Indicates whether this stream can be read from.
  bool writeable = false;       /// Indicates whether this stream can be written to.
  bool seekable = false;        /// Indicates whether this stream can be seeked within.
  protected bool isopen = true; /// Indicates whether this stream is open.

  protected bool readEOF = false; /** Indicates whether this stream is at eof
                                   * after the last read attempt.
                                   */

  protected bool prevCr = false; /** For a non-seekable stream indicates that
                                  * the last readLine or readLineW ended on a
                                  * '\r' character.
                                  */

  this() {}

  /***
   * Read up to size bytes into the buffer and return the number of bytes
   * actually read. A return value of 0 indicates end-of-file.
   */
  abstract size_t readBlock(void* buffer, size_t size);

  // reads block of data of specified size,
  // throws ReadException on error
  void readExact(void* buffer, size_t size) {
    for(;;) {
      if (!size) return;
      size_t readsize = readBlock(buffer, size); // return 0 on eof
      if (readsize == 0) break;
      buffer += readsize;
      size -= readsize;
    }
    if (size != 0)
      throw new ReadException("not enough data in stream");
  }

  // reads block of data big enough to fill the given
  // array, returns actual number of bytes read
  size_t read(ubyte[] buffer) {
    return readBlock(buffer.ptr, buffer.length);
  }

  // read a single value of desired type,
  // throw ReadException on error
  void read(out byte x) { readExact(&x, x.sizeof); }
  void read(out ubyte x) { readExact(&x, x.sizeof); }
  void read(out short x) { readExact(&x, x.sizeof); }
  void read(out ushort x) { readExact(&x, x.sizeof); }
  void read(out int x) { readExact(&x, x.sizeof); }
  void read(out uint x) { readExact(&x, x.sizeof); }
  void read(out long x) { readExact(&x, x.sizeof); }
  void read(out ulong x) { readExact(&x, x.sizeof); }
  void read(out float x) { readExact(&x, x.sizeof); }
  void read(out double x) { readExact(&x, x.sizeof); }
  void read(out real x) { readExact(&x, x.sizeof); }
  void read(out ifloat x) { readExact(&x, x.sizeof); }
  void read(out idouble x) { readExact(&x, x.sizeof); }
  void read(out ireal x) { readExact(&x, x.sizeof); }
  void read(out cfloat x) { readExact(&x, x.sizeof); }
  void read(out cdouble x) { readExact(&x, x.sizeof); }
  void read(out creal x) { readExact(&x, x.sizeof); }
  void read(out char x) { readExact(&x, x.sizeof); }
  void read(out wchar x) { readExact(&x, x.sizeof); }
  void read(out dchar x) { readExact(&x, x.sizeof); }

  // reads a string, written earlier by write()
  void read(out char[] s) {
    size_t len;
    read(len);
    s = readString(len);
  }

  // reads a Unicode string, written earlier by write()
  void read(out wchar[] s) {
    size_t len;
    read(len);
    s = readStringW(len);
  }

  // reads a line, terminated by either CR, LF, CR/LF, or EOF
  char[] readLine() {
    return readLine(null);
  }

  // reads a line, terminated by either CR, LF, CR/LF, or EOF
  // reusing the memory in buffer if result will fit and otherwise
  // allocates a new string
  char[] readLine(char[] result) {
    size_t strlen = 0;
    char ch = getc();
    while (readable) {
      switch (ch) {
      case '\r':
        if (seekable) {
          ch = getc();
          if (ch != '\n')
            ungetc(ch);
        } else {
          prevCr = true;
        }
        goto case;
      case '\n':
      case char.init:
        result.length = strlen;
        return result;

      default:
        if (strlen < result.length) {
          result[strlen] = ch;
        } else {
          result ~= ch;
        }
        strlen++;
      }
      ch = getc();
    }
    result.length = strlen;
    return result;
  }

  // reads a Unicode line, terminated by either CR, LF, CR/LF,
  // or EOF; pretty much the same as the above, working with
  // wchars rather than chars
  wchar[] readLineW() {
    return readLineW(null);
  }

  // reads a Unicode line, terminated by either CR, LF, CR/LF,
  // or EOF;
  // fills supplied buffer if line fits and otherwise allocates a new string.
  wchar[] readLineW(wchar[] result) {
    size_t strlen = 0;
    wchar c = getcw();
    while (readable) {
      switch (c) {
      case '\r':
        if (seekable) {
          c = getcw();
          if (c != '\n')
            ungetcw(c);
        } else {
          prevCr = true;
        }
        goto case;
      case '\n':
      case wchar.init:
        result.length = strlen;
        return result;

      default:
        if (strlen < result.length) {
          result[strlen] = c;
        } else {
          result ~= c;
        }
        strlen++;
      }
      c = getcw();
    }
    result.length = strlen;
    return result;
  }

  // iterate through the stream line-by-line - due to Regan Heath
  int opApply(scope int delegate(ref char[] line) dg) {
    int res = 0;
    char[128] buf;
    while (!eof) {
      char[] line = readLine(buf);
      res = dg(line);
      if (res) break;
    }
    return res;
  }

  // iterate through the stream line-by-line with line count and string
  int opApply(scope int delegate(ref ulong n, ref char[] line) dg) {
    int res = 0;
    ulong n = 1;
    char[128] buf;
    while (!eof) {
      auto line = readLine(buf);
      res = dg(n,line);
      if (res) break;
      n++;
    }
    return res;
  }

  // iterate through the stream line-by-line with wchar[]
  int opApply(scope int delegate(ref wchar[] line) dg) {
    int res = 0;
    wchar[128] buf;
    while (!eof) {
      auto line = readLineW(buf);
      res = dg(line);
      if (res) break;
    }
    return res;
  }

  // iterate through the stream line-by-line with line count and wchar[]
  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) {
    int res = 0;
    ulong n = 1;
    wchar[128] buf;
    while (!eof) {
      auto line = readLineW(buf);
      res = dg(n,line);
      if (res) break;
      n++;
    }
    return res;
  }

  // reads a string of given length, throws
  // ReadException on error
  char[] readString(size_t length) {
    char[] result = new char[length];
    readExact(result.ptr, length);
    return result;
  }

  // reads a Unicode string of given length, throws
  // ReadException on error
  wchar[] readStringW(size_t length) {
    auto result = new wchar[length];
    readExact(result.ptr, result.length * wchar.sizeof);
    return result;
  }

  // unget buffer
  private wchar[] unget;
  final bool ungetAvailable() { return unget.length > 1; }

  // reads and returns next character from the stream,
  // handles characters pushed back by ungetc()
  // returns char.init on eof.
  char getc() {
    char c;
    if (prevCr) {
      prevCr = false;
      c = getc();
      if (c != '\n')
        return c;
    }
    if (unget.length > 1) {
      c = cast(char)unget[unget.length - 1];
      unget.length = unget.length - 1;
    } else {
      readBlock(&c,1);
    }
    return c;
  }

  // reads and returns next Unicode character from the
  // stream, handles characters pushed back by ungetc()
  // returns wchar.init on eof.
  wchar getcw() {
    wchar c;
    if (prevCr) {
      prevCr = false;
      c = getcw();
      if (c != '\n')
        return c;
    }
    if (unget.length > 1) {
      c = unget[unget.length - 1];
      unget.length = unget.length - 1;
    } else {
      void* buf = &c;
      size_t n = readBlock(buf,2);
      if (n == 1 && readBlock(buf+1,1) == 0)
          throw new ReadException("not enough data in stream");
    }
    return c;
  }

  // pushes back character c into the stream; only has
  // effect on further calls to getc() and getcw()
  char ungetc(char c) {
    if (c == c.init) return c;
    // first byte is a dummy so that we never set length to 0
    if (unget.length == 0)
      unget.length = 1;
    unget ~= c;
    return c;
  }

  // pushes back Unicode character c into the stream; only
  // has effect on further calls to getc() and getcw()
  wchar ungetcw(wchar c) {
    if (c == c.init) return c;
    // first byte is a dummy so that we never set length to 0
    if (unget.length == 0)
      unget.length = 1;
    unget ~= c;
    return c;
  }

  int vreadf(TypeInfo[] arguments, va_list args) {
    string fmt;
    int j = 0;
    int count = 0, i = 0;
    char c = getc();
    while ((j < arguments.length || i < fmt.length) && !eof) {
      if (fmt.length == 0 || i == fmt.length) {
        i = 0;
        if (arguments[j] is typeid(char[])) {
          fmt = va_arg!(string)(args);
          j++;
          continue;
        } else if (arguments[j] is typeid(int*) ||
                   arguments[j] is typeid(byte*) ||
                   arguments[j] is typeid(short*) ||
                   arguments[j] is typeid(long*)) {
          fmt = "%d";
        } else if (arguments[j] is typeid(uint*) ||
                   arguments[j] is typeid(ubyte*) ||
                   arguments[j] is typeid(ushort*) ||
                   arguments[j] is typeid(ulong*)) {
          fmt = "%d";
        } else if (arguments[j] is typeid(float*) ||
                   arguments[j] is typeid(double*) ||
                   arguments[j] is typeid(real*)) {
          fmt = "%f";
        } else if (arguments[j] is typeid(char[]*) ||
                   arguments[j] is typeid(wchar[]*) ||
                   arguments[j] is typeid(dchar[]*)) {
          fmt = "%s";
        } else if (arguments[j] is typeid(char*)) {
          fmt = "%c";
        }
      }
      if (fmt[i] == '%') {      // a field
        i++;
        bool suppress = false;
        if (fmt[i] == '*') {    // suppress assignment
          suppress = true;
          i++;
        }
        // read field width
        int width = 0;
        while (isDigit(fmt[i])) {
          width = width * 10 + (fmt[i] - '0');
          i++;
        }
        if (width == 0)
          width = -1;
        // skip any modifier if present
        if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L')
          i++;
        // check the typechar and act accordingly
        switch (fmt[i]) {
        case 'd':       // decimal/hexadecimal/octal integer
        case 'D':
        case 'u':
        case 'U':
        case 'o':
        case 'O':
        case 'x':
        case 'X':
        case 'i':
        case 'I':
          {
            while (isWhite(c)) {
              c = getc();
              count++;
            }
            bool neg = false;
            if (c == '-') {
              neg = true;
              c = getc();
              count++;
            } else if (c == '+') {
              c = getc();
              count++;
            }
            char ifmt = cast(char)(fmt[i] | 0x20);
            if (ifmt == 'i')    { // undetermined base
              if (c == '0')     { // octal or hex
                c = getc();
                count++;
                if (c == 'x' || c == 'X')       { // hex
                  ifmt = 'x';
                  c = getc();
                  count++;
                } else {        // octal
                  ifmt = 'o';
                }
              }
              else      // decimal
                ifmt = 'd';
            }
            long n = 0;
            switch (ifmt)
            {
                case 'd':       // decimal
                case 'u': {
                  while (isDigit(c) && width) {
                    n = n * 10 + (c - '0');
                    width--;
                    c = getc();
                    count++;
                  }
                } break;

                case 'o': {     // octal
                  while (isOctalDigit(c) && width) {
                    n = n * 8 + (c - '0');
                    width--;
                    c = getc();
                    count++;
                  }
                } break;

                case 'x': {     // hexadecimal
                  while (isHexDigit(c) && width) {
                    n *= 0x10;
                    if (isDigit(c))
                      n += c - '0';
                    else
                      n += 0xA + (c | 0x20) - 'a';
                    width--;
                    c = getc();
                    count++;
                  }
                } break;

                default:
                    assert(0);
            }
            if (neg)
              n = -n;
            if (arguments[j] is typeid(int*)) {
              int* p = va_arg!(int*)(args);
              *p = cast(int)n;
            } else if (arguments[j] is typeid(short*)) {
              short* p = va_arg!(short*)(args);
              *p = cast(short)n;
            } else if (arguments[j] is typeid(byte*)) {
              byte* p = va_arg!(byte*)(args);
              *p = cast(byte)n;
            } else if (arguments[j] is typeid(long*)) {
              long* p = va_arg!(long*)(args);
              *p = n;
            } else if (arguments[j] is typeid(uint*)) {
              uint* p = va_arg!(uint*)(args);
              *p = cast(uint)n;
            } else if (arguments[j] is typeid(ushort*)) {
              ushort* p = va_arg!(ushort*)(args);
              *p = cast(ushort)n;
            } else if (arguments[j] is typeid(ubyte*)) {
              ubyte* p = va_arg!(ubyte*)(args);
              *p = cast(ubyte)n;
            } else if (arguments[j] is typeid(ulong*)) {
              ulong* p = va_arg!(ulong*)(args);
              *p = cast(ulong)n;
            }
            j++;
            i++;
          } break;

        case 'f':       // float
        case 'F':
        case 'e':
        case 'E':
        case 'g':
        case 'G':
          {
            while (isWhite(c)) {
              c = getc();
              count++;
            }
            bool neg = false;
            if (c == '-') {
              neg = true;
              c = getc();
              count++;
            } else if (c == '+') {
              c = getc();
              count++;
            }
            real n = 0;
            while (isDigit(c) && width) {
              n = n * 10 + (c - '0');
              width--;
              c = getc();
              count++;
            }
            if (width && c == '.') {
              width--;
              c = getc();
              count++;
              double frac = 1;
              while (isDigit(c) && width) {
                n = n * 10 + (c - '0');
                frac *= 10;
                width--;
                c = getc();
                count++;
              }
              n /= frac;
            }
            if (width && (c == 'e' || c == 'E')) {
              width--;
              c = getc();
              count++;
              if (width) {
                bool expneg = false;
                if (c == '-') {
                  expneg = true;
                  width--;
                  c = getc();
                  count++;
                } else if (c == '+') {
                  width--;
                  c = getc();
                  count++;
                }
                real exp = 0;
                while (isDigit(c) && width) {
                  exp = exp * 10 + (c - '0');
                  width--;
                  c = getc();
                  count++;
                }
                if (expneg) {
                  while (exp--)
                    n /= 10;
                } else {
                  while (exp--)
                    n *= 10;
                }
              }
            }
            if (neg)
              n = -n;
            if (arguments[j] is typeid(float*)) {
              float* p = va_arg!(float*)(args);
              *p = n;
            } else if (arguments[j] is typeid(double*)) {
              double* p = va_arg!(double*)(args);
              *p = n;
            } else if (arguments[j] is typeid(real*)) {
              real* p = va_arg!(real*)(args);
              *p = n;
            }
            j++;
            i++;
          } break;

        case 's': {     // string
          while (isWhite(c)) {
            c = getc();
            count++;
          }
          char[] s;
          char[]* p;
          size_t strlen;
          if (arguments[j] is typeid(char[]*)) {
            p = va_arg!(char[]*)(args);
            s = *p;
          }
          while (!isWhite(c) && c != char.init) {
            if (strlen < s.length) {
              s[strlen] = c;
            } else {
              s ~= c;
            }
            strlen++;
            c = getc();
            count++;
          }
          s = s[0 .. strlen];
          if (arguments[j] is typeid(char[]*)) {
            *p = s;
          } else if (arguments[j] is typeid(char*)) {
            s ~= 0;
            auto q = va_arg!(char*)(args);
            q[0 .. s.length] = s[];
          } else if (arguments[j] is typeid(wchar[]*)) {
            auto q = va_arg!(const(wchar)[]*)(args);
            *q = toUTF16(s);
          } else if (arguments[j] is typeid(dchar[]*)) {
            auto q = va_arg!(const(dchar)[]*)(args);
            *q = toUTF32(s);
          }
          j++;
          i++;
        } break;

        case 'c': {     // character(s)
          char* s = va_arg!(char*)(args);
          if (width < 0)
            width = 1;
          else
            while (isWhite(c)) {
            c = getc();
            count++;
          }
          while (width-- && !eof) {
            *(s++) = c;
            c = getc();
            count++;
          }
          j++;
          i++;
        } break;

        case 'n': {     // number of chars read so far
          int* p = va_arg!(int*)(args);
          *p = count;
          j++;
          i++;
        } break;

        default:        // read character as is
          goto nws;
        }
      } else if (isWhite(fmt[i])) {     // skip whitespace
        while (isWhite(c))
          c = getc();
        i++;
      } else {  // read character as is
      nws:
        if (fmt[i] != c)
          break;
        c = getc();
        i++;
      }
    }
    ungetc(c);
    return count;
  }

  int readf(...) {
    return vreadf(_arguments, _argptr);
  }

  // returns estimated number of bytes available for immediate reading
  @property size_t available() { return 0; }

  /***
   * Write up to size bytes from buffer in the stream, returning the actual
   * number of bytes that were written.
   */
  abstract size_t writeBlock(const void* buffer, size_t size);

  // writes block of data of specified size,
  // throws WriteException on error
  void writeExact(const void* buffer, size_t size) {
    const(void)* p = buffer;
    for(;;) {
      if (!size) return;
      size_t writesize = writeBlock(p, size);
      if (writesize == 0) break;
      p += writesize;
      size -= writesize;
    }
    if (size != 0)
      throw new WriteException("unable to write to stream");
  }

  // writes the given array of bytes, returns
  // actual number of bytes written
  size_t write(const(ubyte)[] buffer) {
    return writeBlock(buffer.ptr, buffer.length);
  }

  // write a single value of desired type,
  // throw WriteException on error
  void write(byte x) { writeExact(&x, x.sizeof); }
  void write(ubyte x) { writeExact(&x, x.sizeof); }
  void write(short x) { writeExact(&x, x.sizeof); }
  void write(ushort x) { writeExact(&x, x.sizeof); }
  void write(int x) { writeExact(&x, x.sizeof); }
  void write(uint x) { writeExact(&x, x.sizeof); }
  void write(long x) { writeExact(&x, x.sizeof); }
  void write(ulong x) { writeExact(&x, x.sizeof); }
  void write(float x) { writeExact(&x, x.sizeof); }
  void write(double x) { writeExact(&x, x.sizeof); }
  void write(real x) { writeExact(&x, x.sizeof); }
  void write(ifloat x) { writeExact(&x, x.sizeof); }
  void write(idouble x) { writeExact(&x, x.sizeof); }
  void write(ireal x) { writeExact(&x, x.sizeof); }
  void write(cfloat x) { writeExact(&x, x.sizeof); }
  void write(cdouble x) { writeExact(&x, x.sizeof); }
  void write(creal x) { writeExact(&x, x.sizeof); }
  void write(char x) { writeExact(&x, x.sizeof); }
  void write(wchar x) { writeExact(&x, x.sizeof); }
  void write(dchar x) { writeExact(&x, x.sizeof); }

  // writes a string, together with its length
  void write(const(char)[] s) {
    write(s.length);
    writeString(s);
  }

  // writes a Unicode string, together with its length
  void write(const(wchar)[] s) {
    write(s.length);
    writeStringW(s);
  }

  // writes a line, throws WriteException on error
  void writeLine(const(char)[] s) {
    writeString(s);
    version (Windows)
      writeString("\r\n");
    else version (Mac)
      writeString("\r");
    else
      writeString("\n");
  }

  // writes a Unicode line, throws WriteException on error
  void writeLineW(const(wchar)[] s) {
    writeStringW(s);
    version (Windows)
      writeStringW("\r\n");
    else version (Mac)
      writeStringW("\r");
    else
      writeStringW("\n");
  }

  // writes a string, throws WriteException on error
  void writeString(const(char)[] s) {
    writeExact(s.ptr, s.length);
  }

  // writes a Unicode string, throws WriteException on error
  void writeStringW(const(wchar)[] s) {
    writeExact(s.ptr, s.length * wchar.sizeof);
  }

  // writes data to stream using vprintf() syntax,
  // returns number of bytes written
  size_t vprintf(const(char)[] format, va_list args) {
    // shamelessly stolen from OutBuffer,
    // by Walter's permission
    char[1024] buffer;
    char* p = buffer.ptr;
    auto f = toStringz(format);
    size_t psize = buffer.length;
    size_t count;
    while (true) {
      version (Windows) {
        count = _vsnprintf(p, psize, f, args);
        if (count != -1)
          break;
        psize *= 2;
        p = cast(char*) alloca(psize);
      } else version (Posix) {
        count = vsnprintf(p, psize, f, args);
        if (count == -1)
          psize *= 2;
        else if (count >= psize)
          psize = count + 1;
        else
          break;
        p = cast(char*) alloca(psize);
      } else
          throw new Exception("unsupported platform");
    }
    writeString(p[0 .. count]);
    return count;
  }

  // writes data to stream using printf() syntax,
  // returns number of bytes written
  version (GNU)
  size_t printf(const(char)[] format, ...) {
      return vprintf(format, _argptr);
  }
  else version (Win64)
  size_t printf(const(char)[] format, ...) {
    return vprintf(format, _argptr);
  }
  else version (X86_64)
  size_t printf(const(char)[] format, ...) {
    va_list ap;
    va_start(ap, __va_argsave);
    auto result = vprintf(format, ap);
    va_end(ap);
    return result;
  }
  else
  size_t printf(const(char)[] format, ...) {
    va_list ap;
    ap = cast(va_list) &format;
    ap += format.sizeof;
    return vprintf(format, ap);
  }

  private void doFormatCallback(dchar c) {
    char[4] buf;
    auto b = std.utf.toUTF8(buf, c);
    writeString(b);
  }

  // writes data to stream using writef() syntax,
  OutputStream writef(...) {
    return writefx(_arguments,_argptr,0);
  }

  // writes data with trailing newline
  OutputStream writefln(...) {
    return writefx(_arguments,_argptr,1);
  }

  // writes data with optional trailing newline
  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) {
    doFormat(&doFormatCallback,arguments,argptr);
    if (newline)
      writeLine("");
    return this;
  }

  /***
   * Copies all data from s into this stream.
   * This may throw ReadException or WriteException on failure.
   * This restores the file position of s so that it is unchanged.
   */
  void copyFrom(Stream s) {
    if (seekable) {
      ulong pos = s.position;
      s.position = 0;
      copyFrom(s, s.size);
      s.position = pos;
    } else {
      ubyte[128] buf;
      while (!s.eof) {
        size_t m = s.readBlock(buf.ptr, buf.length);
        writeExact(buf.ptr, m);
      }
    }
  }

  /***
   * Copy a specified number of bytes from the given stream into this one.
   * This may throw ReadException or WriteException on failure.
   * Unlike the previous form, this doesn't restore the file position of s.
   */
  void copyFrom(Stream s, ulong count) {
    ubyte[128] buf;
    while (count > 0) {
      size_t n = cast(size_t)(count<buf.length ? count : buf.length);
      s.readExact(buf.ptr, n);
      writeExact(buf.ptr, n);
      count -= n;
    }
  }

  /***
   * Change the current position of the stream. whence is either SeekPos.Set, in
   which case the offset is an absolute index from the beginning of the stream,
   SeekPos.Current, in which case the offset is a delta from the current
   position, or SeekPos.End, in which case the offset is a delta from the end of
   the stream (negative or zero offsets only make sense in that case). This
   returns the new file position.
   */
  abstract ulong seek(long offset, SeekPos whence);

  /***
   * Aliases for their normal seek counterparts.
   */
  ulong seekSet(long offset) { return seek (offset, SeekPos.Set); }
  ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto
  ulong seekEnd(long offset) { return seek (offset, SeekPos.End); }     /// ditto

  /***
   * Sets file position. Equivalent to calling seek(pos, SeekPos.Set).
   */
  @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); }

  /***
   * Returns current file position. Equivalent to seek(0, SeekPos.Current).
   */
  @property ulong position() { return seek(0, SeekPos.Current); }

  /***
   * Retrieve the size of the stream in bytes.
   * The stream must be seekable or a SeekException is thrown.
   */
  @property ulong size() {
    assertSeekable();
    ulong pos = position, result = seek(0, SeekPos.End);
    position = pos;
    return result;
  }

  // returns true if end of stream is reached, false otherwise
  @property bool eof() {
    // for unseekable streams we only know the end when we read it
    if (readEOF && !ungetAvailable())
      return true;
    else if (seekable)
      return position == size;
    else
      return false;
  }

  // returns true if the stream is open
  @property bool isOpen() { return isopen; }

  // flush the buffer if writeable
  void flush() {
    if (unget.length > 1)
      unget.length = 1; // keep at least 1 so that data ptr stays
  }

  // close the stream somehow; the default just flushes the buffer
  void close() {
    if (isopen)
      flush();
    readEOF = prevCr = isopen = readable = writeable = seekable = false;
  }

  /***
   * Read the entire stream and return it as a string.
   * If the stream is not seekable the contents from the current position to eof
   * is read and returned.
   */
  override string toString() {
    if (!readable)
      return super.toString();
    try
    {
        size_t pos;
        size_t rdlen;
        size_t blockSize;
        char[] result;
        if (seekable) {
          ulong orig_pos = position;
          scope(exit) position = orig_pos;
          position = 0;
          blockSize = cast(size_t)size;
          result = new char[blockSize];
          while (blockSize > 0) {
            rdlen = readBlock(&result[pos], blockSize);
            pos += rdlen;
            blockSize -= rdlen;
          }
        } else {
          blockSize = 4096;
          result = new char[blockSize];
          while ((rdlen = readBlock(&result[pos], blockSize)) > 0) {
            pos += rdlen;
            blockSize += rdlen;
            result.length = result.length + blockSize;
          }
        }
        return cast(string) result[0 .. pos];
    }
    catch (Throwable)
    {
        return super.toString();
    }
  }

  /***
   * Get a hash of the stream by reading each byte and using it in a CRC-32
   * checksum.
   */
  override size_t toHash() @trusted {
    if (!readable || !seekable)
      return super.toHash();
    try
    {
        ulong pos = position;
        scope(exit) position = pos;
        CRC32 crc;
        crc.start();
        position = 0;
        ulong len = size;
        for (ulong i = 0; i < len; i++)
        {
          ubyte c;
          read(c);
          crc.put(c);
        }

        union resUnion
        {
            size_t hash;
            ubyte[4] crcVal;
        }
        resUnion res;
        res.crcVal = crc.finish();
        return res.hash;
    }
    catch (Throwable)
    {
        return super.toHash();
    }
  }

  // helper for checking that the stream is readable
  final protected void assertReadable() {
    if (!readable)
      throw new ReadException("Stream is not readable");
  }
  // helper for checking that the stream is writeable
  final protected void assertWriteable() {
    if (!writeable)
      throw new WriteException("Stream is not writeable");
  }
  // helper for checking that the stream is seekable
  final protected void assertSeekable() {
    if (!seekable)
      throw new SeekException("Stream is not seekable");
  }
}

/***
 * A base class for streams that wrap a source stream with additional
 * functionality.
 *
 * The method implementations forward read/write/seek calls to the
 * source stream. A FilterStream can change the position of the source stream
 * arbitrarily and may not keep the source stream state in sync with the
 * FilterStream, even upon flushing and closing the FilterStream. It is
 * recommended to not make any assumptions about the state of the source position
 * and read/write state after a FilterStream has acted upon it. Specifc subclasses
 * of FilterStream should document how they modify the source stream and if any
 * invariants hold true between the source and filter.
 */
class FilterStream : Stream {
  private Stream s;              // source stream

  /// Property indicating when this stream closes to close the source stream as
  /// well.
  /// Defaults to true.
  bool nestClose = true;

  /// Construct a FilterStream for the given source.
  this(Stream source) {
    s = source;
    resetSource();
  }

  // source getter/setter

  /***
   * Get the current source stream.
   */
  final Stream source(){return s;}

  /***
   * Set the current source stream.
   *
   * Setting the source stream closes this stream before attaching the new
   * source. Attaching an open stream reopens this stream and resets the stream
   * state.
   */
  void source(Stream s) {
    close();
    this.s = s;
    resetSource();
  }

  /***
   * Indicates the source stream changed state and that this stream should reset
   * any readable, writeable, seekable, isopen and buffering flags.
   */
  void resetSource() {
    if (s !is null) {
      readable = s.readable;
      writeable = s.writeable;
      seekable = s.seekable;
      isopen = s.isOpen;
    } else {
      readable = writeable = seekable = false;
      isopen = false;
    }
    readEOF = prevCr = false;
  }

  // read from source
  override size_t readBlock(void* buffer, size_t size) {
    size_t res = s.readBlock(buffer,size);
    readEOF = res == 0;
    return res;
  }

  // write to source
  override size_t writeBlock(const void* buffer, size_t size) {
    return s.writeBlock(buffer,size);
  }

  // close stream
  override void close() {
    if (isopen) {
      super.close();
      if (nestClose)
        s.close();
    }
  }

  // seek on source
  override ulong seek(long offset, SeekPos whence) {
    readEOF = false;
    return s.seek(offset,whence);
  }

  override @property size_t available() { return s.available; }
  override void flush() { super.flush(); s.flush(); }
}

/***
 * This subclass is for buffering a source stream.
 *
 * A buffered stream must be
 * closed explicitly to ensure the final buffer content is written to the source
 * stream. The source stream position is changed according to the block size so
 * reading or writing to the BufferedStream may not change the source stream
 * position by the same amount.
 */
class BufferedStream : FilterStream {
  ubyte[] buffer;       // buffer, if any
  size_t bufferCurPos;    // current position in buffer
  size_t bufferLen;       // amount of data in buffer
  bool bufferDirty = false;
  size_t bufferSourcePos; // position in buffer of source stream position
  ulong streamPos;      // absolute position in source stream

  /* Example of relationship between fields:
   *
   *  s             ...01234567890123456789012EOF
   *  buffer                |--                     --|
   *  bufferCurPos                       |
   *  bufferLen             |--            --|
   *  bufferSourcePos                        |
   *
   */

  invariant() {
    assert(bufferSourcePos <= bufferLen);
    assert(bufferCurPos <= bufferLen);
    assert(bufferLen <= buffer.length);
  }

  enum size_t DefaultBufferSize = 8192;

  /***
   * Create a buffered stream for the stream source with the buffer size
   * bufferSize.
   */
  this(Stream source, size_t bufferSize = DefaultBufferSize) {
    super(source);
    if (bufferSize)
      buffer = new ubyte[bufferSize];
  }

  override protected void resetSource() {
    super.resetSource();
    streamPos = 0;
    bufferLen = bufferSourcePos = bufferCurPos = 0;
    bufferDirty = false;
  }

  // reads block of data of specified size using any buffered data
  // returns actual number of bytes read
  override size_t readBlock(void* result, size_t len) {
    if (len == 0) return 0;

    assertReadable();

    ubyte* outbuf = cast(ubyte*)result;
    size_t readsize = 0;

    if (bufferCurPos + len < bufferLen) {
      // buffer has all the data so copy it
      outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len];
      bufferCurPos += len;
      readsize = len;
      goto ExitRead;
    }

    readsize = bufferLen - bufferCurPos;
    if (readsize > 0) {
      // buffer has some data so copy what is left
      outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen];
      outbuf += readsize;
      bufferCurPos += readsize;
      len -= readsize;
    }

    flush();

    if (len >= buffer.length) {
      // buffer can't hold the data so fill output buffer directly
      size_t siz = super.readBlock(outbuf, len);
      readsize += siz;
      streamPos += siz;
    } else {
      // read a new block into buffer
        bufferLen = super.readBlock(buffer.ptr, buffer.length);
        if (bufferLen < len) len = bufferLen;
        outbuf[0 .. len] = buffer[0 .. len];
        bufferSourcePos = bufferLen;
        streamPos += bufferLen;
        bufferCurPos = len;
        readsize += len;
    }

  ExitRead:
    return readsize;
  }

  // write block of data of specified size
  // returns actual number of bytes written
  override size_t writeBlock(const void* result, size_t len) {
    assertWriteable();

    ubyte* buf = cast(ubyte*)result;
    size_t writesize = 0;

    if (bufferLen == 0) {
      // buffer is empty so fill it if possible
      if ((len < buffer.length) && (readable)) {
        // read in data if the buffer is currently empty
        bufferLen = s.readBlock(buffer.ptr, buffer.length);
        bufferSourcePos = bufferLen;
        streamPos += bufferLen;

      } else if (len >= buffer.length) {
        // buffer can't hold the data so write it directly and exit
        writesize = s.writeBlock(buf,len);
        streamPos += writesize;
        goto ExitWrite;
      }
    }

    if (bufferCurPos + len <= buffer.length) {
      // buffer has space for all the data so copy it and exit
      buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len];
      bufferCurPos += len;
      bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen;
      writesize = len;
      bufferDirty = true;
      goto ExitWrite;
    }

    writesize = buffer.length - bufferCurPos;
    if (writesize > 0) {
      // buffer can take some data
      buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize];
      bufferCurPos = bufferLen = buffer.length;
      buf += writesize;
      len -= writesize;
      bufferDirty = true;
    }

    assert(bufferCurPos == buffer.length);
    assert(bufferLen == buffer.length);

    flush();

    writesize += writeBlock(buf,len);

  ExitWrite:
    return writesize;
  }

  override ulong seek(long offset, SeekPos whence) {
    assertSeekable();

    if ((whence != SeekPos.Current) ||
        (offset + bufferCurPos < 0) ||
        (offset + bufferCurPos >= bufferLen)) {
      flush();
      streamPos = s.seek(offset,whence);
    } else {
      bufferCurPos += offset;
    }
    readEOF = false;
    return streamPos-bufferSourcePos+bufferCurPos;
  }

  // Buffered readLine - Dave Fladebo
  // reads a line, terminated by either CR, LF, CR/LF, or EOF
  // reusing the memory in buffer if result will fit, otherwise
  // will reallocate (using concatenation)
  template TreadLine(T) {
      T[] readLine(T[] inBuffer)
      {
          size_t    lineSize = 0;
          bool    haveCR = false;
          T       c = '\0';
          size_t    idx = 0;
          ubyte*  pc = cast(ubyte*)&c;

        L0:
          for(;;) {
              size_t start = bufferCurPos;
            L1:
              foreach(ubyte b; buffer[start .. bufferLen]) {
                  bufferCurPos++;
                  pc[idx] = b;
                  if(idx < T.sizeof - 1) {
                      idx++;
                      continue L1;
                  } else {
                      idx = 0;
                  }
                  if(c == '\n' || haveCR) {
                      if(haveCR && c != '\n') bufferCurPos--;
                      break L0;
                  } else {
                      if(c == '\r') {
                          haveCR = true;
                      } else {
                          if(lineSize < inBuffer.length) {
                              inBuffer[lineSize] = c;
                          } else {
                              inBuffer ~= c;
                          }
                          lineSize++;
                      }
                  }
              }
              flush();
              size_t res = super.readBlock(buffer.ptr, buffer.length);
              if(!res) break L0; // EOF
              bufferSourcePos = bufferLen = res;
              streamPos += res;
          }
          return inBuffer[0 .. lineSize];
      }
  } // template TreadLine(T)

  override char[] readLine(char[] inBuffer) {
    if (ungetAvailable())
      return super.readLine(inBuffer);
    else
      return TreadLine!(char).readLine(inBuffer);
  }
  alias Stream.readLine readLine;

  override wchar[] readLineW(wchar[] inBuffer) {
    if (ungetAvailable())
      return super.readLineW(inBuffer);
    else
      return TreadLine!(wchar).readLine(inBuffer);
  }
  alias Stream.readLineW readLineW;

  override void flush()
  out {
    assert(bufferCurPos == 0);
    assert(bufferSourcePos == 0);
    assert(bufferLen == 0);
  }
  body {
    if (writeable && bufferDirty) {
      if (bufferSourcePos != 0 && seekable) {
        // move actual file pointer to front of buffer
        streamPos = s.seek(-bufferSourcePos, SeekPos.Current);
      }
      // write buffer out
      bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen);
      if (bufferSourcePos != bufferLen) {
        throw new WriteException("Unable to write to stream");
      }
    }
    super.flush();
    long diff = cast(long)bufferCurPos-bufferSourcePos;
    if (diff != 0 && seekable) {
      // move actual file pointer to current position
      streamPos = s.seek(diff, SeekPos.Current);
    }
    // reset buffer data to be empty
    bufferSourcePos = bufferCurPos = bufferLen = 0;
    bufferDirty = false;
  }

  // returns true if end of stream is reached, false otherwise
  override @property bool eof() {
    if ((buffer.length == 0) || !readable) {
      return super.eof;
    }
    // some simple tests to avoid flushing
    if (ungetAvailable() || bufferCurPos != bufferLen)
      return false;
    if (bufferLen == buffer.length)
      flush();
    size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen);
    bufferSourcePos +=  res;
    bufferLen += res;
    streamPos += res;
    return readEOF;
  }

  // returns size of stream
  override @property ulong size() {
    if (bufferDirty) flush();
    return s.size;
  }

  // returns estimated number of bytes available for immediate reading
  override @property size_t available() {
    return bufferLen - bufferCurPos;
  }
}

/// An exception for File errors.
class StreamFileException: StreamException {
  /// Construct a StreamFileException with given error message.
  this(string msg) { super(msg); }
}

/// An exception for errors during File.open.
class OpenException: StreamFileException {
  /// Construct an OpenFileException with given error message.
  this(string msg) { super(msg); }
}

// access modes; may be or'ed
enum FileMode {
  In = 1,
  Out = 2,
  OutNew = 6, // includes FileMode.Out
  Append = 10 // includes FileMode.Out
}

version (Windows) {
  private import std.c.windows.windows;
  extern (Windows) {
    void FlushFileBuffers(HANDLE hFile);
    DWORD  GetFileType(HANDLE hFile);
  }
}
version (Posix) {
  private import core.sys.posix.fcntl;
  private import core.sys.posix.unistd;
  alias int HANDLE;
}

/// This subclass is for unbuffered file system streams.
class File: Stream {

  version (Windows) {
    private HANDLE hFile;
  }
  version (Posix) {
    private HANDLE hFile = -1;
  }

  this() {
    super();
    version (Windows) {
      hFile = null;
    }
    version (Posix) {
      hFile = -1;
    }
    isopen = false;
  }

  // opens existing handle; use with care!
  this(HANDLE hFile, FileMode mode) {
    super();
    this.hFile = hFile;
    readable = cast(bool)(mode & FileMode.In);
    writeable = cast(bool)(mode & FileMode.Out);
    version(Windows) {
      seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK
    } else {
      auto result = lseek(hFile, 0, 0);
      seekable = (result != ~0);
    }
  }

  /***
   * Create the stream with no open file, an open file in read mode, or an open
   * file with explicit file mode.
   * mode, if given, is a combination of FileMode.In
   * (indicating a file that can be read) and FileMode.Out (indicating a file
   * that can be written).
   * Opening a file for reading that doesn't exist will error.
   * Opening a file for writing that doesn't exist will create the file.
   * The FileMode.OutNew mode will open the file for writing and reset the
   * length to zero.
   * The FileMode.Append mode will open the file for writing and move the
   * file position to the end of the file.
   */
  this(string filename, FileMode mode = FileMode.In)
  {
      this();
      open(filename, mode);
  }


  /***
   * Open a file for the stream, in an identical manner to the constructors.
   * If an error occurs an OpenException is thrown.
   */
  void open(string filename, FileMode mode = FileMode.In) {
    close();
    int access, share, createMode;
    parseMode(mode, access, share, createMode);
    seekable = true;
    readable = cast(bool)(mode & FileMode.In);
    writeable = cast(bool)(mode & FileMode.Out);
    version (Windows) {
      hFile = CreateFileW(std.utf.toUTF16z(filename), access, share,
                          null, createMode, 0, null);
      isopen = hFile != INVALID_HANDLE_VALUE;
    }
    version (Posix) {
      hFile = core.sys.posix.fcntl.open(toStringz(filename), access | createMode, share);
      isopen = hFile != -1;
    }
    if (!isopen)
      throw new OpenException(cast(string) ("Cannot open or create file '"
                                            ~ filename ~ "'"));
    else if ((mode & FileMode.Append) == FileMode.Append)
      seekEnd(0);
  }

  private void parseMode(int mode,
                         out int access,
                         out int share,
                         out int createMode) {
    version (Windows) {
      share |= FILE_SHARE_READ | FILE_SHARE_WRITE;
      if (mode & FileMode.In) {
        access |= GENERIC_READ;
        createMode = OPEN_EXISTING;
      }
      if (mode & FileMode.Out) {
        access |= GENERIC_WRITE;
        createMode = OPEN_ALWAYS; // will create if not present
      }
      if ((mode & FileMode.OutNew) == FileMode.OutNew) {
        createMode = CREATE_ALWAYS; // resets file
      }
    }
    version (Posix) {
      share = octal!666;
      if (mode & FileMode.In) {
        access = O_RDONLY;
      }
      if (mode & FileMode.Out) {
        createMode = O_CREAT; // will create if not present
        access = O_WRONLY;
      }
      if (access == (O_WRONLY | O_RDONLY)) {
        access = O_RDWR;
      }
      if ((mode & FileMode.OutNew) == FileMode.OutNew) {
        access |= O_TRUNC; // resets file
      }
    }
  }

  /// Create a file for writing.
  void create(string filename) {
    create(filename, FileMode.OutNew);
  }

  /// ditto
  void create(string filename, FileMode mode) {
    close();
    open(filename, mode | FileMode.OutNew);
  }

  /// Close the current file if it is open; otherwise it does nothing.
  override void close() {
    if (isopen) {
      super.close();
      if (hFile) {
        version (Windows) {
          CloseHandle(hFile);
          hFile = null;
        } else version (Posix) {
          core.sys.posix.unistd.close(hFile);
          hFile = -1;
        }
      }
    }
  }

  // destructor, closes file if still opened
  ~this() { close(); }

  version (Windows) {
    // returns size of stream
    override @property ulong size() {
      assertSeekable();
      uint sizehi;
      uint sizelow = GetFileSize(hFile,&sizehi);
      return (cast(ulong)sizehi << 32) + sizelow;
    }
  }

  override size_t readBlock(void* buffer, size_t size) {
    assertReadable();
    version (Windows) {
      auto dwSize = to!DWORD(size);
      ReadFile(hFile, buffer, dwSize, &dwSize, null);
      size = dwSize;
    } else version (Posix) {
      size = core.sys.posix.unistd.read(hFile, buffer, size);
      if (size == -1)
        size = 0;
    }
    readEOF = (size == 0);
    return size;
  }

  override size_t writeBlock(const void* buffer, size_t size) {
    assertWriteable();
    version (Windows) {
      auto dwSize = to!DWORD(size);
      WriteFile(hFile, buffer, dwSize, &dwSize, null);
      size = dwSize;
    } else version (Posix) {
      size = core.sys.posix.unistd.write(hFile, buffer, size);
      if (size == -1)
        size = 0;
    }
    return size;
  }

  override ulong seek(long offset, SeekPos rel) {
    assertSeekable();
    version (Windows) {
      int hi = cast(int)(offset>>32);
      uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel);
      if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0))
        throw new SeekException("unable to move file pointer");
      ulong result = (cast(ulong)hi << 32) + low;
    } else version (Posix) {
      auto result = lseek(hFile, cast(int)offset, rel);
      if (result == cast(typeof(result))-1)
        throw new SeekException("unable to move file pointer");
    }
    readEOF = false;
    return cast(ulong)result;
  }

  /***
   * For a seekable file returns the difference of the size and position and
   * otherwise returns 0.
   */

  override @property size_t available() {
    if (seekable) {
      ulong lavail = size - position;
      if (lavail > size_t.max) lavail = size_t.max;
      return cast(size_t)lavail;
    }
    return 0;
  }

  // OS-specific property, just in case somebody wants
  // to mess with underlying API
  HANDLE handle() { return hFile; }

  // run a few tests
  unittest {
    File file = new File;
    int i = 666;
    file.create("stream.$$$");
    // should be ok to write
    assert(file.writeable);
    file.writeLine("Testing stream.d:");
    file.writeString("Hello, world!");
    file.write(i);
    // string#1 + string#2 + int should give exacly that
    version (Windows)
      assert(file.position == 19 + 13 + 4);
    version (Posix)
      assert(file.position == 18 + 13 + 4);
    // we must be at the end of file
    assert(file.eof);
    file.close();
    // no operations are allowed when file is closed
    assert(!file.readable && !file.writeable && !file.seekable);
    file.open("stream.$$$");
    // should be ok to read
    assert(file.readable);
    assert(file.available == file.size);
    char[] line = file.readLine();
    char[] exp = "Testing stream.d:".dup;
    assert(line[0] == 'T');
    assert(line.length == exp.length);
    assert(!std.algorithm.cmp(line, "Testing stream.d:"));
    // jump over "Hello, "
    file.seek(7, SeekPos.Current);
    version (Windows)
      assert(file.position == 19 + 7);
    version (Posix)
      assert(file.position == 18 + 7);
    assert(!std.algorithm.cmp(file.readString(6), "world!"));
    i = 0; file.read(i);
    assert(i == 666);
    // string#1 + string#2 + int should give exacly that
    version (Windows)
      assert(file.position == 19 + 13 + 4);
    version (Posix)
      assert(file.position == 18 + 13 + 4);
    // we must be at the end of file
    assert(file.eof);
    file.close();
    file.open("stream.$$$",FileMode.OutNew | FileMode.In);
    file.writeLine("Testing stream.d:");
    file.writeLine("Another line");
    file.writeLine("");
    file.writeLine("That was blank");
    file.position = 0;
    char[][] lines;
    foreach(char[] line; file) {
      lines ~= line.dup;
    }
    assert( lines.length == 4 );
    assert( lines[0] == "Testing stream.d:");
    assert( lines[1] == "Another line");
    assert( lines[2] == "");
    assert( lines[3] == "That was blank");
    file.position = 0;
    lines = new char[][4];
    foreach(ulong n, char[] line; file) {
      lines[cast(size_t)(n-1)] = line.dup;
    }
    assert( lines[0] == "Testing stream.d:");
    assert( lines[1] == "Another line");
    assert( lines[2] == "");
    assert( lines[3] == "That was blank");
    file.close();
    remove("stream.$$$");
  }
}

/***
 * This subclass is for buffered file system streams.
 *
 * It is a convenience class for wrapping a File in a BufferedStream.
 * A buffered stream must be closed explicitly to ensure the final buffer
 * content is written to the file.
 */
class BufferedFile: BufferedStream {

  /// opens file for reading
  this() { super(new File()); }

  /// opens file in requested mode and buffer size
  this(string filename, FileMode mode = FileMode.In,
       size_t bufferSize = DefaultBufferSize) {
    super(new File(filename,mode),bufferSize);
  }

  /// opens file for reading with requested buffer size
  this(File file, size_t bufferSize = DefaultBufferSize) {
    super(file,bufferSize);
  }

  /// opens existing handle; use with care!
  this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) {
    super(new File(hFile,mode),buffersize);
  }

  /// opens file in requested mode
  void open(string filename, FileMode mode = FileMode.In) {
    File sf = cast(File)s;
    sf.open(filename,mode);
    resetSource();
  }

  /// creates file in requested mode
  void create(string filename, FileMode mode = FileMode.OutNew) {
    File sf = cast(File)s;
    sf.create(filename,mode);
    resetSource();
  }

  // run a few tests same as File
  unittest {
    BufferedFile file = new BufferedFile;
    int i = 666;
    file.create("stream.$$$");
    // should be ok to write
    assert(file.writeable);
    file.writeLine("Testing stream.d:");
    file.writeString("Hello, world!");
    file.write(i);
    // string#1 + string#2 + int should give exacly that
    version (Windows)
      assert(file.position == 19 + 13 + 4);
    version (Posix)
      assert(file.position == 18 + 13 + 4);
    // we must be at the end of file
    assert(file.eof);
    long oldsize = cast(long)file.size;
    file.close();
    // no operations are allowed when file is closed
    assert(!file.readable && !file.writeable && !file.seekable);
    file.open("stream.$$$");
    // should be ok to read
    assert(file.readable);
    // test getc/ungetc and size
    char c1 = file.getc();
    file.ungetc(c1);
    assert( file.size == oldsize );
    assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:"));
    // jump over "Hello, "
    file.seek(7, SeekPos.Current);
    version (Windows)
      assert(file.position == 19 + 7);
    version (Posix)
      assert(file.position == 18 + 7);
    assert(!std.algorithm.cmp(file.readString(6), "world!"));
    i = 0; file.read(i);
    assert(i == 666);
    // string#1 + string#2 + int should give exacly that
    version (Windows)
      assert(file.position == 19 + 13 + 4);
    version (Posix)
      assert(file.position == 18 + 13 + 4);
    // we must be at the end of file
    assert(file.eof);
    file.close();
    remove("stream.$$$");
  }

}

/// UTF byte-order-mark signatures
enum BOM {
        UTF8,           /// UTF-8
        UTF16LE,        /// UTF-16 Little Endian
        UTF16BE,        /// UTF-16 Big Endian
        UTF32LE,        /// UTF-32 Little Endian
        UTF32BE,        /// UTF-32 Big Endian
}

private enum int NBOMS = 5;
immutable Endian[NBOMS] BOMEndian =
[ std.system.endian,
  Endian.littleEndian, Endian.bigEndian,
  Endian.littleEndian, Endian.bigEndian
  ];

immutable ubyte[][NBOMS] ByteOrderMarks =
[ [0xEF, 0xBB, 0xBF],
  [0xFF, 0xFE],
  [0xFE, 0xFF],
  [0xFF, 0xFE, 0x00, 0x00],
  [0x00, 0x00, 0xFE, 0xFF]
  ];


/***
 * This subclass wraps a stream with big-endian or little-endian byte order
 * swapping.
 *
 * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or
 * written.
 * Note that an EndianStream should not be used as the source of another
 * FilterStream since a FilterStream call the source with byte-oriented
 * read/write requests and the EndianStream will not perform any byte swapping.
 * The EndianStream reads and writes binary data (non-getc functions) in a
 * one-to-one
 * manner with the source stream so the source stream's position and state will be
 * kept in sync with the EndianStream if only non-getc functions are called.
 */
class EndianStream : FilterStream {

  Endian endian;        /// Endianness property of the source stream.

  /***
   * Create the endian stream for the source stream source with endianness end.
   * The default endianness is the native byte order.
   * The Endian type is defined
   * in the std.system module.
   */
  this(Stream source, Endian end = std.system.endian) {
    super(source);
    endian = end;
  }

  /***
   * Return -1 if no BOM and otherwise read the BOM and return it.
   *
   * If there is no BOM or if bytes beyond the BOM are read then the bytes read
   * are pushed back onto the ungetc buffer or ungetcw buffer.
   * Pass ungetCharSize == 2 to use
   * ungetcw instead of ungetc when no BOM is present.
   */
  int readBOM(int ungetCharSize = 1) {
    ubyte[4] BOM_buffer;
    int n = 0;       // the number of read bytes
    int result = -1; // the last match or -1
    for (int i=0; i < NBOMS; ++i) {
      int j;
      immutable ubyte[] bom = ByteOrderMarks[i];
      for (j=0; j < bom.length; ++j) {
        if (n <= j) { // have to read more
          if (eof)
            break;
          readExact(&BOM_buffer[n++],1);
        }
        if (BOM_buffer[j] != bom[j])
          break;
      }
      if (j == bom.length) // found a match
        result = i;
    }
    ptrdiff_t m = 0;
    if (result != -1) {
      endian = BOMEndian[result]; // set stream endianness
      m = ByteOrderMarks[result].length;
    }
    if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) {
      while (n-- > m)
        ungetc(BOM_buffer[n]);
    } else { // should eventually support unget for dchar as well
      if (n & 1) // make sure we have an even number of bytes
        readExact(&BOM_buffer[n++],1);
      while (n > m) {
        n -= 2;
        wchar cw = *(cast(wchar*)&BOM_buffer[n]);
        fixBO(&cw,2);
        ungetcw(cw);
      }
    }
    return result;
  }

  /***
   * Correct the byte order of buffer to match native endianness.
   * size must be even.
   */
  final void fixBO(const(void)* buffer, size_t size) {
    if (endian != std.system.endian) {
      ubyte* startb = cast(ubyte*)buffer;
      uint* start = cast(uint*)buffer;
      switch (size) {
      case 0: break;
      case 2: {
        ubyte x = *startb;
        *startb = *(startb+1);
        *(startb+1) = x;
        break;
      }
      case 4: {
        *start = bswap(*start);
        break;
      }
      default: {
        uint* end = cast(uint*)(buffer + size - uint.sizeof);
        while (start < end) {
          uint x = bswap(*start);
          *start = bswap(*end);
          *end = x;
          ++start;
          --end;
        }
        startb = cast(ubyte*)start;
        ubyte* endb = cast(ubyte*)end;
        auto len = uint.sizeof - (startb - endb);
        if (len > 0)
          fixBO(startb,len);
      }
      }
    }
  }

  /***
   * Correct the byte order of the given buffer in blocks of the given size and
   * repeated the given number of times.
   * size must be even.
   */
  final void fixBlockBO(void* buffer, uint size, size_t repeat) {
    while (repeat--) {
      fixBO(buffer,size);
      buffer += size;
    }
  }

  override void read(out byte x) { readExact(&x, x.sizeof); }
  override void read(out ubyte x) { readExact(&x, x.sizeof); }
  override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out ifloat x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out idouble x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out ireal x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out cfloat x) { readExact(&x, x.sizeof); fixBlockBO(&x,float.sizeof,2); }
  override void read(out cdouble x) { readExact(&x, x.sizeof); fixBlockBO(&x,double.sizeof,2); }
  override void read(out creal x) { readExact(&x, x.sizeof); fixBlockBO(&x,real.sizeof,2); }
  override void read(out char x) { readExact(&x, x.sizeof); }
  override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
  override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }

  override wchar getcw() {
    wchar c;
    if (prevCr) {
      prevCr = false;
      c = getcw();
      if (c != '\n')
        return c;
    }
    if (unget.length > 1) {
      c = unget[unget.length - 1];
      unget.length = unget.length - 1;
    } else {
      void* buf = &c;
      size_t n = readBlock(buf,2);
      if (n == 1 && readBlock(buf+1,1) == 0)
          throw new ReadException("not enough data in stream");
      fixBO(&c,c.sizeof);
    }
    return c;
  }

  override wchar[] readStringW(size_t length) {
    wchar[] result = new wchar[length];
    readExact(result.ptr, length * wchar.sizeof);
    fixBlockBO(result.ptr, wchar.sizeof, length);
    return result;
  }

  /// Write the specified BOM b to the source stream.
  void writeBOM(BOM b) {
    immutable ubyte[] bom = ByteOrderMarks[b];
    writeBlock(bom.ptr, bom.length);
  }

  override void write(byte x) { writeExact(&x, x.sizeof); }
  override void write(ubyte x) { writeExact(&x, x.sizeof); }
  override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(ifloat x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(idouble x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(ireal x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(cfloat x) { fixBlockBO(&x,float.sizeof,2); writeExact(&x, x.sizeof); }
  override void write(cdouble x) { fixBlockBO(&x,double.sizeof,2); writeExact(&x, x.sizeof); }
  override void write(creal x) { fixBlockBO(&x,real.sizeof,2); writeExact(&x, x.sizeof);  }
  override void write(char x) { writeExact(&x, x.sizeof); }
  override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
  override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }

  override void writeStringW(const(wchar)[] str) {
    foreach(wchar cw;str) {
      fixBO(&cw,2);
      s.writeExact(&cw, 2);
    }
  }

  override @property bool eof() { return s.eof && !ungetAvailable();  }
  override @property ulong size() { return s.size;  }

  unittest {
    MemoryStream m;
    m = new MemoryStream ();
    EndianStream em = new EndianStream(m,Endian.bigEndian);
    uint x = 0x11223344;
    em.write(x);
    assert( m.data[0] == 0x11 );
    assert( m.data[1] == 0x22 );
    assert( m.data[2] == 0x33 );
    assert( m.data[3] == 0x44 );
    em.position = 0;
    ushort x2 = 0x5566;
    em.write(x2);
    assert( m.data[0] == 0x55 );
    assert( m.data[1] == 0x66 );
    em.position = 0;
    static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12];
    em.fixBO(x3.ptr,12);
    if (std.system.endian == Endian.littleEndian) {
      assert( x3[0] == 12 );
      assert( x3[1] == 11 );
      assert( x3[2] == 10 );
      assert( x3[4] == 8 );
      assert( x3[5] == 7 );
      assert( x3[6] == 6 );
      assert( x3[8] == 4 );
      assert( x3[9] == 3 );
      assert( x3[10] == 2 );
      assert( x3[11] == 1 );
    }
    em.endian = Endian.littleEndian;
    em.write(x);
    assert( m.data[0] == 0x44 );
    assert( m.data[1] == 0x33 );
    assert( m.data[2] == 0x22 );
    assert( m.data[3] == 0x11 );
    em.position = 0;
    em.write(x2);
    assert( m.data[0] == 0x66 );
    assert( m.data[1] == 0x55 );
    em.position = 0;
    em.fixBO(x3.ptr,12);
    if (std.system.endian == Endian.bigEndian) {
      assert( x3[0] == 12 );
      assert( x3[1] == 11 );
      assert( x3[2] == 10 );
      assert( x3[4] == 8 );
      assert( x3[5] == 7 );
      assert( x3[6] == 6 );
      assert( x3[8] == 4 );
      assert( x3[9] == 3 );
      assert( x3[10] == 2 );
      assert( x3[11] == 1 );
    }
    em.writeBOM(BOM.UTF8);
    assert( m.position == 3 );
    assert( m.data[0] == 0xEF );
    assert( m.data[1] == 0xBB );
    assert( m.data[2] == 0xBF );
    em.writeString ("Hello, world");
    em.position = 0;
    assert( m.position == 0 );
    assert( em.readBOM() == BOM.UTF8 );
    assert( m.position == 3 );
    assert( em.getc() == 'H' );
    em.position = 0;
    em.writeBOM(BOM.UTF16BE);
    assert( m.data[0] == 0xFE );
    assert( m.data[1] == 0xFF );
    em.position = 0;
    em.writeBOM(BOM.UTF16LE);
    assert( m.data[0] == 0xFF );
    assert( m.data[1] == 0xFE );
    em.position = 0;
    em.writeString ("Hello, world");
    em.position = 0;
    assert( em.readBOM() == -1 );
    assert( em.getc() == 'H' );
    assert( em.getc() == 'e' );
    assert( em.getc() == 'l' );
    assert( em.getc() == 'l' );
    em.position = 0;
  }
}

/***
 * Parameterized subclass that wraps an array-like buffer with a stream
 * interface.
 *
 * The type Buffer must support the length property, opIndex and opSlice.
 * Compile in release mode when directly instantiating a TArrayStream to avoid
 * link errors.
 */
class TArrayStream(Buffer): Stream {
  Buffer buf; // current data
  ulong len;  // current data length
  ulong cur;  // current file position

  /// Create the stream for the the buffer buf. Non-copying.
  this(Buffer buf) {
    super ();
    this.buf = buf;
    this.len = buf.length;
    readable = writeable = seekable = true;
  }

  // ensure subclasses don't violate this
  invariant() {
    assert(len <= buf.length);
    assert(cur <= len);
  }

  override size_t readBlock(void* buffer, size_t size) {
    assertReadable();
    ubyte* cbuf = cast(ubyte*) buffer;
    if (len - cur < size)
      size = cast(size_t)(len - cur);
    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)];
    cbuf[0 .. size] = ubuf[];
    cur += size;
    return size;
  }

  override size_t writeBlock(const void* buffer, size_t size) {
    assertWriteable();
    ubyte* cbuf = cast(ubyte*) buffer;
    ulong blen = buf.length;
    if (cur + size > blen)
      size = cast(size_t)(blen - cur);
    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)];
    ubuf[] = cbuf[0 .. size];
    cur += size;
    if (cur > len)
      len = cur;
    return size;
  }

  override ulong seek(long offset, SeekPos rel) {
    assertSeekable();
    long scur; // signed to saturate to 0 properly

    switch (rel) {
    case SeekPos.Set: scur = offset; break;
    case SeekPos.Current: scur = cast(long)(cur + offset); break;
    case SeekPos.End: scur = cast(long)(len + offset); break;
    default:
        assert(0);
    }

    if (scur < 0)
      cur = 0;
    else if (scur > len)
      cur = len;
    else
      cur = cast(ulong)scur;

    return cur;
  }

  override @property size_t available () { return cast(size_t)(len - cur); }

  /// Get the current memory data in total.
  @property ubyte[] data() {
    if (len > size_t.max)
      throw new StreamException("Stream too big");
    const(void)[] res = buf[0 .. cast(size_t)len];
    return cast(ubyte[])res;
  }

  override string toString() {
      // assume data is UTF8
      return to!(string)(cast(char[])data);
  }
}

/* Test the TArrayStream */
unittest {
  char[100] buf;
  TArrayStream!(char[]) m;

  m = new TArrayStream!(char[]) (buf);
  assert (m.isOpen);
  m.writeString ("Hello, world");
  assert (m.position == 12);
  assert (m.available == 88);
  assert (m.seekSet (0) == 0);
  assert (m.available == 100);
  assert (m.seekCur (4) == 4);
  assert (m.available == 96);
  assert (m.seekEnd (-8) == 92);
  assert (m.available == 8);
  assert (m.size == 100);
  assert (m.seekSet (4) == 4);
  assert (m.readString (4) == "o, w");
  m.writeString ("ie");
  assert (buf[0..12] == "Hello, wield");
  assert (m.position == 10);
  assert (m.available == 90);
  assert (m.size == 100);
  m.seekSet (0);
  assert (m.printf ("Answer is %d", 42) == 12);
  assert (buf[0..12] == "Answer is 42");
}

/// This subclass reads and constructs an array of bytes in memory.
class MemoryStream: TArrayStream!(ubyte[]) {

  /// Create the output buffer and setup for reading, writing, and seeking.
  // clear to an empty buffer.
  this() { this(cast(ubyte[]) null); }

  /***
   * Create the output buffer and setup for reading, writing, and seeking.
   * Load it with specific input data.
   */
  this(ubyte[] buf) { super (buf); }
  this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto
  this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto

  /// Ensure the stream can write count extra bytes from cursor position without an allocation.
  void reserve(size_t count) {
    if (cur + count > buf.length)
      buf.length = cast(uint)((cur + count) * 2);
  }

  override size_t writeBlock(const void* buffer, size_t size) {
    reserve(size);
    return super.writeBlock(buffer,size);
  }

  unittest {
    MemoryStream m;

    m = new MemoryStream ();
    assert (m.isOpen);
    m.writeString ("Hello, world");
    assert (m.position == 12);
    assert (m.seekSet (0) == 0);
    assert (m.available == 12);
    assert (m.seekCur (4) == 4);
    assert (m.available == 8);
    assert (m.seekEnd (-8) == 4);
    assert (m.available == 8);
    assert (m.size == 12);
    assert (m.readString (4) == "o, w");
    m.writeString ("ie");
    assert (cast(char[]) m.data == "Hello, wield");
    m.seekEnd (0);
    m.writeString ("Foo");
    assert (m.position == 15);
    assert (m.available == 0);
    m.writeString ("Foo foo foo foo foo foo foo");
    assert (m.position == 42);
    m.position = 0;
    assert (m.available == 42);
    m.writef("%d %d %s",100,345,"hello");
    auto str = m.toString();
    assert (str[0..13] == "100 345 hello", str[0 .. 13]);
    assert (m.available == 29);
    assert (m.position == 13);

    MemoryStream m2;
    m.position = 3;
    m2 = new MemoryStream ();
    m2.writeString("before");
    m2.copyFrom(m,10);
    str = m2.toString();
    assert (str[0..16] == "before 345 hello");
    m2.position = 3;
    m2.copyFrom(m);
    auto str2 = m.toString();
    str = m2.toString();
    assert (str == ("bef" ~ str2));
  }
}

import std.mmfile;

/***
 * This subclass wraps a memory-mapped file with the stream API.
 * See std.mmfile module.
 */
class MmFileStream : TArrayStream!(MmFile) {

  /// Create stream wrapper for file.
  this(MmFile file) {
    super (file);
    MmFile.Mode mode = file.mode();
    writeable = mode > MmFile.Mode.read;
  }

  override void flush() {
    if (isopen) {
      super.flush();
      buf.flush();
    }
  }

  override void close() {
    if (isopen) {
      super.close();
      delete buf;
      buf = null;
    }
  }
}

unittest {
  MmFile mf = new MmFile("testing.txt",MmFile.Mode.readWriteNew,100,null);
  MmFileStream m;
  m = new MmFileStream (mf);
  m.writeString ("Hello, world");
  assert (m.position == 12);
  assert (m.seekSet (0) == 0);
  assert (m.seekCur (4) == 4);
  assert (m.seekEnd (-8) == 92);
  assert (m.size == 100);
  assert (m.seekSet (4));
  assert (m.readString (4) == "o, w");
  m.writeString ("ie");
  ubyte[] dd = m.data;
  assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield");
  m.position = 12;
  m.writeString ("Foo");
  assert (m.position == 15);
  m.writeString ("Foo foo foo foo foo foo foo");
  assert (m.position == 42);
  m.close();
  mf = new MmFile("testing.txt");
  m = new MmFileStream (mf);
  assert (!m.writeable);
  char[] str = m.readString(12);
  assert (str == "Hello, wield");
  m.close();
  std.file.remove("testing.txt");
}


/***
 * This subclass slices off a portion of another stream, making seeking relative
 * to the boundaries of the slice.
 *
 * It could be used to section a large file into a
 * set of smaller files, such as with tar archives. Reading and writing a
 * SliceStream does not modify the position of the source stream if it is
 * seekable.
 */
class SliceStream : FilterStream {
  private {
    ulong pos;  // our position relative to low
    ulong low; // low stream offset.
    ulong high; // high stream offset.
    bool bounded; // upper-bounded by high.
  }

  /***
   * Indicate both the source stream to use for reading from and the low part of
   * the slice.
   *
   * The high part of the slice is dependent upon the end of the source
   * stream, so that if you write beyond the end it resizes the stream normally.
   */
  this (Stream s, ulong low)
  in {
    assert (low <= s.size);
  }
  body {
    super(s);
    this.low = low;
    this.high = 0;
    this.bounded = false;
  }

  /***
   * Indicate the high index as well.
   *
   * Attempting to read or write past the high
   * index results in the end being clipped off.
   */
  this (Stream s, ulong low, ulong high)
  in {
    assert (low <= high);
    assert (high <= s.size);
  }
  body {
    super(s);
    this.low = low;
    this.high = high;
    this.bounded = true;
  }

  invariant() {
    if (bounded)
      assert (pos <= high - low);
    else
      // size() does not appear to be const, though it should be
      assert (pos <= (cast()s).size - low);
  }

  override size_t readBlock (void *buffer, size_t size) {
    assertReadable();
    if (bounded && size > high - low - pos)
        size = cast(size_t)(high - low - pos);
    ulong bp = s.position;
    if (seekable)
      s.position = low + pos;
    size_t ret = super.readBlock(buffer, size);
    if (seekable) {
      pos = s.position - low;
      s.position = bp;
    }
    return ret;
  }

  override size_t writeBlock (const void *buffer, size_t size) {
    assertWriteable();
    if (bounded && size > high - low - pos)
        size = cast(size_t)(high - low - pos);
    ulong bp = s.position;
    if (seekable)
      s.position = low + pos;
    size_t ret = s.writeBlock(buffer, size);
    if (seekable) {
      pos = s.position - low;
      s.position = bp;
    }
    return ret;
  }

  override ulong seek(long offset, SeekPos rel) {
    assertSeekable();
    long spos;

    switch (rel) {
      case SeekPos.Set:
        spos = offset;
        break;
      case SeekPos.Current:
        spos = cast(long)(pos + offset);
        break;
      case SeekPos.End:
        if (bounded)
          spos = cast(long)(high - low + offset);
        else
          spos = cast(long)(s.size - low + offset);
        break;
      default:
        assert(0);
    }

    if (spos < 0)
      pos = 0;
    else if (bounded && spos > high - low)
      pos = high - low;
    else if (!bounded && spos > s.size - low)
      pos = s.size - low;
    else
      pos = cast(ulong)spos;

    readEOF = false;
    return pos;
  }

  override @property size_t available() {
    size_t res = s.available;
    ulong bp = s.position;
    if (bp <= pos+low && pos+low <= bp+res) {
      if (!bounded || bp+res <= high)
        return cast(size_t)(bp + res - pos - low);
      else if (high <= bp+res)
        return cast(size_t)(high - pos - low);
    }
    return 0;
  }

  unittest {
    MemoryStream m;
    SliceStream s;

    m = new MemoryStream ((cast(char[])"Hello, world").dup);
    s = new SliceStream (m, 4, 8);
    assert (s.size == 4);
    assert (m.position == 0);
    assert (s.position == 0);
    assert (m.available == 12);
    assert (s.available == 4);

    assert (s.writeBlock (cast(char *) "Vroom", 5) == 4);
    assert (m.position == 0);
    assert (s.position == 4);
    assert (m.available == 12);
    assert (s.available == 0);
    assert (s.seekEnd (-2) == 2);
    assert (s.available == 2);
    assert (s.seekEnd (2) == 4);
    assert (s.available == 0);
    assert (m.position == 0);
    assert (m.available == 12);

    m.seekEnd(0);
    m.writeString("\nBlaho");
    assert (m.position == 18);
    assert (m.available == 0);
    assert (s.position == 4);
    assert (s.available == 0);

    s = new SliceStream (m, 4);
    assert (s.size == 14);
    assert (s.toString () == "Vrooorld\nBlaho");
    s.seekEnd (0);
    assert (s.available == 0);

    s.writeString (", etcetera.");
    assert (s.position == 25);
    assert (s.seekSet (0) == 0);
    assert (s.size == 25);
    assert (m.position == 18);
    assert (m.size == 29);
    assert (m.toString() == "HellVrooorld\nBlaho, etcetera.");
  }
}
