#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2011 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module provides a stream interface and two implementations thereof:
## the `PFileStream` and the `PStringStream` which implement the stream
## interface for Nimrod file objects (`TFile`) and strings. Other modules
## may provide other implementations for this standard stream interface.

proc newEIO(msg: string): ref EIO =
  new(result)
  result.msg = msg

type
  PStream* = ref TStream
  TStream* = object of TObject ## Stream interface that supports
                               ## writing or reading.
    close*: proc (s: PStream)
    atEnd*: proc (s: PStream): bool
    setPosition*: proc (s: PStream, pos: int)
    getPosition*: proc (s: PStream): int
    readData*: proc (s: PStream, buffer: pointer, bufLen: int): int
    writeData*: proc (s: PStream, buffer: pointer, bufLen: int)

proc write*[T](s: PStream, x: T) = 
  ## generic write procedure. Writes `x` to the stream `s`. Implementation:
  ##
  ## .. code-block:: Nimrod
  ##
  ##     s.writeData(s, addr(x), sizeof(x))
  var y: T
  shallowCopy(y, x)
  s.writeData(s, addr(y), sizeof(y))

proc write*(s: PStream, x: string) = 
  ## writes the string `x` to the the stream `s`. No length field or 
  ## terminating zero is written.
  s.writeData(s, cstring(x), x.len)

proc read[T](s: PStream, result: var T) = 
  ## generic read procedure. Reads `result` from the stream `s`.
  if s.readData(s, addr(result), sizeof(T)) != sizeof(T):
    raise newEIO("cannot read from stream")

proc readChar*(s: PStream): char =
  ## reads a char from the stream `s`. Raises `EIO` if an error occured.
  ## Returns '\0' as an EOF marker.
  discard s.readData(s, addr(result), sizeof(result))

proc readBool*(s: PStream): bool = 
  ## reads a bool from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt8*(s: PStream): int8 = 
  ## reads an int8 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt16*(s: PStream): int16 = 
  ## reads an int16 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt32*(s: PStream): int32 = 
  ## reads an int32 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt64*(s: PStream): int64 = 
  ## reads an int64 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readFloat32*(s: PStream): float32 = 
  ## reads a float32 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readFloat64*(s: PStream): float64 = 
  ## reads a float64 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readStr*(s: PStream, length: int): TaintedString = 
  ## reads a string of length `length` from the stream `s`. Raises `EIO` if 
  ## an error occured.
  result = newString(length).TaintedString
  var L = s.readData(s, addr(string(result)[0]), length)
  if L != length: setLen(result.string, L)

proc readLine*(s: PStream): TaintedString =
  ## Reads a line from a stream `s`. Note: This is not very efficient. Raises 
  ## `EIO` if an error occured.
  result = TaintedString""
  while not s.atEnd(s): 
    var c = readChar(s)
    if c == '\c': 
      c = readChar(s)
      break
    elif c == '\L' or c == '\0': break
    result.string.add(c)

type
  PStringStream* = ref TStringStream ## a stream that encapsulates a string
  TStringStream* = object of TStream
    data*: string
    pos: int
    
proc ssAtEnd(s: PStream): bool = 
  var s = PStringStream(s)
  return s.pos >= s.data.len
    
proc ssSetPosition(s: PStream, pos: int) = 
  var s = PStringStream(s)
  s.pos = min(pos, s.data.len-1)

proc ssGetPosition(s: PStream): int =
  var s = PStringStream(s)
  return s.pos

proc ssReadData(s: PStream, buffer: pointer, bufLen: int): int =
  var s = PStringStream(s)
  result = min(bufLen, s.data.len - s.pos)
  if result > 0: 
    copyMem(buffer, addr(s.data[s.pos]), result)
    inc(s.pos, result)

proc ssWriteData(s: PStream, buffer: pointer, bufLen: int) = 
  var s = PStringStream(s)
  if bufLen > 0: 
    setLen(s.data, s.data.len + bufLen)
    copyMem(addr(s.data[s.pos]), buffer, bufLen)
    inc(s.pos, bufLen)

proc ssClose(s: PStream) =
  var s = PStringStream(s)
  s.data = nil

proc newStringStream*(s: string = ""): PStringStream = 
  ## creates a new stream from the string `s`.
  new(result)
  result.data = s
  result.pos = 0
  result.close = ssClose
  result.atEnd = ssAtEnd
  result.setPosition = ssSetPosition
  result.getPosition = ssGetPosition
  result.readData = ssReadData
  result.writeData = ssWriteData

type
  PFileStream* = ref TFileStream ## a stream that encapsulates a `TFile`
  TFileStream* = object of TStream
    f: TFile

proc fsClose(s: PStream) = close(PFileStream(s).f)
proc fsAtEnd(s: PStream): bool = return EndOfFile(PFileStream(s).f)
proc fsSetPosition(s: PStream, pos: int) = setFilePos(PFileStream(s).f, pos)
proc fsGetPosition(s: PStream): int = return int(getFilePos(PFileStream(s).f))

proc fsReadData(s: PStream, buffer: pointer, bufLen: int): int = 
  result = readBuffer(PFileStream(s).f, buffer, bufLen)
  
proc fsWriteData(s: PStream, buffer: pointer, bufLen: int) = 
  if writeBuffer(PFileStream(s).f, buffer, bufLen) != bufLen: 
    raise newEIO("cannot write to stream")

proc newFileStream*(f: TFile): PFileStream = 
  ## creates a new stream from the file `f`.
  new(result)
  result.f = f
  result.close = fsClose
  result.atEnd = fsAtEnd
  result.setPosition = fsSetPosition
  result.getPosition = fsGetPosition
  result.readData = fsReadData
  result.writeData = fsWriteData

proc newFileStream*(filename: string, mode: TFileMode): PFileStream = 
  ## creates a new stream from the file named `filename` with the mode `mode`.
  ## If the file cannot be opened, nil is returned.
  var f: TFile
  if Open(f, filename, mode): result = newFileStream(f)


when true:
  nil
else:
  type
    TFileHandle* = cint ## Operating system file handle
    PFileHandleStream* = ref TFileHandleStream
    TFileHandleStream* = object of TStream
      handle*: TFileHandle
      pos: int

  proc newEOS(msg: string): ref EOS =
    new(result)
    result.msg = msg

  proc hsGetPosition(s: PFileHandleStream): int = 
    return s.pos

  when defined(windows):
    # do not import windows as this increases compile times:
    nil
  else:
    import posix
    
    proc hsSetPosition(s: PFileHandleStream, pos: int) = 
      discard lseek(s.handle, pos, SEEK_SET)

    proc hsClose(s: PFileHandleStream) = discard close(s.handle)
    proc hsAtEnd(s: PFileHandleStream): bool = 
      var pos = hsGetPosition(s)
      var theEnd = lseek(s.handle, 0, SEEK_END)
      result = pos >= theEnd
      hsSetPosition(s, pos) # set position back

    proc hsReadData(s: PFileHandleStream, buffer: pointer, bufLen: int): int = 
      result = posix.read(s.handle, buffer, bufLen)
      inc(s.pos, result)
      
    proc hsWriteData(s: PFileHandleStream, buffer: pointer, bufLen: int) = 
      if posix.write(s.handle, buffer, bufLen) != bufLen: 
        raise newEIO("cannot write to stream")
      inc(s.pos, bufLen)

  proc newFileHandleStream*(handle: TFileHandle): PFileHandleStream = 
    new(result)
    result.handle = handle
    result.pos = 0
    result.close = hsClose
    result.atEnd = hsAtEnd
    result.setPosition = hsSetPosition
    result.getPosition = hsGetPosition
    result.readData = hsReadData
    result.writeData = hsWriteData

  proc newFileHandleStream*(filename: string, 
                            mode: TFileMode): PFileHandleStream = 
    when defined(windows): 
      nil
    else:
      var flags: cint
      case mode
      of fmRead:              flags = posix.O_RDONLY
      of fmWrite:             flags = O_WRONLY or int(O_CREAT)
      of fmReadWrite:         flags = O_RDWR or int(O_CREAT)
      of fmReadWriteExisting: flags = O_RDWR
      of fmAppend:            flags = O_WRONLY or int(O_CREAT) or O_APPEND
      var handle = open(filename, flags)
      if handle < 0: raise newEOS("posix.open() call failed")
    result = newFileHandleStream(handle)