require 'runit/testcase'
require 'runit/cui/testrunner'
require 'prb/prb'
require 'prb/sign'
require 'prb/opt'

class PRbCoreTest < RUNIT::TestCase
  def transaction(&block)
    PRb.primary.transaction(&block)
  end

  def test_00_prb
    prb = PRb.primary
    assert_kind_of(PRb::Store, prb)
    assert_equal(prb, $prb)
    assert_equal(PRb::Store[prb.db_name], prb)
    assert_equal(PRb::Store[$prb.db_name], prb)
  end

  def test_01_attr
    root = $prb.root
    assert_kind_of(PRb::PRbRoot, root)
    assert_equal(root['empty'], nil)

    root[:foo] = nil
    root[:bigint] = nil

    begin
      transaction do
	root[:foo] = 'foo'
	root[:sym] = :symbol
	root[:hash] = {:sym => :Symbol, 'str' => "str"}
	root[:num] = 2
	root[:ary] = [3, 4, root]
	root[:float] = 12.3
	root[:bigint] = 123456789123456789123456789
	raise 'abort'
      end
    rescue
    end

    assert_equal(root[:foo], nil)
    assert_equal(root[:bigint], nil)

    transaction do
      root[:foo] = 'foo'
      root[:sym] = :symbol
      root[:hash] = {:sym => :Symbol, 'str' => "str"}
      root[:num] = 2
      root[:ary] = [3, 4, root]
      root[:float] = 12.3
      root[:bigint] = 123456789123456789123456789
    end

    transaction do
      assert_equal(root[:foo], root["foo"])
    end

    transaction do
      assert_equal(root[:foo], 'foo')
      assert_equal(root[:sym], :symbol)
      assert_kind_of(PRb::PRbAlist, root[:hash])
      ary = root[:ary]
      assert_equal(ary, root[:ary])
      assert_kind_of(PRb::PRbList, ary)
      assert_equal(ary.to_a, [3, 4, root])
      assert_equal(root[:float], 12.3)
      assert_equal(root[:bigint], 123456789123456789123456789)
    end

    begin
      transaction do
	root['bigint'] = nil
	raise 'abort'
      end
    rescue
    end

    transaction do
      assert_equal(root[:bigint], 123456789123456789123456789)
      root['bigint'] = nil
    end

    transaction do
      assert_equal(root[:bigint], nil)
    end
    
    $prb.gc

    transaction do
      assert_equal(root[:foo], 'foo')
      assert_equal(root[:sym], :symbol)
      assert_kind_of(PRb::PRbAlist, root[:hash])
      ary = root[:ary]
      assert_equal(ary, root[:ary])
      assert_kind_of(PRb::PRbList, ary)
      assert_equal(ary.to_a, [3, 4, root])
      assert_equal(root[:float], 12.3)
      assert_equal(root[:bigint], nil)
    end
  end

  def test_02_list
    root = $prb.root
    ary = nil

    transaction do
      root[:ary] = PRb::PRbList.knew
      ary = root[:ary]
      ary.push(1)
      assert_equal(ary.head, 1)
      assert_equal(ary.tail, 1)
    end

    transaction do
      root[:ary] = [1, 2, "3"]
      ary = root[:ary]
    end
    assert_kind_of(PRb::PRbList, ary)
    transaction do
      ary.push(4)
    end
    transaction do
      assert_equal(ary.length, 4)
      assert_equal(ary.to_a, [1, 2, "3", 4])
      assert_equal(ary.tail, 4)
      v = ary.pop
      assert_equal(v, 4)
      assert_equal(ary.length, 3)
      assert_equal(ary.to_a, [1, 2, "3"])
    end
    transaction do
      ary.unshift(0)
    end
    transaction do
      assert_equal(ary.size, 4)
      assert_equal(ary.to_a, [0, 1, 2, "3"])
      assert_equal(ary.head, 0)
      v = ary.shift
      assert_equal(v, 0)
    end
    transaction do
      ary.shift
      ary.shift
      ary.shift
      assert_equal(ary.size, 0)
      assert_equal(ary.to_a, [])
      ary.shift
    end
    transaction do
      ary.unshift(1)
      assert_equal(ary.size, 1)
      assert_equal(ary.to_a, [1])
    end
    transaction do
      100.times do
	ary.unshift(1)
      end
    end
    100.times do
      transaction do
	ary.unshift(1)
	assert_equal(ary.pop, 1)
      end
    end
  end
end

class PRbClassTest < RUNIT::TestCase
  class Item < PRb::PRbObject
    def self.alloc; $prb.alloc(self); end
    prb_attr :name, :ary
  end

  def transaction(&block)
    $pool.transaction(&block)
  end

  def test_01
    x = nil
    transaction do
      x = Item.alloc
      $prb.root.add(x)
      x.name = "seki"
      x.ary = []
    end

    transaction do
      assert_equal(x.name, "seki")
    end

    $prb.gc

    ary = nil
    transaction do
      ary = x.ary
      assert_kind_of(PRb::PRbList, ary)
      ary.push(1)
      ary.push("2")
      ary.push(3.to_s.intern)
      ary.push(x)
      ary.push(ary)
    end
    
    transaction do
      assert_equal(ary.to_a, [1, "2", '3'.intern, x, ary])
    end

    transaction do
      $prb.root.delete(x)
    end

    $prb.gc
  end

  def test_02_root
    x = transaction do
      x = PRb::PRbRoot.knew
      $prb.root.add(x)
      x
    end

    ary = []
    10.times do
      transaction do
	item = Item.knew
	x.add(item)
	ary.push(item)
	assert_equal(x.all_entry, ary)
      end
    end

    transaction do
      item = x.fetch(ary[4]._prb_ref)
      assert_equal(ary[4], item)
    end

    transaction do
      $prb.root.delete(x)
    end

    $prb.gc
  end
end

class PRbSignTest < RUNIT::TestCase
  def transaction(&block)
    $pool.transaction(&block)
  end

  def test_01
    s = nil
    transaction do
      s = PRbSign::GuestBook.knew
      $prb.root.add(s)
    end

    transaction do
      s.add_user('seki', 'seki00')
    end

    it = transaction do
      s.get_user('seki', 'seki00')
    end
    assert_kind_of(PRb::PRbAttr, it)

    transaction do
      assert_equal('seki', it[:name])
    end

    transaction do
      assert_exception(PRbSign::BadSign) do
	s.get_user('seki', 'seki01')
      end
    end

    transaction do
      s.signbook.set_sign('seki', 'seki00', 'seki01')
    end

    transaction do
      assert_equal(it, s.get_user('seki', 'seki01'))
    end

    transaction do
      $prb.root.delete(s)
    end

    $prb.gc
  end
end

if __FILE__ == $0
  
  n, db, user, pw = DBIPool::check_options(ARGV, {:db => 'DBI:Pg:prbut'})

  if /^DBI:Pg:(.+)$/ =~ db
    system("dropdb #{$1}")
    system("createdb #{$1}")
  end

  $pool = DBIPool.new(n, db, user, pw)
  $prb = PRb::Store.new($pool)

  $prb.setup_table

  RUNIT::CUI::TestRunner.run(PRbCoreTest.suite)
  RUNIT::CUI::TestRunner.run(PRbClassTest.suite)
  RUNIT::CUI::TestRunner.run(PRbSignTest.suite)
end
