#
# port.rb
#
#   Copyright (c) 1999 Minero Aoki <aamine@dp.u-netsurf.ne.jp>
#
#   This program is free software.
#   You can distribute/modify this program under the terms of
#   the GNU Lesser General Public License version 2 or later.
#

require 'amstd/fileutils'


module TMail


class Port

  # initialize() may defined by user

end


class FilePort < Port

  FU = FileUtilities

  def initialize( fname )
    @filename = File.expand_path( fname )
    super()
  end

  def ropen
    if iterator? then
      begin
        p = FileIStream.new( @filename )
        yield p
      ensure
        p.close if p
      end
    else
      FileIStream.new( @filename )
    end
  end

  def wopen
    if iterator? then
      begin
        p = FileOStream.new( @filename )
        yield p
      ensure
        p.close if p
      end
    else
      FileOStream.new( @filename )
    end
  end

  def aopen
    if iterator? then
      begin
        p = FileOStream.new( @filename, 'a' )
        yield p
      ensure
        p.close if p
      end
    else
      FileOStream.new( @filename, 'a' )
    end
  end


  def to_s
    "<FilePort:#{@filename}>"
  end

  def rm
    File.unlink( @filename ) if File.file?( @filename )
  end

  def mv( port )
    FU.mv @filename, port.filename
    rm
  end

  BSIZE = FU::BSIZE

  def cp( port )
    if Port === port then
      FU.cp @filename, port.filename
    else
      ropen {|f|
        while tmp = f.read( BSIZE ) do
          port << tmp
        end
      }
    end
  end

  def size
    File.size @filename
  end

  def eql?( other )
    FilePort === other and @filename == other.filename
  end
  alias == eql?

  def hash
    @filename.hash
  end


  protected

  attr :filename

end



class Stream

  CR = "\r"[0]
  LF = "\n"[0]

  def closed?
    @closed
  end

  alias interrupted? closed?

  attr :term, true

end


class FileIStream < Stream

  def initialize( fname )
    @filename = fname
    @f = File.open( fname, 'r' )
    @term = nil
    @closed = false
    @pre = nil
  end

  def stop
    @pre = @f.tell
    @f.close
    @closed = true
  end
  alias interrupt stop

  def restart
    @f = File.open( @filename, 'r' )
    @f.seek @pre, 0
    @closed = false
  end

  def close
    @f.close
    @closed = true
    @pre = nil
    @filename = nil
  end

  def to_s
    "<FileIStream:open?=#{not @closed},file=#{@filename}>"
  end

  def each
    setterm unless @term
    @f.each( @term ){|l| yield l }
  end

  def gets
    setterm unless @term
    @f.gets @term
  end

  def getc
    @f.getc
  end

  def readall
    @f.read.to_s
  end

  def read( num )
    setterm unless @term
    @f.read num
  end

  BSIZE = FilePort::BSIZE

  def copy_to( to )
    while tmp = read( BSIZE ) do
      to << tmp
    end
  end


  private

  def setterm
    @term = "\n"
    pre = @f.tell
    while ch = @f.getc do
      if ch == CR then
        if @f.getc == LF then
          @term = "\r\n" ; break
        else
          @term = "\r" ; break
        end
      elsif ch == LF then
        @term = "\n" ; break
      end
    end
    @f.seek pre, 0
  end

end


class FileOStream < Stream
  
  def initialize( fname, mode = 'w' )
    @filename = fname
    @closed = false
    @pre = false
    @f = File.open( fname, mode )
    @term = "\n"
  end

  def stop
    @pre = @f.tell
    @f.close
    @closed = true
  end
  alias interrupt stop

  def restart
    @f = File.open( @filename, 'w' )
    @f.seek @pre, 0
    @closed = false
  end

  def close
    @f.close
    @closed = true
    @pre = nil
    @filename = nil
  end

  def to_s
    "<FileOStream:open?=#{not @closed},file=#{@filename}>"
  end

  def puts( str = nil )
    @term = "\n" unless @term
    @f.write str if str
    @f.write @term
  end

  def putc( ch )
    @f.putc ch
  end

  def write( str )
    @f.write str
  end

  def <<( str )
    @f.write str
    self
  end

  def rewind
    @f.rewind
  end

end


#--------------------------------------------------------------


class StringPort < Port

  def initialize
    @str = ''
    super()
  end

  def ropen
    p = StringIStream.new( @str )
    if iterator? then
      yield p
      p.close
    else
      return p
    end
  end

  def wopen
    @str.replace ''
    p = StringOStream.new( @str )
    if iterator? then
      yield p
      p.close
    else
      return p
    end
  end

  def aopen
    p = StringOStream.new( @str )
    if iterator? then
      yield p
      p.close
    else
      return p
    end
  end

  def to_s
    "<StringPort:str=#{@str[0,30]}...>"
  end

  def rm
    @str.replace ''
    @str.freeze
  end

  def cp( port )
    if StringPort === port then
      port.str.replace @str
    else
      port.wopen do |s|
        s.write @str
      end
    end
  end

  def mv( port )
    cp port
    port.rm
  end

  def size
    @str.size
  end

  def eql?( other )
    StringPort === other and @str == other.str
  end
  alias == eql?

  def hash
    @str.hash
  end


  protected
  
  attr :str

end


class StringIStream < Stream

  def initialize( str )
    @str = str
    @closed = false
    @pos = 0
    @term = nil
  end

  def stop
    @closed = true
  end
  alias interrupt stop

  def restart
    @closed = false
  end

  def close
    @closed = true
    @str = nil
  end

  def to_s
    "<StringIStream:open?=#{not @closed},str=#{@str[0,30].inspect}>"
  end

  def rewind
    @pos = 0
  end

  def each( &block )
    setterm unless @term
    @str.each( @term ){|l| yield l }
  end

  def gets
    setterm unless @term
    if n = @str.index( @term, @pos )
      ret = @str[ @pos, n + @term.size - @pos ]
      @pos = n + @term.size
      if @pos == @str.size then @pos += 1 end
    else
      ret = @str[ @pos, @str.size - @pos ]
      @pos = @str.size + 1
    end

    return ret
  end

  def getc
    ret = @str[ @pos ]
    @pos += 1
    return ret
  end

  def readall
    ret = @str[ @pos, @str.size - @pos ]
    @pos = @str.size + 1
    ret
  end

  def read( num )
    ret = @str[ @pos, num ]
    @pos += num
    if @pos == @str.size then @pos += 1 end
    return ret
  end

  def copy_to( p )
    p << readall
  end


  private

  def setterm
    if /\n|\r\n|\r/o === @str then
      @term = $&
    else
      @term = "\n"
    end
  end

end


class StringOStream < Stream

  def initialize( str )
    @str = str
    @term = "\n"
    @closed = false
  end

  def stop
    @closed = true
  end
  alias interrupt stop

  def restart
    @closed = false
  end

  def close
    @closed = true
    @str = nil
  end

  def to_s
    "<StringOStream:open?=#{not @closed},str=#{@str[0,30].inspect}>"
  end

  def rewind
    @str.replace ''
  end

  def puts( str = nil )
    @str << str if str
    @str << @term
  end

  def putc( ch )
    @str << ch.chr
  end

  def write( str )
    @str << str
  end

  def <<( str )
    @str << str
    self
  end

end


end   # module TMail
