#!/usr/bin/env ruby
require "xmlrpc/server"
require "rexml/document"

$bookmark_path = './'

s = XMLRPC::CGIServer.new

REXML::Attribute.class_eval( %q^
	def to_string
		%Q[#@expanded_name="#{to_s().gsub(/"/, '&quot;')}"]
	end
^ )

class Array
  def every(&block)
    arity = block.arity
    return self.each(&block) if arity <= 0

    i = 0

    while i < self.size
      yield(*self[i, arity])
      i += arity
    end
    self
  end
end

class Bookmark
  def initialize(uri, user, password)
    r = uri.rindex("/")
    if r == nil then
      @filename = uri
    else
      @filename = uri[r + 1, uri.length - r - 1]
    end
    @filename = $bookmark_path + @filename
    file = File.new @filename    
    if file then
      @doc = REXML::Document.new(file, { :compress_whitespace => :all})
      @root = @doc.root 
    else
      @doc = REXML::Document.new
      @doc.add(REXML::XMLDecl.new("1.0", "UTF-8"))
      @doc.add(REXML::Element.new('<xbel folded="no" version="1.0" xmlns="http://kazehakase.sourceforge.jp/2004">'))
    end
  end
  attr_accessor :filename, :root, :doc

  def get_bookmark(node, id)
    bookmark = nil
    node.each_element_with_attribute("id") do |item|
      if item.attributes["id"] == "#{id}" then
        bookmark = item 
        break
      end
      if item.has_elements? then
        bookmark = get_bookmark(item, id)
	if bookmark then
	  break
	end
      end
    end
    return bookmark if bookmark
  end
  
  def set_properties(bookmark, *props)
    props.every do |name, value|
      if name == "link" then
        bookmark.attributes["href"] = "#{value}"
      elsif name == "visited" then
        bookmark.attributes["visited"] = "#{value}"
      elsif name == "title" || name == "desc" then
        elm = bookmark.elements["#{name}"]
        if elm == nil then
	  elm = REXML::Element.new("#{name}")
	  bookmark.add_element(elm)
        end 
	elm.text="#{value}"
      elsif name == "update_interval" || name == "location" then
        info = bookmark.elements["info"]
        if info == nil then
	  info = REXML::Element.new("info")
	  bookmark.add_element(info)
        end
        elm = info.elements["metadata"]
        if elm == nil then
	  elm = REXML::Element.new("metadata")
	  info.add_element(elm)
	  elm.attributes["owner"] = "http://kazehakase.sourceforge.jp/"
        end
        elm.attributes["kz:#{name}"] = "#{value}"
      end
    end
  end

  def get_max_id(node, id)
    ret_id = id
    node.each_element_with_attribute("id") do |item|
      next if item.attributes["id"] == nil
      if item.attributes["id"].to_i > ret_id then
        ret_id = item.attributes["id"].to_i 
      end
      if item.has_elements? then
        ret_id = get_max_id(item, ret_id)
      end
    end
    return ret_id 
  end

  def create_id
    return get_max_id(root, 0)
  end
  
  def write
    file = File.open(@filename,"w")
    @doc.write(file, -1, false)
  end

end

class BookmarkRPC
  # Insert a new bookmark into the folder 
  #  uri :
  #  user :
  #  password :
  #  parent_id : ID of parent folder
  #  sibling_id :ID of the bookmark before which the new bookmark is inserted
  #  type : folder or bookmark or separator (not implemented yet)
  #  props : the properties of the new bookmark
  def insert(uri, user, password, parent_id, sibling_id, type, *props)
    return if type != "folder" && type != "bookmark" && type != "separator"
    b = Bookmark.new(uri, user, password)
    xbel = b.root 
    folder = b.get_bookmark(xbel, parent_id) 
    folder = b.root if folder == nil
    sibling = b.get_bookmark(xbel, sibling_id)
    id = b.create_id + 1

    bookmark = REXML::Element.new(type)
    folder.insert_before(sibling, bookmark)
    bookmark.attributes["id"] = "#{id}"
    b.set_properties(bookmark, *props)
    
    b.write
    "#{id}"
  end

  # Move the bookmark before the given position
  # not implemented yet
  #  uri :
  #  user :
  #  password :
  #  id : ID of the target bookmark
  #  parent_id : ID of parent folder
  #  sibling_id :ID of the bookmark before which the bookmark is inserted
  def move(uri, user, password, id, parent_id, sibling_id)
    b = Bookmark.new(uri, user, password)
    xbel = b.root 
    folder = b.get_bookmark(xbel, parent_id) 
    sibling = b.get_bookmark(xbel, sibling_id)
    bookmark = b.get_bookmark(xbel, id)
    return if bookmark == nil
    folder = b.root if folder == nil
    bookmark = bookmark.remove
    folder.insert_before(sibling, bookmark)

    b.write
    "#{id}"
  end
  
  # the bookmark(id) is removed.
  # Remove the bookmark 
  #  uri :
  #  user :
  #  password :
  #  id : the target bookmark
  def remove(uri, user, password, id)
    b = Bookmark.new(uri, user, password)
    xbel = b.root 
    bookmark = b.get_bookmark(xbel, id) 
    if bookmark
      bookmark.remove
      b.write
    end
    "#{id}"
  end
  
  # Set properties to the bookmark.
  #  uri :
  #  user :
  #  password :
  #  id : the target bookmark
  #  props : new properties of the bookmark 
  def update(uri, user, password, id, *props)
    b = Bookmark.new(uri, user, password)
    xbel = b.root 
    bookmark = b.get_bookmark(xbel, id) 
 
    if bookmark then
      b.set_properties(bookmark, *props)
      b.write
    else
      id = 0
    end
    "#{id}"
  end
end

# for test
#b = BookmarkRPC.new
#b.insert("http://localhost/~zoe/bookmarks", "zoe", "hoge", "6", "1", "folder", "title", "test","link", "hh", "location", "http://localhost", "update_interval", "10")
#b.update("bookmarks", "zoe", "hoge", "1", "title", "change","link", "hdddh")
#b.move("bookmarks", "zoe", "hoge", "9", "0", "6")

s.add_handler("bookmark", BookmarkRPC.new)
s.serve

