=begin
  gettext.rb - GetText module

  Copyright (C) 2001-2006  Masao Mutoh
  Copyright (C) 2001-2003  Masahiro Sakai

      Masao Mutoh       <mutoh@highway.ne.jp>
      Masahiro Sakai    <s01397ms@sfc.keio.ac.jp>

  You may redistribute it and/or modify it under the same
  license terms as Ruby.

  $Id: gettext.rb,v 1.15 2006/06/11 15:36:20 mutoh Exp $
=end

require 'rbconfig'
require 'gettext/version'
require 'gettext/mo'
require 'gettext/locale'
require 'gettext/textdomainmanager'
require 'gettext/string'

module GetText
  # If the textdomain isn't bound when calling GetText.textdomain, this error is raised.
  class NoboundTextDomainError < RuntimeError
  end

  def self.included(mod)  #:nodoc:
    mod.extend self
  end

  @@__textdomainmanagers = Hash.new

  # call-seq:
  # bindtextdomain(domainname, options = {})
  #
  # Bind a textdomain(%{path}/%{locale}/LC_MESSAGES/%{domainname}.mo) to your program.
  # Normally, the texdomain scope becomes a ruby-script-file. 
  # So you need to call this function each ruby-script-files. 
  # On the other hand, if you call this function under GetText::Container 
  # (gettext/container, gettext/erb, gettext/rails), the textdomain scope becomes a Class/Module.
  # * domainname: the textdomain name.
  # * options: options as an Hash.
  #   * :path - the path to the mo-files. When the value is nil, it will search default paths such as 
  #     /usr/share/locale, /usr/local/share/locale)
  #   * :locale - the locale string such as "ja_JP.UTF-8".  Generally, you should use GetText.set_locale instead.
  #     The value is searched order by:
  #     the value of this value > System default language.
  #   * :charset - output charset.  This affect the current textdomain only. Generally, you should use GetText.set_output_charset instead.
  #     The value is searched order by:
  #     the value of Locale.set_output_charset > ENV["OUTPUT_CHARSET"] > this value > System default charset.
  # * Returns: the GetText::TextDomain.
  # Note: Don't use locale_, charset argument(not in options). 
  # They are remained for backward compatibility. 
  #
  def bindtextdomain(domainname, options = {}, locale_ = nil, charset = nil)
    opt = {}
    if options.kind_of? String
      # For backward compatibility
      opt = {:path => options, :locale => locale_, :charset => charset}
    elsif options
      opt = options
    end
    opt[:locale] = opt[:locale] ? Locale::Object.new(opt[:locale]) : Locale.get
    opt[:charset] = output_charset if output_charset
    locale.charset = opt[:charset] if opt[:charset]
    Locale.set_current(opt[:locale])
    manager = @@__textdomainmanagers[bound_target]
    if manager
      manager.set_locale(opt[:locale]) 
    else
      manager = TextDomainManager.new(bound_target, opt[:locale])
      @@__textdomainmanagers[bound_target] = manager
    end
    manager.add_textdomain(domainname, opt)
    manager
  end

  # Binds a existed textdomain to your program. 
  # This is the same function with GetText.bindtextdomain but simpler than bindtextdomain.
  # Notice that you need to call GetText.bindtextdomain first. If the domainname hasn't bound yet, 
  # raises GetText::NoboundTextDomainError.
  # * domainname: a textdomain name.
  # * Returns: the GetText::TextDomain.
  def textdomain(domainname)
    domain = TextDomainManager.textdomain(domainname)
    raise NoboundTextDomainError, "#{domainname} is not bound." unless domain
    manager = @@__textdomainmanagers[bound_target]
    unless manager
      manager = TextDomainManager.new(bound_target, Locale.get)
      @@__textdomainmanagers[bound_target] = manager
    end
    manager.add_textdomain(domainname)
    manager.set_locale(Locale.get)
  end

  TARGET_REGEXP = /\:\:/  # :nodoc:

  # Iterates bound textdomains.
  # * klass: a class/module to find. Default is the class of self.
  # * ignore_targets: Ignore tragets.
  # * Returns: a bound GetText::TextDomain or nil.
  def each_textdomain(klass = bound_target, ignore_targets = []) #:nodoc:
    (klass.ancestors + [GetText]).each do |target|
      unless ignore_targets.include? target
	ignore_targets << target
	manager = @@__textdomainmanagers[target]
	if manager
	  manager.each{ |textdomain|
	    yield textdomain
	  }
	end
	if target.to_s =~ TARGET_REGEXP
	  each_textdomain(Module.const_get($`), ignore_targets){|domain|
	    yield domain
	  }
	end
      end
    end
    self
  end

  def bound_target # :nodoc:
    if self.kind_of? Class or self.kind_of? Module
      self
    else
      self.class
    end
  end

  # call-seq:
  #   gettext(msgid)
  #   _(msgid)
  #
  # Translates msgid and return the message.
  # * msgid: the message id.
  # * Returns: localized text by msgid. If there are not binded mo-file, it will return msgid.
  def gettext(msgid)
    ret = nil
    each_textdomain {|textdomain|
      ret = textdomain.gettext(msgid)
      break if ret
    }
    ret ? ret : msgid
  end

  # call-seq:
  #   ngettext(msgid, msgid_plural, n)
  #   ngettext(msgids, n)  # msgids = [msgid, msgid_plural]
  #   n_(msgid, msgid_plural, n)
  #   n_(msgids, n)  # msgids = [msgid, msgid_plural]
  #
  # The ngettext is similar to the gettext function as it finds the message catalogs in the same way. 
  # But it takes two extra arguments for plural form.
  #
  # * msgid: the singular form.
  # * msgid_plural: the plural form.
  # * n: a number used to determine the plural form.
  # * Returns: the localized text which key is msgid_plural if n is plural(follow plural-rule) or msgid.
  #   "plural-rule" is defined in po-file.
  def ngettext(arg1, arg2, arg3 = nil)
    if arg1.kind_of?(Array)
      msgid = arg1[0]
      msgid_plural = arg1[1]
      n = arg2
    else
      msgid = arg1
      msgid_plural = arg2
      n = arg3
    end
    ret = nil
    each_textdomain {|textdomain|
      ret = textdomain.ngettext(msgid, msgid_plural, n)
      break if ret
    }
    ret ? ret : (n == 1 ? msgid : msgid_plural)
  end

  # This function does nothing. But it is required in order to recognize the msgid by rgettext.
  # * msgid: the message id.
  # * Returns: msgid.
  def N_(msgid)
    msgid
  end

  # This is same function as N_ but for ngettext. 
  # * msgid: the message id.
  # * msgid_plural: the plural message id.
  # * Returns: msgid.
  def Nn_(msgid, msgid_plural)
    [msgid, msgid_plural]
  end

  # call-seq:
  #   sgettext(msgid, div = '|')
  #   s_(msgid, div = '|')
  #
  # Translates msgid, but if there are no localized text, 
  # it returns a last part of msgid separeted "div".
  #
  # * msgid: the message id.
  # * div: separator or nil.
  # * Returns: the localized text by msgid. If there are no localized text, 
  #   it returns a last part of msgid separeted "div".
  # See: http://www.gnu.org/software/gettext/manual/html_mono/gettext.html#SEC151
  def sgettext(msgid, div = '|')
    msg = gettext(msgid)
    if msg == msgid
      if index = msg.rindex(div)
	msg = msg[(index + 1)..-1]
      end
    end
    msg
  end

  # Sets locale to the current class/module
  #
  # Notice that you shouldn't use this for your own Libraries.
  # * locale: a locale string or Locale::Object.
  # * this_target_only: true if you want to change the current class/module only.
  # Otherwise, this changes the locale of the current class/module and its ancestors.
  # Default is false.
  # * Returns: self
  def set_locale(locale, this_target_only = false)
    ret = nil
    if locale
      if locale.kind_of? Locale::Object
	ret = locale
      else
	ret = Locale::Object.new(locale.to_s)
      end
      ret.charset = output_charset if output_charset
      Locale.set(ret)
    else
      Locale.clear
      ret = Locale.get
    end
    if this_target_only
      manager = @@__textdomainmanagers[bound_target]
      if manager
	manager.set_locale(ret)
      end
    else
      each_textdomain {|textdomain|
	textdomain.set_locale(ret)
      }
    end
    self
  end

  # Sets locale to the all textdomains.
  #
  # Notice that you shouldn't use this for your own Libraries.
  # * locale: a locale string or Locale::Object.
  # * Returns: self
  def set_locale_all(locale)
    ret = nil
    if locale
      if locale.kind_of? Locale::Object
	ret = locale
      else
	ret = Locale::Object.new(locale.to_s)
      end
      ret.charset = output_charset if output_charset
      Locale.set(ret)
    else
      Locale.set(nil)
      ret = Locale.get
    end
    TextDomainManager.each_all {|textdomain|
      textdomain.set_locale(ret)
    }
    self
  end

  # Same as GetText.set_locale.
  # * locale: a locale string
  # * src: internal usage only. You shouldn't use this.
  # * Returns: a locale string
  def locale=(locale)
    set_locale(locale)
    locale
  end

  # Sets charset(String) such as "euc-jp", "sjis", "CP932", "utf-8", ... 
  # You shouldn't use this in your own Libraries.
  # * charset: an output_charset
  # * Returns: charset
  def set_output_charset(charset)
    TextDomainManager.output_charset = charset
    self
  end

  # Same as GetText.set_output_charset
  # * charset: an output_charset
  # * Returns: charset
  def output_charset=(charset)
    TextDomainManager.output_charset = charset
  end

  # Gets the current output_charset which is set using GetText.set_output_charset.
  # * Returns: output_charset.
  def output_charset
    TextDomainManager.output_charset
  end

  # Gets the current locale.
  # * Returns: a current Locale::Object
  def locale
    Locale.current
  end

  # Deprecated. Now this function do nothing. Use GetText.output_charset= instead.
  def set_charset(cs) #:nodoc:
    $stderr.puts "Deprecated. Now this function do nothing. Use GetText.output_charset= instead." if $DEBUG
    self
  end

  def charset=(cs) #:nodoc:
    set_charset(cs)
    cs
  end
  
  # Add default locale path.
  # * path: a new locale path. (e.g.) "/usr/share/locale/%{locale}/LC_MESSAGES/%{name}.mo"
  #   ('locale' => "ja_JP", 'name' => "textdomain")
  # * Returns: the new DEFAULT_LOCALE_PATHS
  def add_default_locale_path(path)
    TextDomain.add_default_locale_path(path)
  end

  # Show the current textdomain information. This function is for debugging.
  # * options: options as a Hash.
  #   * :with_messages - show informations with messages of the current mo file. Default is false.
  #   * :out - An output target. Default is STDOUT.
  #   * :with_paths - show the load paths for mo-files.
  # * Returns: localized text by msgid. If there are not binded mo-file, it will return msgid.
  def current_textdomain_info(options = {})
    opts = {:with_messages => false, :with_paths => false, :out => STDOUT}.merge(options)
    ret = nil
    each_textdomain {|textdomain|
      opts[:out].puts "TextDomain name: \"#{textdomain.name}\""
      opts[:out].puts "TextDomain current locale: \"#{textdomain.current_locale}\""
      opts[:out].puts "TextDomain current mo filename: \"#{textdomain.current_mo.filename}\""
      if opts[:with_paths]
	opts[:out].puts "TextDomain locale file paths:"
	textdomain.locale_paths.each do |v|
	  opts[:out].puts "  #{v}"
	end
      end
      if opts[:with_messages]
	opts[:out].puts "The messages in the mo file:"
	textdomain.current_mo.each{|k, v|
	  opts[:out].puts "  \"#{k}\": \"#{v}\""
	}
      end
    }
  end

  alias :setlocale :locale= #:nodoc:
  alias :_ :gettext   #:nodoc:
  alias :n_ :ngettext #:nodoc:
  alias :s_ :sgettext #:nodoc:

  module_function :bindtextdomain, :textdomain, :each_textdomain,
    :N_, :gettext, :_, :ngettext, :n_, :sgettext, :s_, :bound_target,
    :setlocale, :set_locale, :locale=, :set_locale_all, :locale, :charset=, :set_charset,
    :set_output_charset, :output_charset=, :output_charset, :current_textdomain_info
end
