# prime/prime.rb
# $Id: prime.rb,v 1.18 2005/03/07 07:51:32 komatsu Exp $
#
# Copyright (C) 2002, 2003, 2004 Hiroyuki Komatsu <komatsu@taiyaki.org>
#     All rights reserved.
#     This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of 
# the GNU General Public License version 2.
#

require 'prime/prime-config'
require 'prime/taiyaki'
require 'prime/prime2'
require 'prime/prime08'

PRIME_CONFIGFILE_GLOBAL = File::join2(PRIME_CONFIG_DIR,  "Custom_prime.rb")
PRIME_CONFIGFILE_LOCAL  = (ENV['PRIME_CONFIG'] or 
                             File::join2(PRIME_USER_DIR, "Custom_prime.rb"))

class Prime
  include Debug
  include Prime2
  include Prime08

  def initialize (engine_files = nil)
    @debug_mode = false
    Dir::ensure(PRIME_USER_DIR)
    initialize_rc()

    initialize_prime2()
    initialize_prime08()
  end

  def initialize_rc
    if File::exist?(PRIME_CONFIGFILE_GLOBAL) == false then
      $stderr.puts "PRIME-ERROR:"
      $stderr.puts "  The file Custom_prime.rb is not found."
      $stderr.puts "  You might need to install Custom_prime.rb by"
      $stderr.puts "  'make install-etc'.  Sorry for your inconvenience."
      Kernel::exit()
    end

    load(PRIME_CONFIGFILE_GLOBAL)
    if FileTest::exist?(PRIME_CONFIGFILE_LOCAL) then
      load(PRIME_CONFIGFILE_LOCAL)
    else
      Dir::ensure(File::dirname(PRIME_CONFIGFILE_LOCAL))
      `cp #{PRIME_CONFIGFILE_GLOBAL} #{PRIME_CONFIGFILE_LOCAL}`
    end

    case PRIME_ENV['typing_method']
    when 'tcode' then
      ## FIXME: Is it necessary?
      ## FIXME: <komatsu@taiyaki.org> (2004-06-25)
      if PRIME_ENV.has_key?('style_mask_pending_chars') == false then
        PRIME_ENV['style_mask_pending_chars'] = true
      end
    when 'english' then
      PRIME_ENV['style_auto_space'] = true
      PRIME_ENV['engines'] = [
        :PrimeEngineEnglish,
        :PrimeEngineUserdict2English,
        :PrimeEnginePersonalDict,
        :PrimeEngineAlphabet,
        :PrimeEngineNumber,
      ]
    end
  end

  def get_env (key)
    return PRIME_ENV[key]
  end

  def check_existence (pron, pos, literal)
    results = @engines.command(:check_existence, pron, literal, pos)
    results.each {|result|
      return true if result
    }
    return false
  end

  public
  ## This returns an avairable prefix string for the literal.
  ## ex). get_prefix("This", "is") => " "
  def Prime::get_prefix (context, literal)
    if PRIME_ENV['style_auto_space'] and context and context != "" then
      if (context[-1] > 128 and literal[0] > 128) or
          (literal[0] < ?a and literal[0] > ?z and
             literal[0] < ?A and literal[0] > ?Z) or
          (context =~ /[(¡Ö¡¢¡£¡Ø¡Ú¡Ò¡Ô¡Ê¡Ì¡Î¡Ð¡È¡Æ]$/) then
        return ""
      else
        return " "
      end
    else
      return ""
    end
  end
end


class PrimeQuery
  attr_accessor :input, :pos, :method, :context
  def initialize(input = [], pos = nil, method = :exact, context = nil)
    @input   = input
    @pos     = pos
    # method = {:prefix, :exact, :literal_prefix, :literal_exact,:overall, :context}
    @method  = method 
    @context = context
  end
end

## PrimeWord contains data of a word generated by a user's query.
class PrimeWord
  attr_reader   :pron, :literal, :pos, :index
  attr_accessor :data, :score, 
      :conjugation, :conjugation_pos, :rest, :prefix
  def initialize(pron, literal, pos, score, data = {})
    @pron        = (pron    or "")
    @literal     = (literal or "")
    @pos         = (pos     or "Ì¤ÃÎ¸ì")
    @score       = score.to_i
    @data        = data
    @conjugation = ""
    @rest        = ""
    @prefix      = ""
#    @label       = [pron, literal].join("\t")
    @index       = [pron, literal, pos].join("\t")
  end

  def values
    return [@pron, @literal, @pos, @score, @data]
  end

  def label ()
    return (to_text_pron + "\t" + to_text_literal)
  end

  def to_text_literal
    return (@prefix + @literal + @conjugation + @rest)
  end
  def to_text_pron
    return (@pron + @conjugation + @rest)
  end

  ## This returns a string data of this word for sending to clients.
  def to_text
    data_list = [
      to_text_pron(),
      to_text_literal(),
      "priority=#{@score}",
      "part=#{@pos}",
      "base=#{@literal}",
      "basekey=#{@pron}",
      ( "conjugation=#{@conjugation}"  unless @conjugation.empty?() ),
      ( "suffix=#{@rest}"              unless @rest.empty?()        ),
      ( "form=#{@data['form']}"        if @data.has_key?('form')    ),
      ( "usage=#{@data['usage']}"      if @data.has_key?('usage')   ),
      ( "comment=#{@data['comment']}"  if @data.has_key?('comment') ),
    ]
    return data_list.compact.join("\t")
  end

  ## This is for the PRIME2 protocol.
  def to_text2
    data_list = [
      to_text_literal(),
      ( "form=#{@data['form']}"        if @data.has_key?('form')    ),
      ( "usage=#{@data['usage']}"      if @data.has_key?('usage')   ),
      ( "comment=#{@data['comment']}"  if @data.has_key?('comment') ),
    ]
    return data_list.compact.join("\t")
  end
end

class PrimeWordList < Array
  def PrimeWordList::concat(*words)
    ## This method does not sort the given words.
    result = words.flatten.compact

    mark = {}
    merged = PrimeWordList.new
    result.each {|word|
      if mark[word.index] then
        word0 = mark[word.index]
        ## FIXME: Make this merging more intelligence.
        ## FIXME: <komatsu@taiyaki.org> (2004-01-24)
        word.data.keys().each { | key |
          if word0.data.has_key?(key) then
            # Do nothing yet
          else
            word0.data[key] = word.data[key]
          end
        }
      else
	mark[word.index] = word
	merged.push(word)
      end
    }
    return merged
  end

  def PrimeWordList::sort (*words)
    result = words.flatten.compact.sort {|word1, word2| 
      (word2.score != word1.score) ? (word2.score <=> word1.score) :
                                     (word1.pron.length <=> word2.pron.length)
    }
    return result
  end

  def PrimeWordList::merge2 (words1, words2)
    words2 = PrimeWordList::merge_by_literal(nil, *words2)
    if words1.empty? then
      return words2
    end

    words1 = PrimeWordList::merge_by_literal(nil, *words1)
    upper_words = words1.map { | word | word.to_text_literal() }

    words2.each { | word |
      unless upper_words.member?( word.to_text_literal() ) then
        words1.push( word )
      end
    }
    return words1
  end

  def PrimeWordList::merge (*words)
    return PrimeWordList::merge_internal(:index, nil, words)
  end

  ## FIXME: Change the name of method.
  ## FIXME: <komatsu@taiyaki.org> (2004-01-26)
  def PrimeWordList::merge_with_label (context, *words)
    return PrimeWordList::merge_internal(:label, context, words)
  end

  def PrimeWordList::merge_by_literal (context, *words)
    return PrimeWordList::merge_internal(:literal, context, words)
  end

  def PrimeWordList::merge_internal (get_key_function, context, words)
    ## merge should be sorted by inter-engine score.
    ## FIXME: Isn't it necessary to sort here?
    ## FIXME: <komatsu@taiyaki.org> (2004-01-24)
    result = words.flatten.compact.sort {|word1, word2| 
      (word1.score != word2.score) ? (word2.score <=> word1.score) :
        (word2.pron.length <=> word1.pron.length)
    }

    mark = {}
    merged = PrimeWordList.new()
    result.each {|word|
      if word.literal != "" then
        word_key = word.send(get_key_function)
        if mark[word_key] then
          word0 = mark[word_key]

          ## FIXME: Make this merging more intelligence.
          ## FIXME: <komatsu@taiyaki.org> (2004-01-24)
          word.data.keys().each { | key |
            if word0.data.has_key?(key) then
              # Do nothing yet
            else
              word0.data[key] = word.data[key]
            end
          }
        else
          mark[word_key] = word
          word.prefix = Prime::get_prefix(context, word.literal)
          merged.push(word)
        end
      end
    }
    return merged
  end

  ## This attaches a prefix characters depend on the context,
  ## and returns the given words.
  def PrimeWordList::attach_prefix (context, words)
    words.each { | word |
      word.prefix = Prime::get_prefix(context, word.literal)
    }
    return words
  end

  def to_text
    texts = self.map {|word|
      word.to_text
    }
    return texts.join("\n")
  end
end
