require 'rubyunit'
require 'stringio'

# borrowed from rubicon/builtin/TestIO.rb

class TestStrignIO < TestCase
  SAMPLE = "08: This is a line\n"

  LINE_LENGTH = SAMPLE.length

  TESTSTR  = ""
  TESTSTR1 = ""

  10.times {|i| TESTSTR << sprintf("%02d: This is a line\n", i)}
  99.times {|i| TESTSTR1 << sprintf("Line %02d\n", i)}

  TESTSTR.freeze
  TESTSTR1.freeze

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

  def test_s_new
    io = StringIO.new(TESTSTR, "r")
    begin
      count = 0
      io.each { count += 1 }
      assert_equal(10, count)
    ensure
      io.close
    end

    io = StringIO.new(TESTSTR, "r")

    begin
      io.close
      assert_exception(IOError) { io.gets }
    ensure
    end

    io = StringIO.open(TESTSTR, "r")
    io.sysread(3*LINE_LENGTH)
    begin
      assert_equal(3*LINE_LENGTH, io.tell)
      count = 0
      io.each { count += 1 }
      assert_equal(7, count)
    ensure
      io.close
    end

    assert_no_exception(Errno::EACCES) {StringIO.new(TESTSTR)}
    assert_exception(Errno::EACCES) {StringIO.new(TESTSTR, "w")}
    assert_exception(Errno::EACCES) {StringIO.new(TESTSTR, "a")}
    assert_exception(Errno::EACCES) {StringIO.new(TESTSTR, "r+")}
    assert_exception(Errno::EACCES) {StringIO.new(TESTSTR, "w+")}
    assert_exception(Errno::EACCES) {StringIO.new(TESTSTR, "a+")}
  end

  class Dummy
    def to_s
      "dummy"
    end
  end

  def test_LSHIFT # '<<'
    StringIO.open(s = "", "w") do |io|
      io << 1 << "\n" << Dummy.new << "\n" << "cat\n"
      io.close
    end
    assert_equal(s, "1\ndummy\ncat\n")
  end

  def test_clone
    # check file position shared
    StringIO.open(TESTSTR, "r") do |file|
      assert_kind_of(StringIO, file)
      io = [file, file.clone]
      assert_kind_of(StringIO, io[1])
      assert_equal(0, file.lineno)
      count = 0
      while line = io[count & 1].gets
	num = line[0..1].to_i
	assert_equal(count, num)
	count += 1
      end
      assert_equal(10, count)
    end
  end

  def test_close
    io = StringIO.new(TESTSTR, "r")
    begin
      io.close
      assert_exception(IOError) { io.gets }
    ensure
      begin
        io.close
      rescue Exception
      end
    end
  end

  def test_close_read
    o = StringIO.new("", "w")
    i = StringIO.new(o.string)
    begin
      o.puts "Hello"
      assert_equal("Hello\n", i.gets)
      i.close_read
      assert_exception(IOError) { i.gets }
    ensure
      o.close_write
    end
  end

  def test_close_write
    o = StringIO.new("", "w")
    i = StringIO.new(o.string)
    begin 
      o.puts "Hello"
      assert_equal("Hello\n", i.gets)
      o.close_write
      assert_exception(IOError) { o.puts "Hello" }
    ensure
      i.close
    end
  end

  def test_closed?
    f = StringIO.open(TESTSTR)
    assert(!f.closed?)
    f.close
    assert(f.closed?)

    io = StringIO.new("", "r+")
    assert(!io.closed?)
    io.close_read
    assert(!io.closed?)
    io.close_write
    assert(io.closed?)
  end

  def test_each
    count = 0
    StringIO.open(TESTSTR) do |file|
      file.each do |line|
        num = line[0..1].to_i
        assert_equal(count, num)
        count += 1
      end
      assert_equal(10, count)
    end

    count = 0
    StringIO.open(TESTSTR) do |file|
      file.each(nil) do |contents|
        contents.split(/\n/).each do |line|
          num = line[0..1].to_i
          assert_equal(count, num)
          count += 1
        end
      end
    end
    assert_equal(10, count)

    count = 0
    StringIO.open(TESTSTR) do |file|
      file.each(' ') do |thing|
        count += 1
      end
    end
    assert_equal(41, count)
  end

  def test_each_byte
    count = 0
    data = 
      "00: This is a line\n" +
      "01: This is a line\n" +
      "02: This is a line\n" +
      "03: This is a line\n" +
      "04: This is a line\n" +
      "05: This is a line\n" +
      "06: This is a line\n" +
      "07: This is a line\n" +
      "08: This is a line\n" +
      "09: This is a line\n" 

    StringIO.open(TESTSTR) do |file|
      file.each_byte do |b|
        assert_equal(data[count], b)
        count += 1
      end
    end
    assert_equal(SAMPLE.length*10, count)
  end

  def test_each_line
    count = 0
    StringIO.open(TESTSTR) do |file|
      file.each_line do |line|
        num = line[0..1].to_i
        assert_equal(count, num)
        count += 1
      end
      assert_equal(10, count)
    end

    count = 0
    StringIO.open(TESTSTR) do |file|
      file.each_line(nil) do |contents|
        contents.split(/\n/).each do |line|
          num = line[0..1].to_i
          assert_equal(count, num)
          count += 1
        end
      end
    end
    assert_equal(10, count)

    count = 0
    StringIO.open(TESTSTR) do |file|
      file.each_line(' ') do |thing|
        count += 1
      end
    end
    assert_equal(41, count)
  end

  def test_eof
    StringIO.open(TESTSTR) do |file|
      10.times do
        assert(!file.eof)
        assert(!file.eof?)
        file.gets
      end
      assert(file.eof)
      assert(file.eof?)
    end
  end

  def test_fcntl
  end

  def test_flush
  end

  def test_getc
    count = 0
    data = 
      "00: This is a line\n" +
      "01: This is a line\n" +
      "02: This is a line\n" +
      "03: This is a line\n" +
      "04: This is a line\n" +
      "05: This is a line\n" +
      "06: This is a line\n" +
      "07: This is a line\n" +
      "08: This is a line\n" +
      "09: This is a line\n" 
    
    StringIO.open(TESTSTR) do |file|
      while (ch = file.getc)
        assert_equal(data[count], ch)
        count += 1
      end
      assert_equal(nil, file.getc)
    end
    assert_equal(SAMPLE.length*10, count)
  end

  def test_gets
    count = 0
    StringIO.open(TESTSTR) do |file|
      while (line = file.gets)
        num = line[0..1].to_i
        assert_equal(count, num)
        count += 1
      end
      assert_equal(nil, file.gets)
      assert_equal(10, count)
    end

    count = 0
    StringIO.open(TESTSTR) do |file|
      while (contents = file.gets(nil))
        contents.split(/\n/).each do |line|
          num = line[0..1].to_i
          assert_equal(count, num)
          count += 1
        end
      end
    end
    assert_equal(10, count)

    count = 0
    StringIO.open(TESTSTR) do |file|
      while (thing = file.gets(' '))
        count += 1
      end
    end
    assert_equal(41, count)
  end

  def test_ioctl
  end

  # see tty?
  def test_isatty
    StringIO.open(TESTSTR) { |f|  assert(!f.isatty) }
  end

  def test_lineno
    count = 1
    StringIO.open(TESTSTR) do |file|
      while (line = file.gets)
        assert_equal(count, file.lineno)
        count += 1
      end
      assert_equal(11, count)
      file.rewind
      assert_equal(0, file.lineno)
    end

    count = 1
    StringIO.open(TESTSTR) do |file|
      while (line = file.gets('i'))
        assert_equal(count, file.lineno)
        count += 1
      end
      assert_equal(32, count)
    end
  end

  def test_lineno=
    StringIO.open(TESTSTR) do |f|
      assert_equal(0, f.lineno)
      assert_equal(123, f.lineno = 123)
      assert_equal(123, f.lineno)
      f.gets
      assert_equal(124, f.lineno)
      f.lineno = 0
      f.gets
      assert_equal(1, f.lineno)
      f.gets
      assert_equal(2, f.lineno)
    end
  end

  def test_pos
    pos = 0
    StringIO.open(TESTSTR, "rb") do |file|
      assert_equal(0, file.pos)
      while (line = file.gets)
        pos += line.length
        assert_equal(pos, file.pos)
      end
    end
  end

  def test_pos=
    nums = [ 5, 8, 0, 1, 0 ]

    StringIO.open(TESTSTR) do |file|
      file.pos = 999
      assert_nil(file.gets)
      assert_exception(Errno::EINVAL) {file.pos = -1}
      for pos in nums
        assert_equal(LINE_LENGTH*pos, file.pos = LINE_LENGTH*pos) # !!!
        line = file.gets
        assert_equal(pos, line[0..1].to_i)
      end
    end
  end

  def test_print
    StringIO.open(s = "", "w") do |file|
      file.print "hello"
      file.print 1,2
      $_ = "wombat\n"
      file.print
      $\ = ":"
      $, = ","
      file.print 3, 4
      file.print 5, 6
      $\ = nil
      file.print "\n"
      $, = nil
    end

    StringIO.open(s) do |file|
      content = file.gets(nil)
      assert_equal("hello12wombat\n3,4:5,6:\n", content)
    end

    StringIO.new(TESTSTR) {|io| assert_exception(IOError) {io.print "hello"}}
  end

  def test_printf
    # tested under Kernel.sprintf
  end

  def test_putc
    StringIO.open(s = "", "wb") do |file|
      file.putc "A"
      0.upto(255) { |ch| file.putc ch }
    end

    StringIO.open(s, "rb") do |file|
      assert_equal(?A, file.getc)
      0.upto(255) { |ch| assert_equal(ch, file.getc) }
    end

    StringIO.new(TESTSTR) {|io| assert_exception(IOError) {io.putc ?A}}
  end

  def test_puts
    StringIO.open(s = "", "w") do |file|
      file.puts "line 1", "line 2"
      file.puts [ Dummy.new, 4 ]
    end

    StringIO.open(s) do |file|
      assert_equal("line 1\n",  file.gets)
      assert_equal("line 2\n",  file.gets)
      assert_equal("dummy\n",   file.gets)
      assert_equal("4\n",       file.gets)
    end
  end

  def test_read
    StringIO.open(TESTSTR) do |file|
      content = file.read
      assert_equal(SAMPLE.length*10, content.length)
      count = 0
      content.split(/\n/).each do |line|
        num = line[0..1].to_i
        assert_equal(count, num)
        count += 1
      end
    end

    StringIO.open(TESTSTR) do |file|
      assert_equal("00: This is ", file.read(12))
      assert_equal("a line\n01: T", file.read(12))
    end
  end

  def test_readchar
    count = 0
    data = 
      "00: This is a line\n" +
      "01: This is a line\n" +
      "02: This is a line\n" +
      "03: This is a line\n" +
      "04: This is a line\n" +
      "05: This is a line\n" +
      "06: This is a line\n" +
      "07: This is a line\n" +
      "08: This is a line\n" +
      "09: This is a line\n" 
    
    StringIO.open(TESTSTR) do |file|
      190.times do |count|
        ch = file.readchar
        assert_equal(data[count], ch)
        count += 1
      end
      assert_exception(EOFError) { file.readchar }
    end
  end

  def test_readline
    count = 0
    StringIO.open(TESTSTR) do |file|
      10.times do |count|
        line = file.readline
        num = line[0..1].to_i
        assert_equal(count, num)
        count += 1
      end
      assert_exception(EOFError) { file.readline }
    end

    count = 0
    StringIO.open(TESTSTR) do |file|
      contents = file.readline(nil)
      contents.split(/\n/).each do |line|
        num = line[0..1].to_i
        assert_equal(count, num)
        count += 1
      end
      assert_exception(EOFError) { file.readline }
    end
    assert_equal(10, count)

    count = 0
    StringIO.open(TESTSTR) do |file|
      41.times do |count|
        thing = file.readline(' ')
        count += 1
      end
      assert_exception(EOFError) { file.readline }
    end
  end

  def test_readlines
    StringIO.open(TESTSTR) do |file|
      lines = file.readlines
      assert_equal(10, lines.size)
    end

    StringIO.open(TESTSTR) do |file|
      lines = file.readlines(nil)
      assert_equal(1, lines.size)
      assert_equal(SAMPLE.length*10, lines[0].size)
    end
  end

  def test_rewind
    f1 = StringIO.new(TESTSTR)
    assert_equal("00: This is a line\n", f1.gets)
    assert_equal("01: This is a line\n", f1.gets)
    f1.rewind
    assert_equal("00: This is a line\n", f1.gets)

    f1.readlines
    assert_nil(f1.gets)
    f1.rewind
    assert_equal("00: This is a line\n", f1.gets)

    f1.close
  end

  def test_seek
    nums = [ 5, 8, 0, 1, 0 ]

    StringIO.open(TESTSTR, "rb") do |file|
      file.seek(999, IO::SEEK_SET)
      assert_nil(file.gets)
      assert_exception(Errno::EINVAL) {file.seek(-1)}
      for pos in nums
        assert_equal(0, file.seek(LINE_LENGTH*pos))
        line = file.gets
        assert_equal(pos, line[0..1].to_i)
      end
    end

    nums = [5, -2, 4, -7, 0 ]
    StringIO.open(TESTSTR) do |file|
      count = -1
      file.seek(0)
      for pos in nums
        assert_equal(0, file.seek(LINE_LENGTH*pos, IO::SEEK_CUR))
        line = file.gets
        count = count + pos + 1
        assert_equal(count, line[0..1].to_i)
      end
    end

    nums = [ 5, 8, 1, 10, 1 ]

    StringIO.open(TESTSTR) do |file|
      file.seek(0)
      for pos in nums
        assert_equal(0, file.seek(-LINE_LENGTH*pos, IO::SEEK_END))
        line = file.gets
        assert_equal(10-pos, line[0..1].to_i)
      end
    end
  end

  def test_size
    StringIO.open(TESTSTR) do |file|
      assert_equal(10*LINE_LENGTH, file.size)
    end
  end

  def test_truncate
    StringIO.open(TESTSTR, "r") do |file|
      assert_exception(IOError) {file.truncate(0)}
    end
    StringIO.open(TESTSTR.dup) do |file|
      assert_exception(Errno::EINVAL) {file.truncate(-1)}
      file.truncate(5*LINE_LENGTH)
      assert_equal(5*LINE_LENGTH, file.size)
      assert_equal(5*LINE_LENGTH, file.read.size)
      assert_equal(5*LINE_LENGTH, file.string.size)
    end
  end

  def test_sysread
    StringIO.open(TESTSTR) do |file|
      assert_equal("", file.sysread(0))
      assert_equal("0", file.sysread(1))
      assert_equal("0:", file.sysread(2))
      assert_equal(" Thi", file.sysread(4))
      rest = file.sysread(100000)
      assert_equal(SAMPLE.length*10 - (1+2+4), rest.length)
      assert_exception(EOFError) { file.sysread(1) }
    end
  end

  def test_syswrite
    StringIO.open(s = "", "w") do |file|
      file.syswrite ""
      file.syswrite "hello"
      file.syswrite 1
      file.syswrite "\n"
    end

    StringIO.open(s) do |file|
      assert_equal("hello1\n", file.gets)
    end

    StringIO.new(TESTSTR) {|io| assert_exception(IOError) {io.syswrite "hello"}}
  end

  # see also pos
  def test_tell
    pos = 0
    StringIO.open(TESTSTR, "rb") do |file|
      assert_equal(0, file.tell)
      while (line = file.gets)
        pos += line.length
        assert_equal(pos, file.tell)
      end
    end
  end

  # see isatty
  def test_tty?
    StringIO.open(TESTSTR) { |f|  assert(!f.tty?) }
  end

  def test_ungetc
    assert_no_exception(IOError, Errno::EACCES) do
      StringIO.open(TESTSTR) do |file|
	c = file.getc
	assert_no_exception(IOError) {file.ungetc(c)}
	assert_equal(c, file.getc)
	assert_exception(IOError) {file.ungetc(c+1)}
	1 while file.getc
	assert_exception(IOError) {file.ungetc(?A)}
      end
    end

    StringIO.open(TESTSTR.dup) do |file|
      assert_equal(?0, file.getc)
      assert_equal(?0, file.getc)
      assert_equal(?:, file.getc)
      assert_equal(? , file.getc)
      assert_nil(file.ungetc(?:))
      assert_equal(?:, file.getc)
      1 while file.getc
      assert_nil(file.ungetc(?A))
      assert_equal(?A, file.getc)
    end
  end

  def test_write
    StringIO.open(s = "", "w") do |file|
      assert_equal(10, file.write('*' * 10))
      assert_equal(5,  file.write('!' * 5))
      assert_equal(0,  file.write(''))
      assert_equal(1,  file.write(1))
      assert_equal(3,  file.write(2.30000))
      assert_equal(1,  file.write("\n"))
    end
    
    StringIO.open(s) do |file|
      assert_equal("**********!!!!!12.3\n", file.gets)
    end

    StringIO.new(TESTSTR) {|io| assert_exception(IOError) {io.write "hello"}}
  end
end
