# -*- coding: utf-8 -*-

=begin
  gettext/text_domain_manager - GetText::TextDomainManager class

  Copyright (C) 2009  Masao Mutoh

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

=end

require 'gettext/class_info'
require 'gettext/text_domain'
require 'gettext/text_domain_group'

module GetText

  module TextDomainManager

    @@text_domain_pool = {}
    @@text_domain_group_pool = {}

    @@output_charset = nil
    @@gettext_classes = []

    @@singular_message_cache = {}
    @@plural_message_cache = {}
    @@cached = ! $DEBUG

    extend self

    # Find text domain by name
    def text_domain_pool(domainname)
      @@text_domain_pool[domainname]
    end

    # Set the value whether cache messages or not.
    # true to cache messages, otherwise false.
    #
    # Default is true. If $DEBUG is false, messages are not checked even if
    # this value is true.
    def cached=(val)
      @@cached = val
      TextDomain.cached = val
    end

    # Return the cached value.
    def cached?
      TextDomain.cached?
    end

    # Gets the output charset.
    def output_charset
      @@output_charset
    end

    # Sets the output charset.The program can have a output charset.
    def output_charset=(charset)
      @@output_charset = charset
      @@text_domain_pool.each do |key, text_domain|
        text_domain.output_charset = charset
      end
    end

    # bind text domain to the class.
    def bind_to(klass, domainname, options = {})
      warn "Bind the domain '#{domainname}' to '#{klass}'. " if $DEBUG

      charset = options[:output_charset] || self.output_charset
      text_domain = create_or_find_text_domain(domainname,options[:path],charset)
      target_klass = ClassInfo.normalize_class(klass)
      create_or_find_text_domain_group(target_klass).add(text_domain)
      @@gettext_classes << target_klass unless @@gettext_classes.include? target_klass

      text_domain
    end

    def each_text_domains(klass) #:nodoc:
      lang = Locale.candidates[0]
      ClassInfo.related_classes(klass, @@gettext_classes).each do |target|
        if group = @@text_domain_group_pool[target]
          group.text_domains.each do |text_domain|
            yield text_domain, lang
          end
        end
      end
    end

    # Translates msgid, but if there are no localized text,
    # it returns a last part of msgid separeted "div" or whole of the msgid with no "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".
    def translate_singular_message(klass, msgid, div = nil)
      klass = ClassInfo.normalize_class(klass)
      key = [Locale.current, klass, msgid, div].hash
      msg = @@singular_message_cache[key]
      return msg if msg and @@cached
      # Find messages from related classes.
      each_text_domains(klass) do |text_domain, lang|
        msg = text_domain.translate_singular_message(lang, msgid)
        break if msg
      end

      # If not found, return msgid.
      msg ||= msgid
      if div and msg == msgid
        if index = msg.rindex(div)
          msg = msg[(index + 1)..-1]
        end
      end
      @@singular_message_cache[key] = msg
    end

    # This function is similar to the get_singular_message function
    # as it finds the message catalogs in the same way.
    # But it takes two extra arguments for plural form.
    # The msgid parameter must contain the singular form of the string to be converted.
    # It is also used as the key for the search in the catalog.
    # The msgid_plural parameter is the plural form.
    # The parameter n is used to determine the plural form.
    # If no message catalog is found msgid1 is returned if n == 1, otherwise msgid2.
    # And if msgid includes "div", it returns a last part of msgid separeted "div".
    #
    # * msgid: the singular form with "div". (e.g. "Special|An apple", "An apple")
    # * msgid_plural: the plural form. (e.g. "%{num} Apples")
    # * n: a number used to determine the plural form.
    # * div: the separator. Default is "|".
    # * 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.
    #
    # or
    #
    # * [msgid, msgid_plural] : msgid and msgid_plural an Array
    # * n: a number used to determine the plural form.
    # * div: the separator. Default is "|".
    def translate_plural_message(klass, arg1, arg2, arg3 = "|", arg4 = "|")
      klass = ClassInfo.normalize_class(klass)
      # parse arguments
      if arg1.kind_of?(Array)
        msgid = arg1[0]
        msgid_plural = arg1[1]
        n = arg2
        if arg3 and arg3.kind_of? Numeric
          raise ArgumentError, _("ngettext: 3rd parmeter is wrong: value = %{number}") % {:number => arg3}
        end
        div = arg3
      else
        msgid = arg1
        msgid_plural = arg2
        raise ArgumentError, _("ngettext: 3rd parameter should be a number, not nil.")  unless arg3
        n = arg3
        div = arg4
      end

      key = [Locale.current, klass, msgid, msgid_plural, div].hash
      msgs = @@plural_message_cache[key]
      unless (msgs and @@cached)
        # Find messages from related classes.
        msgs = nil
        each_text_domains(klass) do |text_domain, lang|
          msgs = text_domain.translate_plural_message(lang, msgid, msgid_plural)
          break if msgs
        end

        msgs = [[msgid, msgid_plural], TextDomain::DEFAULT_PLURAL_CALC] unless msgs

        msgstrs = msgs[0]
        if div and msgstrs[0] == msgid and index = msgstrs[0].rindex(div)
          msgstrs[0] = msgstrs[0][(index + 1)..-1]
        end
        @@plural_message_cache[key] = msgs
      end

      # Return the singular or plural message.
      msgstrs = msgs[0]
      plural = msgs[1].call(n)
      return msgstrs[plural] if plural.kind_of?(Numeric)
      return plural ? msgstrs[1] : msgstrs[0]
    end

    # for testing.
    def dump_all_text_domains
      [
        @@text_domain_pool.dup,
        @@text_domain_group_pool.dup,
        @@gettext_classes.dup,
      ]
    end

    # for testing.
    def restore_all_text_domains(dumped_all_text_domains)
      @@text_domain_pool, @@text_domain_group_pool, @@gettext_classes =
        dumped_all_text_domains
      clear_caches
    end

    # for testing.
    def clear_all_text_domains
      @@text_domain_pool = {}
      @@text_domain_group_pool = {}
      @@gettext_classes = []
      clear_caches
    end

    # for testing.
    def clear_caches
      @@singular_message_cache = {}
      @@plural_message_cache = {}
    end

    def create_or_find_text_domain_group(klass) #:nodoc:
      group = @@text_domain_group_pool[klass]
      return group if group

      @@text_domain_group_pool[klass] = TextDomainGroup.new
    end

    def create_or_find_text_domain(name, path, charset)#:nodoc:
      text_domain = @@text_domain_pool[name]
      return text_domain if text_domain

      @@text_domain_pool[name] = TextDomain.new(name, path, charset)
    end
  end
end
