#! /usr/bin/env ruby

require 'open-uri'
require 'optparse'
require 'yaml'

require 'date'
require 'net/http'
require 'uri'

$fossil_dir="#{ENV["HOME"]}/.fossil"
$local_path="#{$fossil_dir}/_dict/"
$conf_file="#{$fossil_dir}/fossil.conf"
$urllist_filename = "urllist"
$descr_filename = "description"
$register_path = "fossil.php"
$register_count = 2 # Number of register destination
$maxurllist = 100 # FIXME: magic no. orz

$tmp_file="#{$fossil_dir}/tmp-tmp"
$input_file="#{$fossil_dir}/input.txt"
$input_history_file="#{$fossil_dir}/input_history.txt"
$fossil_php="/usr/local/share/fossil.php" # FIXME. Hard coding...

$backend="fossil-backend";

class URLManager
  class EntryInfo
    attr_accessor :count, :url, :user, :status, :filter, :descr
  end

  def initialize
    @entry_list = Array.new()

    local_list = $local_path + $urllist_filename

    # if url list is not exist, create it.
    unless File.exist?(local_list)
      f = open(local_list,"w")
      f.chmod(0666) # becomes volunability?
      e = EntryInfo.new
      e.url    = "http://anthy.sourceforge.jp/_dict/yusuke/"
      e.user   = "yusuke"
      e.status   = "ok"
      e.filter = "ok"
      e.descr   = "default fossil url"
      @entry_list << e
    else
      read_list(local_list)
    end

    # Read urllist from publish point
    c = read_conf
    unless c['url'] == nil or c['url'].empty? == true
      read_list(c['url']+$urllist_filename)
    end
  end

  def update_list
    count = 0
    done_list = Array.new
    while (count < $maxurllist)
      count +=1 # ensure this loop ends
      break if done_list.size >= @entry_list.size
      i = rand(@entry_list.size)
      # skip Errored url
      next if @entry_list[i].status == "removed" 
      # skip filterd url
      next if @entry_list[i].status == "ng" 
      # skip already got url
      next unless find_entry_from_url(@entry_list[i].url, done_list) == nil

      get_list = read_list(@entry_list[i].url + $urllist_filename)
      if get_list == false 
        @entry_list[i].status = "removed" 
        next
      end
      
      d = DateTime.now()
      @entry_list[i].status = d.strftime('%Y-%m-%d')
      done_list << @entry_list[i]

      # marge urllist local and remote
      get_list.each do |one|
        next unless find_entry_from_url(one.url) == nil
        puts "    add: #{one.url} "
        @entry_list << one
      end
    end

    # write to local file 
    write_list
    write_descr
  end

  def write_list
    return nil if @entry_list.empty?
    f = open($local_path + $urllist_filename, "w")
    f.puts "#v1 index, url, username, status, filter" # first comment line
    c = 0
    @entry_list.each do |one|
      line = "#{c}, #{one.url}, #{one.user}, #{one.status}, #{one.filter}"
      f.puts line
      c += 1
    end
    f.close
  end

  def write_descr
    f = open($local_path + $descr_filename, "w")
    c = 0
    @entry_list.each do |one|
      line = "#{c} #{one.descr}"
      f.puts line
      c += 1
    end
    f.close
  end

  def read_list path
    path.gsub!(" ","")
    return if path.include?("www.example.com")
    puts "Reading urllist from #{path}"
    f = nil
    get_list = Array.new
    begin
      # FIXME: Why open_uri doesn't handle File and URI?
      unless path =~ /^http/
        f = File.open(path)
      else
        f = open(path)
      end
    rescue OpenURI::HTTPError
      puts "HTTPError: #{$!} / #{path}"
      return false
    rescue Errno::ENOENT
      puts "ENOENT: #{$!}"
      return false
    end

    while f.gets
      # skip comment or blank line
      next if ($_ =~ /^#/) or ($_.size < 2) 

      buf = split(",")
      next unless find_entry_from_url(buf[0]) == nil # already have
      e = EntryInfo.new
      e.url    = buf[1].gsub!(" ","")
      e.user   = buf[2].gsub!(" ","")
      e.status = buf[3].gsub!(" ","")
      e.filter = buf[4].gsub!(" ","")
#      next if e.status.include?("removed") 
      next unless find_entry_from_url(e.url) == nil
      
      @entry_list << e
      get_list << e
    end
    f.close
    return get_list
  end

  def read_descr url
    path = url + $descr_filename 
    descr = ""
    begin
      f = open(path)
    rescue OpenURI::HTTPError
      puts "HTTPError: #{$!}/#{path}"
      return false
    end
    while f.gets
      descr << $_
    end
    return descr
  end

  def register_url(url)
    done_list = Array.new

    $register_count.times do |t|
      i = rand(@entry_list.size)
      next if @entry_list[i].status == "removed" 
      next unless find_entry_from_url(@entry_list[i].url, done_list) == nil

      register_url = @entry_list[i].url + $register_path
      if send_url(url, register_url) == false
        puts "Could not register to #{register_url}. Try another."
        next
      end
      done_list << @entry_list[i]
    end
  end

  def send_url(url, targeturl)
    t = URI.parse(targeturl)
    return false unless t.scheme == "http"
    response = nil
    puts "Register at #{targeturl}"
    Net::HTTP.version_1_2
    Net::HTTP.start(t.host, 80) { |http|
      response = http.post(t.path,
                           'cmd=addurl&url=#{url}&username=#{ENV["USER"]}')
    }
    if response.body.include?("Your url was accepted:#{url}")
      return false
    end
    return true
  end

  def add_list url
    unless url =~ /^http:\/\//
      return false
    end

    register_url(url)

    e = EntryInfo.new
    e.url    = url
    e.user   = ENV["USER"]
    e.status = "ok"
    e.filter = "ok"
    @entry_list << e
  end

  def get_urllist
    return @entry_list                  
  end

  def find_entry_from_url(url, list=@entry_list)
    list.each do |one|
      return one if one.url == url
    end
    return nil
  end
  def find_entry_from_user(user, list=@entry_list)
    list.each do |one|
      return one if one.user == user
    end
    return nil
  end

  def read_conf(conf_file = $conf_file)
    unless File.exist? conf_file
      puts "Could not open config file:#{conf_file}"
      exit(1)
    end
    return YAML.load_file(conf_file)
  end
  
end

#-----------------------------------------------------------------------#
# End of url manager Class                                              #
#-----------------------------------------------------------------------#

#
# dump
#  dump repository
# import
#  import canna format dictionary
# clone/pull
#  get contents from remote repository
#

def dump()
  r = system($backend, "--dump");
  if (!r)
    print "Command Failed\n";
  end
end

def do_merge(target_uri, path)
  file_name = target_uri+"/words/"+ path;
  print file_name + "\n"
  open(file_name) do |f|
    words = f.read
    wf = File.open($tmp_file, "w");
    wf.write(words);
    wf.close;
    
    return system($backend, "--encoding", "utf8", "--read", "tmp-tmp")
  end
end

def do_read(src_file)
  open(src_file) {|f|
    words = f.read
    wf = File.open("tmp-tmp", "w")
    wf.write(words)
    wf.close
  }
  return  system($backend, "--read", "tmp-tmp")
end

def import_line(line, df, name)
  arr = line.split
  idx = arr.shift
  cur_wt = "#T35"
  cur_freq = 0
  order = 0;
  arr.each{|x|
    if ((x =~ /^\#/))
      tmp = x.split('*')
      cur_wt = tmp[0];
      if (tmp.length == 2)
        cur_freq = tmp[1]
      else
        cur_freq = 0
      end
    else
      order = order + 1
      #print idx + ":" + cur_wt + ":" + x + "\n"
      attr = "SRC,"+cur_wt
      attr = attr+ ","+String(cur_freq)
      attr = attr +","+String(order)+","+name
      df.write(idx + " " + x + " ? ? "+attr+" ok\n")
    end
  }
end

def do_import(src_file)
  sf = File::open(src_file)
  df = File::open("tmp-tmp", "w")
  sf.each_line{|x|
    import_line(x, df, src_file)
  }
  system($backend, "--encoding", "euc-jp", "--read", "tmp-tmp")
  system($backend, "--encoding", "utf8", "--update-index");
end

def do_clone(target_uri)
  r = true;
  open(target_uri +"/index") do |f|
    index = f.read
    index.each_line{|s|
      if (s =~ /(\w+) (\w+) (\w+)/)
        if (Integer($2) > 0)
          r = do_merge(target_uri, $1);
          if (!r)
            print "Command Failed\n";
          end
        end
      end
    }
  end
  system($backend, "--encoding", "utf8", "--update-index");
end

def read_conf(conf_file = $conf_file)
  unless File.exist? conf_file
    puts "Could not open config file:#{conf_file}"
    exit(1)
  end
  return YAML.load_file(conf_file)
end


########################################################################
## Main part

def usage(opt)
  puts opt.to_s
  exit
end

def initialize_inputfile
  f = open($input_file,"w")
  f.puts <<INP
# 読み[スペース]漢字 (UTF-8で入力してください)
INP
  f.close
end

def initialize_dir
  Dir::mkdir($fossil_dir, 0700)
  Dir::mkdir($fossil_dir+"/_dict", 0700)
  File::new($tmp_file,"w")
  File::new($input_history_file,"w")
  if File.exist?($fossil_php)
    open($fossil_php) do |s| # copy fossil.php to ~/.fossil/_dict/
      open($fossil_dir+"/_dict/fossil.php", "w") do |d|
        d.write(s.read)
      end
    end
  else
    puts "ERROR: Could not find fossil.php file in #{$fossil_php}."
    puts "please copy from src dir to #{$fossil_dir}/_dict/ ."
  end
  f = open($conf_file,"w")
  f.puts <<CON
---
user_name: somebody
hostname: hostname.example.com
path: public_html/_dict/ 
url: http://www.example.com/~somebody/_dict/  # last '/' is required
CON
  f.close
  initialize_inputfile

  u = URLManager.new # create urllist
  u.write_list
  u.write_descr
  
  puts "Please change config file to your environment and try again."
  puts "    Config file path: #{$conf_file}"
  exit(1)
end

initialize_dir unless FileTest.exist?($fossil_dir)

opt = OptionParser.new(nil,20," ")

opt.on('--pull [url]', 'read word list from url list or specified url') do |v|
  target_uri = ARGV.shift
  if (target_uri)
    do_clone(target_uri)
  else
    u = URLManager.new
    list = u.get_urllist
    list.each do |entry|
      if entry.status == "removed" or entry.filter == "ng"
        next
      end
      do_clone(entry.url)
    end
  end
end
opt.on('--dump', 'dump local word list to stdout') do |v| 
  dump
end
opt.on('--read [file]', 'read word list from file (default: ~/.fossil/input.txt)') do |v| 
  if v == nil
    src_file = $input_file
  else
    src_file = v
  end

  puts "Read words from #{src_file}"
  r = do_read(src_file)
  if (!r)
    print "Command Failed\n";
  end
  system($backend, "--encoding", "utf8", "--update-index")

  # move to imported word list
  src = open(src_file,"r")
  dst = open($input_history_file,"a")
  while src.gets
    dst.puts $_
  end
  src.close
  dst.close

  # if read from default, delete all of old input words
  if v  == nil
    File.delete($input_file)
    initialize_inputfile
  end
end
opt.on('--import [file]','import canna format dic file') do |v| 
  if v == nil
    puts "ERROR: Please spesify the canna format dic file."
  else
    do_import(v)
  end
end
opt.on('--export','export word list(not implemented yet)') do |v|
  puts "ERROR : Sorry, not implemented yet."
  exit 1
end
opt.on('--publish', 'publish your dictionary') do |v| 
  c = read_conf
  unless c['user_name'] == nil or c['user_name'].empty?
    c['user_name'] += "@"
  end

  publish_point = "#{c['user_name']}#{c['hostname']}:#{c['path']}"
  puts "Publish to #{publish_point} using rsync..."
  system "rsync -rpLv --delete ~/.fossil/_dict/* #{publish_point}"
end

opt.on('--update-url', 'update resource url list') do |v| 
  u = URLManager.new  
  u.update_list
  u.write_list
  u.write_descr
end
opt.on('--add-url [url]', 'add resource URL list') do |v| 
  if v == nil
    puts "ERROR: Please spesify resource URL that you want to add."
    exit(1);
  else
    u = URLManager.new
    status = u.add_list(v.gsub(" ",""))
    if status == false
      puts "ERROR: Could not add resource url : #{v}"
    else
      u.write_list
      u.write_descr
    end
  end
end

usage(opt) if ARGV.empty?
opt.parse!(ARGV)
