#
# port.rb
#
#   Copyright (c) 1998-2001 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


  ###
  ###  FilePort
  ###

  class FilePort < Port

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

    attr :filename

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

    alias == eql?

    def hash
      @filename.hash
    end

    def read_all
      s = nil
      ropen do |f|
        s = f.read_all
      end
      s
    end

    alias to_s read_all

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

    def size
      File.size @filename
    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 rm
      File.unlink @filename
    end

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

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

  end



  class Stream

    include Enumerable

    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 inspect
      "<FileIStream:open?=#{not @closed},file=#{@filename}>"
    end

    alias to_s inspect

    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 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 read_all
      @f.read.to_s
    end

    alias readall read_all

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

    def add_to( to )
      while tmp = read(4096) do
        to << tmp
      end
    end

    alias copy_to add_to


    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 inspect
      "<FileOStream:open?=#{not @closed},file=#{@filename}>"
    end

    alias to_s inspect

    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 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 << str
    end

    def rewind
      @f.rewind
    end

  end


  ###
  ###  StringPort
  ###

  class StringPort < Port

    def initialize( str = '', eol = nil, unify = false )
      @str = str
      @eol = eol || "\n"
      @unifyeol = unify
      super()
    end

    attr :str; protected :str

    def size
      @str.size
    end

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

    alias == eql?

    def hash
      @str.hash
    end

    def to_s
      @str.dup
    end

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


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

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

    def aopen
      p = StringOStream.new( @str, @eol, @unifyeol )
      if iterator? then
        yield p
        p.close
      else
        return p
      end
    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

  end


  class StringIStream < Stream

    def initialize( str, eol, unify )
      @str = str
      @term = eol
      @unifyeol = unify

      @closed = false
      @pos = 0
    end

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

    alias to_s inspect

    def string
      @str
    end

    def stop
      @closed = true
    end

    alias interrupt stop

    def restart
      @closed = false
    end

    def close
      @closed = true
      @str = nil
    end

    def eof?
      @pos > @str.size
    end

    def rewind
      @pos = 0
    end

    def each( &block )
      setterm unless @term
      @str.each( @term ) {|s| yield s }
    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

      ret
    end

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

    def read_all
      return '' if eof?

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

    alias readall read_all

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

    def add_to( p )
      p << read_all
    end

    alias copy_to add_to


    private

    def setterm
      if m = /\n|\r\n|\r/.match(@str) then
        @term = m[0]
      else
        @term = "\n"
      end
    end

  end


  class StringOStream < Stream

    def initialize( str, eol, unify )
      @str = str
      @term = eol
      @unifyeol = unify

      @closed = false
    end

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

    alias to_s inspect

    def string
      @str
    end

    def stop
      @closed = true
    end

    alias interrupt stop

    def restart
      @closed = false
    end

    def close
      @closed = true
      @str = nil
    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.to_s
      self
    end

    # convert eol
    def cwrite( str )
      if @unifyeol
        @str << str.gsub( /\n|\r\n|\r/, @eol )
      else
        @str << str
      end
    end

  end

end   # module TMail
