# taiyaki.rb: Ruby Libraries by Hiroyuki Komatsu
# $Id: taiyaki.rb,v 1.9 2005/03/07 07:51:32 komatsu Exp $
#
# Copyright (C) 2002 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.

$KCODE = 'e'
require 'jcode'

begin
  Kernel::require('progressbar')
rescue LoadError
  $stderr.puts "WARNING:"
  $stderr.puts("  The required Ruby library 'progressbar' is not found.")
  class ProgressBar
    def initialize (title, estimated_length) end
    def inc    () end
    def finish () end
  end
end

### Considering under compatibility with Ruby1.6
begin
  Array.new([])
rescue TypeError
  class Array
    alias original_initialize initialize

    def initialize(value = 0)
      if value.kind_of?(Array) then
        original_initialize(value.length)
        self.length.times {|i|
          self[i] = value[i]
        }
      else
        original_initialize(value)
      end
    end
  end
end

def require (name, package = nil, url = nil)
  package_name =
         {'progressbar'                => 'progressbar',
          'sary'                       => 'sary-ruby',
          'prime/prime-dict-config.rb' => 'prime-dict',
          'suikyo/suikyo'              => 'suikyo',
  }
  package_url =
         {'progressbar'   => 'http://www.namazu.org/~satoru/ruby-progressbar/',
          'sary'          => 'http://sary.namazu.org/',
          'prime/prime-dict-config.rb' =>
                             'http://sourceforge.jp/projects/prime/',
          'suikyo/suikyo' => 'http://taiyaki.org/suikyo/',
          'MeCab'         => 'http://chasen.org/~taku/software/mecab/',
  }
  begin
    Kernel::require(name)
  rescue LoadError
    package = (package or package_name[name])
    url     = (url     or package_url[name])

    $stderr.puts("ERROR:")
    if ENV['LANG'] == 'ja_JP.eucJP' then
      $stderr.puts("  Ruby ライブラリの '#{name}' をロードできませんでした。")
      if package then
        $stderr.puts("  おそらく #{package} パッケージを" +
                     "インストールする必要があります。")
      end
      if url then
        $stderr.puts("    <#{url}>")
      end
      $stderr.puts("  お手数をおかけしてすみません。")
    else
      $stderr.puts("  The required Ruby library '#{name}' is not found.")
      if package then
        $stderr.puts("  You might need to install a #{package} package.")
      end
      if url then
        $stderr.puts("    <#{url}>")
      end
      $stderr.puts("  Sorry for your inconvenience.")
    end
    raise(LoadError)
  end
end

def with_io(io, option = "w")
  ### with_io('/tmp/logfile') {|io|
  ###   lines.each {|line|
  ###     io.print(line)
  ###   }
  ### }
  ###
  ### with_io($stdout) {|io|
  ###   lines.each {|line|
  ###     io.print(line)
  ###   }
  ### }
  if io.is_a?(String) then
    io = open(io, option)
    yield io
    io.close()
  else
    yield io
  end
end

module Kernel
  def non_nil? ()
    return (not self.nil?)
  end

  def is_in? (*arg)
    return arg.member?(self)
  end
end

class NilClass
  def each
    return nil
  end

  def empty?
    return true
  end
end

class String
  # 'ぁ-ん'
  HIRAGANA_SJIS = "\202\237-\202\361"
  HIRAGANA_EUC  = "\244\241-\244\363"

  # う゛
  HIRAGANA_VU_SJIS = "\x82\xA4\x81J"
  HIRAGANA_VU_EUC  = "\xA4\xA6\xA1\xAB"
  
  # 'ァ-ン'
  KATAKANA_SJIS = "\203@-\203\223"
  KATAKANA_EUC  = "\245\241-\245\363"

  # ヴ
  KATAKANA_VU_SJIS = "\x83\x94"
  KATAKANA_VU_EUC  = "\xA5\xF4"

  def hiragana!
    case $KCODE[0]
    when ?s, ?S
      self.gsub!(KATAKANA_VU_SJIS, HIRAGANA_VU_SJIS)
      return self.tr!(KATAKANA_SJIS, HIRAGANA_SJIS)
    when ?e, ?E
      self.gsub!(KATAKANA_VU_EUC, HIRAGANA_VU_EUC)
      return self.tr!(KATAKANA_EUC,  HIRAGANA_EUC)
    else
      return nil
    end
  end

  def hiragana
    return ((string = self.dup).hiragana! or string)
  end

  def katakana!
    case $KCODE[0]
    when ?s, ?S
      self.gsub!(HIRAGANA_VU_SJIS, KATAKANA_VU_SJIS)
      return self.tr!(HIRAGANA_SJIS, KATAKANA_SJIS)
    when ?e, ?E
      self.gsub!(HIRAGANA_VU_EUC, KATAKANA_VU_EUC)
      return self.tr!(HIRAGANA_EUC,  KATAKANA_EUC)
    else
      return self
    end
  end

  def katakana
    return ((string = self.dup).katakana! or string)
  end

#   def halfwidth
#    
#   end

  def chars
    return self.split(//)
  end

  def increase
    increasing_list = get_increasing_list()
    if block_given? then
      increasing_list.each {|string|
	yield string
      }
    else
      return increasing_list
    end
  end

  def separate_increase
    increasing_list = get_increasing_separated_pair_list()
    if block_given? then
      increasing_list.each {| (head, tail) |
	yield(head, tail)
      }
    else
      return increasing_list
    end
  end

  def decrease
    decreasing_list = get_increasing_list().reverse()
    if block_given? then
      decreasing_list.each {|string|
	yield string
      }
    else
      return decreasing_list
    end
  end

  def separate (index)
    string_chars = self.chars()
    if index < 0 then
      index += string_chars.length
    end
    return [string_chars[0 , index].join(), string_chars[index .. -1].join()]
  end

  private
  def get_increasing_list ()
    increasing_string = ""
    increasing_list = []
    self.chars.each {|char|
      increasing_string += char
      increasing_list.push(increasing_string)
    }
    return increasing_list
  end

  def get_increasing_separated_pair_list ()
    increasing_list = []
    self.chars.length.times { | index |
      increasing_list.push( self.separate(index + 1) )
    }
    return increasing_list
  end
end


class File
  def File::join2 (*paths)
    dirs = paths[0..-2].map{|path|
      path ? path.split(File::Separator) : ""
    }
    join(dirs, paths[-1])
  end
  # Check join2(nil, "a")


  def File::delext (path)
    paths = path.split(File::Separator)
    #(dir, file) = [paths[0..-2], paths[-1]]
    ### It's enigma that sub(/(.)\.[^.]*/, $1) doesn't work.
    paths[-1] = paths[-1].sub(/(.)\.[^.]*$/, '') + ($1 or '')
    return paths.join(File::Separator)
  end
  # Check delext("text.txt")

  def File::ensure (file, filemode = nil)
    if File::file?(file) == false then
      File::open(file, "w").close()
    end
    if filemode then
      File::chmod(filemode, file)
    end
  end
end

class Dir
  def Dir::ensure (path)
    path = File::expand_path(path)
    if File::directory?(path) then
      return true
    else
      if File::exist?(path) then
	return false
      else
	upper_dir = File::join(path.split(File::Separator)[0..-2])
        ## If path does not have any directory separator like "dir"
        ## not "./dir", the value of upper_dir becames an empty string "".
	if upper_dir == "" or
            ( Dir::ensure(upper_dir) && File::writable_real?(upper_dir) ) then
	  Dir::mkdir(path)
	  return true
	end
      end
    end
    return false
  end
end

class Hash
  def list_push (key, list_item)
    if self.key?(key) then
      self[key].push(list_item)
    else
      self[key] = [list_item]
    end
  end

  def set (key, value)
    if self.has_key?(key) then
      unless self[key].member?(value) then
        self[key].push(value)
      end
    else
      self[key] = [value]
    end
  end
end

module Debug
  attr_accessor :debug_mode
  def debug_message (message, output = nil)
    if $DEBUG || @debug_mode || output then
      ($DEBUG_IO or $stdout).puts "#{self.class}[#{self.id}]: #{message}"
    end
  end

  def Debug::message (message, output = nil)
    if $DEBUG || output then
      ($DEBUG_IO or $stdout).puts message
    end
  end
end

module Marshal
  def Marshal::init_file (filename, threshold_time = nil)
    object = nil
    if File::exist?(filename) && 
	(threshold_time.nil? || File::mtime(filename) > threshold_time) then
      object = Marshal::load_file(filename)
      Debug::message("Marshal::init_file: Load the ruby object `#{filename}'.")
    end
    unless object then
      object = yield
      Marshal::dump_file(object, filename)
      Debug::message("Marshal::init_file: Initialize renewally `#{filename}'.")
    end
    return object
  end

  ## FIXME: I'm not sure why an error happens. (?_?)
  ## FIXME: <komatsu@taiyaki.org> (2004-01-28)
  def Marshal::load_file (filename)
    begin
      object_file = File::open(filename, "r")
      object = Marshal::load(object_file)
      object_file.close
      return object
    rescue
      $stderr.puts "WARINIG: Marshal::load_file (#{filename}) failed."
      return nil
    end
  end

  def Marshal::dump_file (object, filename)
    object_file = File::open(filename, "w")
    Marshal::dump(object, object_file)
    object_file.close
  end
end


#### ------------------------------------------------------------
#### For ProgressBar.rb
#### ------------------------------------------------------------

module Enumerable
  def each_with_status ()
    length = self.length()
    self.each_with_index {|value, index|
      case index
      when 0 then
	status = :first
      when (length - 1) then
	status = :last
      else
	status = nil
      end
      yield(value, status)
    }
  end

  def each_with_pbar (title = '')
    #| each_with_pbar (title/String)
    #| => self/Enumerable
    #|
    #| Is a each function with a progress bar.
    #| If title is nil, this function is the same function as 'each'. 
    #|
    if title.nil? then
      self.each {|value|
	yield value
      }
    elsif self.kind_of?(IO) then
      size = self.stat.size
      pbar = ProgressBar.new(title, size)
      self.each {|value|
	yield value
	pbar.inc(value.length)
      }
      pbar.finish
    else
      pbar = ProgressBar.new(title, self.size)
      self.each {|value|
	yield value
	pbar.inc
      }
      pbar.finish
    end
    return self
  end

  def sort_with_pbar (title = '')
    #| sort_with_pbar (title/String)
    #| => sorted_value/Enumerable
    #|
    #| Is a sort function with a progress bar.
    #| If title is nil, this function is the same function as 'sort'. 
    #|
    if title.nil? then
      if block_given? then
	sorted = self.sort {|value1, value2|
	  yield value1, value2
	}
      else
	sorted = self.sort
      end
      return sorted
    end

    if self.length == 0 then
      estimated_length = 0
    else
      estimated_length = (1.4 * self.length * Math.log(self.length)).to_i
    end
    pbar = ProgressBar.new(title, estimated_length)
    if block_given? then
      sorted = self.sort {|value1, value2|
	pbar.inc
	yield value1, value2
      }
    else
      sorted = self.sort {|value1, value2|
	pbar.inc
	value1 <=> value2
      }
    end
    pbar.finish
    return sorted
  end
end

class Integer
  def times_with_pbar (title = '')
    #| times_with_pbar (title/String)
    #| => self/Integer
    #|
    #| Is a times function with a progress bar.
    #| If title is nil, this function is the same function as 'times'. 
    #|
    if title.nil? then
      self.times {|value|
	yield value
      }
      return self
    end

    pbar = ProgressBar.new(title, self)
    self.times {|value|
      yield value
      pbar.inc
    }
    pbar.finish
  end
end
