#-------------------------------------------
# OpenurlQuerySyntaxErrorクラス
# 文法上の誤りをエラーとする
#-------------------------------------------
class OpenurlQuerySyntaxError < RuntimeError; end

#-------------------------------------------
# Openurlクラス
#-------------------------------------------
class Openurl
  # 一致条件ごとの分類
  MATCH_EXACT = [ :ndl_dpid ] # 完全一致
  MATCH_PART = [ :aulast, :aufirst, :au, :title, :atitle, :jtitle, :btitle, :pub ] # 部分一致
  MATCH_AHEAD = [ :issn, :isbn, :ndl_jpno ] # 前方一致

  # ある項目に対して複数語指定時の検索方法ごとの分類
  LOGIC_MULTI_AND = [ :au, :title, :atitle, :jtitle, :btitle, :pub ] # AND検索
  LOGIC_MULTI_OR = [ :ndl_dpid ] # OR検索

  # 桁チェックが必要な項目
  NUM_CHECK = { issn: 8, isbn: 13 }

  # 集約される項目
  SYNONYMS = [ :title, :aulast, :aufirst ]

  # enjuのフィールド名（検索用）管理
  ENJU_FIELD = { aulast: "au_text", # aulast=au
                aufirst: "au_text", # aufirst=au
                au: "au_text",
                title: "btitle_text",  # title=btitle
                atitle: "atitle_text",
                btitle: "btitle_text",
                jtitle: "jtitle_text",
                pub: "publisher_text",
                issn: "issn_sm",
                isbn: "isbn_sm",
                ndl_jpno: "ndl_jpno_text", # TODO: 現在対応項目はないので保留。
                ndl_dpid: "ndl_dpid_sm",   # TODO: 現在対応項目はないので保留。これのみ完全一致であることに注意。
                associate: ""              # TODO: フィールド名ではないので削除？
  }

  def initialize(params)
    # @openurl_queryに検索項目ごとの検索文を格納
    if params.key?(:any)
      # anyの場合は他に条件が指定されていても無視
      @openurl_query = [ params[:any].strip ]
    else
      @openurl_query = to_sunspot(params)
    end
    @manifestations = []
  end

  attr_reader :openurl_query, :manifestations, :query_text

  # 検索
  def search
    search = Sunspot.new_search(Manifestation)
    role = Role.default
    @query_text = build_query
    query = @query_text
    search.build do
      fulltext query
      with(:required_role_id).less_than_or_equal_to role.id
    end
    search.execute!.results
  end

  private
  # 検索文を結合,
  def build_query
    @openurl_query.join(" AND ")
  end

  # params OpenURLのリクエストのパラメータ
  # 個々のパラメータに合わせて検索文を組立てメソッドを呼ぶ。この時、引数は、enjuのフィールド名と値。
  def to_sunspot(params)
    query = []
    params.each_pair do |key, _value|
      key = key.to_sym
      if MATCH_EXACT.include?(key) # 完全一致
        query << to_sunspot_match_exact(ENJU_FIELD[key], params[key].strip)
      elsif MATCH_PART.include?(key) # 部分一致
        query << to_sunspot_match_part(key, ENJU_FIELD[key], params[key].strip)
      elsif MATCH_AHEAD.include?(key) # 前方一致
        query << to_sunspot_match_ahead(key, ENJU_FIELD[key], params[key].strip)
      end
    end
    # queryにあるau_textの統合
    @openurl_query = unite_au_query(query)
  end

  # 完全一致の項目の検索文組立て
  # TODO 完全一致の項目はndl_dpidだけで、これはデータプロバイダについて議論中なので保留する
  def to_sunspot_match_exact(field, val)
    # 途中に空白がある場合は複数語が指定されているとみなし、ORでつなぐ。
    if /\s+/ =~ val
      "%s:%s" % [ field, val.gsub(/\s+/, " OR ") ]
    else
      "%s:%s" % [ field, val ]
    end
  end

  # 部分一致の項目の検索文組立て
  # key   パラメータのキー（複数指定の検索方法判断に使う）
  # field 項目名
  # val   値
  def to_sunspot_match_part(key, field, val)
    # 途中に空白がある場合は複数語が指定されているとみなし、ANDでつなぐ。
    if /\s+/ =~ val
      if LOGIC_MULTI_AND.include?(key)
        "%s:(%s)" % [ field, val.gsub(/\s+/, " AND ") ]
      else
        raise OpenurlQuerySyntaxError, "the key \"#{key}\" not allow multi words"
      end
    else
      "%s:%s" % [ field, val ]  # fieldに対して語が１つならばこれ
    end
  end

  # 前方一致の項目の検索文組立て
  def to_sunspot_match_ahead(key, field, val)
    # 既定の桁より大きい場合、または、数値でない文字列の場合エラー
    # 数値でない文字列には空白がある場合も含むので複数指定はエラーとなる
    # このチェックはANY検索の時外す
    if NUM_CHECK.include?(key)
      if [ :issn, :isbn ].include?(key.to_sym)
        val.gsub!("-", "")
      end
      raise OpenurlQuerySyntaxError unless /\A\d{1,#{NUM_CHECK[key]}}X?\Z/i =~ val
    end
    "%s:%s*" % [ field, val ]
  end

  # au項目の統合
  # au、aufirst、aulastはau_textに統合する
  def unite_au_query(query)
    new_query = []
    au_item = []
    str = "au_text:"
    au_flg = false
    reg = Regexp.compile(/\A#{str}/)
    query.each do |q|
      if reg =~ q
        au_flg = true
        au_item.push(q[str.length..q.length])
      else
        new_query.push(q)
      end
    end
    if au_flg
      if au_item.size > 1
        str = str + "(%s)" % [ au_item.join(" AND ") ]
      else
        str += au_item[0]
      end
      new_query.push(str)
    end

    new_query
  end
end
