# Copyright (C) 2003-2005 Kouichirou Eto, All rights reserved.

$LOAD_PATH << "../../lib" unless $LOAD_PATH.include?("../../lib")
require "qwik/page"

class Hash
  def map_pair
    hash = {}
    self.each {|k, v|
      nk, nv = yield(k, v)
      hash[nk] = nv
    }
    hash
  end
end

module Qwik
  class WikiDB
    include Enumerable

    SIGNATURE = ["|", ",", ":"]
    DELIMITER = ","

    def initialize(page)
      @page = page
      @str = nil
      @array = []
      @hash = {}
      @rev_hash = {}
      check_new
    end

    def array
      check_new
      @array.dup # dup prevent from destructive method
    end

    def hash
      check_new
      @hash.dup # dup prevent from destructive method
    end

    def rev_hash
      check_new
      @rev_hash.dup # dup prevent from destructive method
    end

    def [](key)
      v = hash[key]
      return nil if v.nil?
      v.dup # dup prevent from destructive method
    end

    def exist?(key)
      self[key] != nil
    end

    def each
      hash.each {|k, v|
	k = k.dup # unfreeze
	v = v.dup # dup prevent from destructive method
	yield(k, v)
      }
    end

    def add(k, *ar)
      ar.flatten!
      @page.add(encode_line(k, ar)+"\n")
    end

    def remove(key)
      return false unless exist?(key) # return if there is not the key.
      status = false
      newar = []
      str = @page.load
      str.each {|line|
	firstchar = line[0, 1]
	if SIGNATURE.include?(firstchar)
	  ar = line.chomp.split(firstchar)
	  ar.shift # drop first column
	  k = ar.shift
	  if k && k == key # do not add the line with the key
	    status = true # ok.
	    next
	  end
	end
	newar << line
      }
      newstr = newar.join("")
#     @page.store(newstr, str.md5hex) # check if there is collision
      @page.store(newstr) # FIXME: Store this should check collision.
      status
    end

    private

    def check_new
      str = @page.load
      return if str == @str
      @str = str
      @array = []
      @str.each {|line|
	firstchar = line[0, 1]
	next unless SIGNATURE.include?(firstchar)
	ar = line.chomp.split(firstchar)
	ar.shift # drop the first column
	k = ar.shift
	if firstchar == ":"
	  v = ar.join(":")
	else
	  v = ar
	end
	v.freeze
	@array << [k, v]
      }

      @hash = to_hash(@array)
      @rev_hash = make_rev_hash(@hash)
      @hash
    end

    def to_hash(ar)
      ha = {}
      ar.each {|k, v| ha[k] = v }
      ha
    end

    def make_rev_hash(hash)
      hash.map_pair {|k, v|
	v = v.first if v.is_a? Array
	[v, k]
      }
    end

    def encode_line(k, *ar)
      ar.flatten!
      nar = ["", k]
      nar << ar unless ar == nil || ar.length == 0
      nar.flatten.join(DELIMITER)
    end
  end
end

if $0 == __FILE__
  require "qwik/test-common"
  $debug = true
end

if $debug
  class TC_WikiDB < Test::Unit::TestCase
    include TestSession

    def test_wikidb
      page = @site.create_new
      @wdb  = Qwik::WikiDB.new(page)

      Qwik::WikiDB.instance_eval {
	public :encode_line
      }

      # test_wikidb_private
      assert_equal(",k,v", @wdb.encode_line("k", "v"))
      assert_equal(",k,v1,v2", @wdb.encode_line("k", ["v1", "v2"]))
      assert_equal(",k,v1,v2", @wdb.encode_line("k", "v1", "v2"))
      assert_equal(",k,,v2", @wdb.encode_line("k", nil, "v2"))

      assert_equal(false, @wdb.exist?("k"))
      @wdb.add("k", "v")
      assert_equal(",k,v\n", page.load)

      assert_equal(true,  @wdb.exist?("k"))
      assert_equal(["v"], @wdb["k"])
      assert_equal("v", @wdb["k"][0])
      assert_equal(false, @wdb["k"][0].frozen?)

      assert_equal({"k"=>["v"]}, @wdb.hash)
      @wdb.add("k", "v2")
      assert_equal(["v2"], @wdb["k"]) # can get second value
      
      assert_equal(true,  @wdb.remove("k")) # remove
      assert_equal(false, @wdb.remove("k")) # fail to remove

      assert_equal(false, @wdb.exist?("k"))
      @wdb.add("k", "v1", "v2")
      assert_equal(true,  @wdb.exist?("k"))
      assert_equal(["v1", "v2"], @wdb["k"])
      assert_equal({"k"=>["v1", "v2"]}, @wdb.hash)
      assert_equal(true,  @wdb.remove("k")) # remove
      assert_equal(false, @wdb.remove("k")) # fail to remove

      @wdb.add("a", "b")
      @wdb.add("c", "d")
      assert_equal({"a"=>["b"], "c"=>["d"]}, @wdb.hash)

      @wdb.each {|k, v|
	assert_equal(false, k.frozen?)
	assert_instance_of(Array, v)
      }

      assert_equal([["a", ["b"]], ["c", ["d"]]], @wdb.array)
      @wdb.array.each {|name, args|
	args = args.dup
	t = args.shift
      }
      assert_equal([["a", ["b"]], ["c", ["d"]]], @wdb.array)

      # test_nil
      page = @site.create_new
      @wdb = Qwik::WikiDB.new(page)
      assert_equal(false, @wdb.exist?("k"))
      @wdb.add("k", nil, "v2")
      assert_equal(",k,,v2\n",  page.load)
      assert_equal(["", "v2"], @wdb["k"])
    end
  end
end
