# Rubyjiro
# Eijiro Scraping Gateway

# パラメータ:
#   -w="[word]"   string  検索語（必須、URLエンコードしたもの）
#   -s=[start]    number  何件目からを表示するか（必須）
#   -n=[count]    number  何件の結果を返すか（必須）
# アウトプット (hashをjsonでserializeしたもの):
#   'success'     boolean 検索が成功したかどうか
#   'error'       string  successがfalseなら、その理由
#   'total'       number  検索結果の数
#   'variations'  array   変化形集 (stringのarray)
#   'spellings'   array   もしかして集 (stringのarray)
#   'idioms'      array   関連Idiom集 (hashのarray)
#     'title'       string    見出し語
#     'keyword'     string    検索語
#   'defs'        array   結果集 (hashのarray)
#     'title'       string    見出し語
#     'body'        string    内容文

require 'open-uri'
require 'rubygems'
require 'nokogiri'
require 'kconv'
require 'cgi'
require 'json'

######################################################################
# RubyjiroSearchクラス
######################################################################
class RubyjiroSearch
  attr_reader :result
  
  # コンストラクタ
  def initialize(w, s, n)
    # コンフィグ
    @original_perpage = 50
    @url_prefix = "http://eow.alc.co.jp/"
    @url_postfix = "/UTF-8/"
    # パラメータ
    @word = w
    @start = s
    @number = n
    # 結果のハッシュを作っておく
    @result = {}
  end
  
  #####################################
  # 検索を実行する
  def run
    if !@word || !@start || !@number
      @result[:success] = false
      @result[:error] = "Insufficient parameters."
      return
    end
    
    # コネクションをテストするついでに最初のページを取得
    @sampledoc = Nokogiri::HTML(open(get_url(1)))
    if !@sampledoc
      @result[:success] = false
      @result[:error] = "Could not establish connection."
      return
    end
    
    # 件数から必要なページを判断
    page_numbers = get_page_numbers([], @start, @number)
    
    # 本家の検索結果は何件か
    @result['total'] = get_total_count
    # イディオムを取得
    @result['idioms'] = get_idioms
    # 変化形を取得
    @result['variations'] = get_variations
    # スペリング候補を取得
    @result['spellings'] = get_spellings
    
    # それぞれのページから結果を取得
    @result['defs'] = []
    page_numbers.each do |page|
      if page[:page] <= @result['total']/@original_perpage+1
        @result['defs'] += get_definitions(get_url(page[:page]), page[:start], page[:number])
      end
    end
    
    @result[:success] = true
  end
  
  #########################################
  # 英辞郎のどのページから何件取るかを算出
  def get_page_numbers(urls, start, number, page=nil)
    page = ((start-1)/@original_perpage)+1 if page==nil
    urls.push({
      :page=>page,
      :start=>start,
      :number=>[@original_perpage,number].min,
    })
    
    number-=@original_perpage-start+1
    if (number>0)
      urls = get_page_numbers(urls, 1, number, page+1)
    end
    urls
  end
  
  #######################################
  # ページ番号から英辞郎URLを作る
  def get_url(page)
    w = CGI.escape(@word)
    "#{@url_prefix}#{w}#{@url_postfix}?pg=#{page}"
  end
  
  ########################################
  # ある検索語での本家の検索結果数を返す
  def get_total_count
    @sampledoc.css("#itemsNumber").search("strong").inner_text.to_i
  end
  
  #########################################
  # 英辞郎Webページからデフィニションを取得する
  def get_definitions(url, start, number)
    defs = []
    doc = Nokogiri::HTML(open(url))
    entries = doc.search('#resultList')[0].children[0].children.to_a.slice(start-1...start-1+number)
    entries.each do |e|
      d = {}
      d['title'] = e.search('span.midashi').inner_text
      e.search('span.midashi').unlink
      e.search('span.kana').unlink
      d['body'] = e.search('div')[0].inner_html
      defs.push(d)
    end
    return defs
  end
  
  ########################################
  # 英辞郎Webページからイディオムを取得
  def get_idioms
    idioms = []
    baseelem = @sampledoc.search("div.sas")[0]
    if baseelem.search("#idiom2")[0]
      baseelem = baseelem.search("#idiom2")[0];
    end
    links = baseelem.search("a")
    links.each do |link|
      if link['target']=="eow_idm"
        i={}
        i['title'] = (link.inner_text)
        link['href']=~/#{@url_prefix}(.*)#{@url_postfix}/
        i['keyword'] = CGI.unescape($1)
        idioms.push(i)
      end
    end
    idioms
  end
  
  #########################################
  # 英辞郎Webページから変化形を取得
  def get_variations
    vars = []
    links = @sampledoc.search("div.sas")[0].search("a")
    links.each do |link|
      if link['href']=~/goGradable/
        vars.push(link.inner_text)
      end
    end
    vars
  end
  
  #########################################
  # 英辞郎Webページからスペリング修正候補を取得
  def get_spellings
    spellings = []
    links = @sampledoc.search("div.sas")[0].search("a")
    links.each do |link|
      if link['href']=~/goFairWord/
        spellings.push(link.inner_text)
      end
    end
    spellings
  end
  
end


######################################################################
# ユーティリティメソッド
######################################################################
# 引数をパースして:word, :start, :numberからなるハッシュを返す
def parse_params(args)
  params = {}
  args.each do |i|
    if i=~/^-w=(.*)/
      params[:word] = $1
    elsif i=~/^-s=(.*)/
      params[:start] = $1.to_i
    elsif i=~/^-n=(.*)/
      params[:number] = $1.to_i
    end
  end
  params
end


################################################################
# 実行部
################################################################
params = parse_params(ARGV);
s = RubyjiroSearch.new(CGI.unescape(params[:word]), params[:start], params[:number])
s.run
puts s.result.to_json
