=begin
  gettext/active_record.rb - GetText for ActiveRecord

  Copyright (C) 2006-2008  Masao Mutoh

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

  $Id: active_record.rb,v 1.25 2008/07/24 17:17:48 mutoh Exp $
=end
require 'gettext'
require 'active_record'
require 'gettext/rails_compat'

module ActiveRecord #:nodoc:
  class Migration
    extend GetText
    include GetText
  end

  class RecordInvalid < ActiveRecordError #:nodoc:
    attr_reader :record
    include GetText
    bindtextdomain("rails")

    def initialize(record)
      @record = record
      super(_("Validation failed: %{error_messages}") % 
            {:error_messages => @record.errors.full_messages.join(", ")})
    end
  end

  
  module ConnectionAdapters #:nodoc:
    # An abstract definition of a column in a table.
    class Column
      attr_accessor :table_class
      alias :human_name_witout_localized :human_name 

      def human_name_with_gettext
        if table_class
          table_class.human_attribute_name(@name)
        else
          @name.humanize
        end
      end
      alias_method_chain :human_name, :gettext
    end
  end

  module Validations # :nodoc:
    class << self
      def real_included(base)
        base.extend ClassMethods
        base.class_eval{
          include GetText
          def gettext(str)  #:nodoc:
            _(str)
          end
          class << self
            def human_attribute_name_with_gettext(attribute_key_name) #:nodoc:
              s_("#{self}|#{attribute_key_name.humanize}")
            end
            alias_method_chain :human_attribute_name, :gettext
            def human_attribute_table_name_for_error(table_name) #:nodoc:
              _(table_name.gsub(/_/, " "))
            end
          end
        }
      end
    end

    if respond_to? :included
      class << self
        def included_with_gettext(base) # :nodoc:
          unless base <= ActiveRecord::Base
            included_without_gettext(base)
          end
          real_included(base)
        end
        alias_method_chain :included, :gettext
      end
    else
      class << self
        # Since rails-1.2.0.
        def append_features_with_gettext(base) # :nodoc:
          unless base <= ActiveRecord::Base
            append_features_without_gettext(base)
          end
          real_included(base)
        end
        alias_method_chain :append_features, :gettext
      end
    end

    module ClassMethods #:nodoc:
      @@custom_error_messages_d = {}
      # Very ugly but...
      def validates_length_of_with_gettext(*attrs)  #:nodoc:
	if attrs.last.is_a?(Hash)
          [:message, :too_long, :too_short, :wrong_length].each do |msg_sym|
            msg = attrs.last[msg_sym]
            @@custom_error_messages_d[msg] = /\A#{Regexp.escape(msg).sub(/%d/, '(\d+)')}\Z/ if msg
          end
	end
	validates_size_of(*attrs)
      end
      alias_method_chain :validates_length_of, :gettext

      def _validates_parse_custom_messages(*attrs) #:nodoc:
	if attrs.last.is_a?(Hash) and attrs.last[:message]
          msg = attrs.last[:message]
          key = msg.dup
          msg.sub!(/%\{val\}/, '%s')
          @@custom_error_messages_d[key] = /\A#{Regexp.escape(msg).sub('%s', '(.*)')}\Z/
	end
        attrs
      end
      
      def validates_format_of_with_gettext(*attrs)  #:nodoc:
        attrs = _validates_parse_custom_messages(*attrs)
	validates_format_of_without_gettext(*attrs)
      end
      alias_method_chain :validates_format_of, :gettext

      def validates_inclusion_of_with_gettext(*attrs)  #:nodoc:
        attrs = _validates_parse_custom_messages(*attrs)
	validates_inclusion_of_without_gettext(*attrs)
      end
      alias_method_chain :validates_inclusion_of, :gettext

      def validates_exclusion_of_with_gettext(*attrs)  #:nodoc:
        attrs = _validates_parse_custom_messages(*attrs)
	validates_exclusion_of_without_gettext(*attrs)
      end
      alias_method_chain :validates_exclusion_of, :gettext

      def custom_error_messages_d  #:nodoc:
	@@custom_error_messages_d
      end
    end
    def custom_error_messages_d  #:nodoc:
      self.class.custom_error_messages_d
    end
  end
  
  class Base
    include GetText
    include Validations

    @@gettext_untranslate = Hash.new(false)
    @@gettext_untranslate_columns = {}

    class << self
      # Untranslate all of the tablename/fieldnames in this model class.
      def untranslate_all
        @@gettext_untranslate[self] = true
      end

      # Returns true if "untranslate_all" is called. Otherwise false.
      def untranslate_all?
        @@gettext_untranslate[self]
      end

      # Sets the untranslate columns.
      # (e.g.) untranslate :foo, :bar, :baz
      def untranslate(*w)
        ary = @@gettext_untranslate_columns[self] || []
        ary += w.collect{|v| v.to_s}
        @@gettext_untranslate_columns[self] = ary
      end
      
      # Returns true if the column is set "untranslate".
      # (e.g.) untranslate? :foo
      def untranslate?(columnname)
        ary = @@gettext_untranslate_columns[self] || []
        ary.include?(columnname)
      end

      def untranslate_data #:nodoc:
        [@@gettext_untranslate[self], @@gettext_untranslate_columns[self] || []]
      end

      def columns_with_gettext
        unless defined? @columns
          @columns = nil 
        end
        unless @columns
          @columns = columns_without_gettext
          @columns.each {|column| 
            column.table_class = self
          }
        end
        @columns
      end
      alias_method_chain :columns, :gettext
    
      # call-seq:
      # set_error_message_title(msg)
      #
      # ((*Deprecated*)) 
      # Use ActionView::Helpers::ActiveRecordHelper::L10n.set_error_message_title
      # instead.
      #
      # Sets a your own title of error message dialog.
      # * msg: [single_msg, plural_msg]. Usually you need to call this with Nn_().
      # * Returns: [single_msg, plural_msg]
      def set_error_message_title(msg, plural_msg = nil)
        ActionView::Helpers::ActiveRecordHelper::L10n.set_error_message_title(msg, plural_msg)
      end
      
      # call-seq:
      # set_error_message_explanation(msg)
      #
      # ((*Deprecated*)) 
      # Use ActionView::Helpers::ActiveRecordHelper::L10n.set_error_message_explanation
      # instead.
      #
      # Sets a your own explanation of the error message dialog.
      # * msg: [single_msg, plural_msg]. Usually you need to call this with Nn_().
      # * Returns: [single_msg, plural_msg]
      def set_error_message_explanation(msg, plural_msg = nil)
        ActionView::Helpers::ActiveRecordHelper::L10n.set_error_message_explanation(msg, plural_msg)
      end
    end
  end

  # activerecord-1.14.3/lib/active_record/validations.rb
  class Errors #:nodoc:
    include GetText

    def initialize_with_gettext(base) # :nodoc:
      initialize_without_gettext(base)
      bindtextdomain("rails")
    end
    alias_method_chain :initialize, :gettext

    def @@default_error_messages.[]=(id, msg) #:nodoc:
      @@default_error_messages.update({id => msg})
      if [:message, :too_long, :too_short, :wrong_length].include?(id)
	@@default_error_messages_d[msg] = /\A#{Regexp.escape(msg).sub(/%d/, '(\d+)')}\Z/ 
      end
    end    

    # You need to define this here, because this values will be updated by application.
    default_error_messages.update(
				  :inclusion => N_("%{fn} is not included in the list"),
				  :exclusion => N_("%{fn} is reserved"),
				  :invalid => N_("%{fn} is invalid"),
				  :confirmation => N_("%{fn} doesn't match confirmation"),
				  :accepted  => N_("%{fn} must be accepted"),
				  :empty => N_("%{fn} can't be empty"),
				  :blank => N_("%{fn} can't be blank"),
				  :too_long => N_("%{fn} is too long (maximum is %d characters)"),  
				  :too_short => N_("%{fn} is too short (minimum is %d characters)"), 
				  :wrong_length => N_("%{fn} is the wrong length (should be %d characters)"),
				  :taken => N_("%{fn} has already been taken"),
				  :not_a_number => N_("%{fn} is not a number"),
                                  :greater_than => N_("%{fn} must be greater than %d"),
                                  :greater_than_or_equal_to => N_("%{fn} must be greater than or equal to %d"),
                                  :equal_to => N_("%{fn} must be equal to %d"),
                                  :less_than => N_("%{fn} must be less than %d"),
                                  :less_than_or_equal_to => N_("%{fn} must be less than or equal to %d"),
                                  :odd => N_("%{fn} must be odd"),
                                  :even => N_("%{fn} must be even")
				  )
    @@default_error_messages_d = {
      default_error_messages[:too_long] => /#{Regexp.escape(default_error_messages[:too_long]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:too_short] => /#{Regexp.escape(default_error_messages[:too_short]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:wrong_length] => /#{Regexp.escape(default_error_messages[:wrong_length]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:greater_than] => /#{Regexp.escape(default_error_messages[:greater_than]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:greater_than_or_equal_to] => /#{Regexp.escape(default_error_messages[:greater_than_or_equal_to]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:equal_to] => /#{Regexp.escape(default_error_messages[:equal_to]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:less_than] => /#{Regexp.escape(default_error_messages[:less_than]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:less_than_or_equal_to] => /#{Regexp.escape(default_error_messages[:less_than_or_equal_to]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:odd] => /#{Regexp.escape(default_error_messages[:odd]).sub(/%d/, '(\d+)')}/,
      default_error_messages[:even] => /#{Regexp.escape(default_error_messages[:even]).sub(/%d/, '(\d+)')}/,
    }
    cattr_accessor :default_error_messages_d

    def localize_error_message(attr, msg, append_field) # :nodoc:
      custom_msg = nil
      #Ugly but... :-<
      @@default_error_messages_d.dup.merge(@base.custom_error_messages_d).each do |key, regexp|
        if regexp =~ msg
          custom_msg = @base.gettext(key)
          custom_msg = _(msg) if custom_msg == msg 
          custom_msg = _(custom_msg) % $1
          custom_msg = _(custom_msg) % {:val => $1}
          break
        end
      end

      unless custom_msg
        custom_msg = @base.gettext(msg)
        custom_msg = _(msg) if custom_msg == msg 
      end

      if attr == "base"
        full_message = custom_msg
      elsif /%\{fn\}/ =~ custom_msg
        full_message = custom_msg % {:fn => @base.class.human_attribute_name(attr)}
      elsif append_field
        full_message = @base.class.human_attribute_name(attr) + " " + custom_msg
      else
        full_message = custom_msg
      end
      full_message
    end

    def localize_error_messages(append_field = true) # :nodoc:
      # e.g.) foo field: "%{fn} foo" => "Foo foo", "foo" => "Foo foo". 
      errors = {}
      each {|attr, msg|
        next if msg.nil?
        errors[attr] ||= []
        errors[attr] << localize_error_message(attr, msg, append_field)
      }
      errors
    end

    # Returns error messages.
    # * Returns nil, if no errors are associated with the specified attribute.
    # * Returns the error message, if one error is associated with the specified attribute.
    # * Returns an array of error messages, if more than one error is associated with the specified attribute.
    # And for GetText,
    # * If the error messages include %{fn}, it returns formatted text such as "foo %{fn}" => "foo Field"
    # * else, the error messages are prepended the field name such as "foo" => "foo" (Same as default behavior).
    # Note that this behaviour is different from full_messages.
    def on_with_gettext(attribute)
      # e.g.) foo field: "%{fn} foo" => "Foo foo", "foo" => "foo". 
      errors = localize_error_messages(false)[attribute.to_s]
      return nil if errors.nil?
      errors.size == 1 ? errors.first : errors
    end
    alias_method_chain :on, :gettext 
    alias :[] :on

    # Returns all the full error messages in an array.
    # * If the error messages include %{fn}, it returns formatted text such as "foo %{fn}" => "foo Field"
    # * else, the error messages are prepended the field name such as "foo" => "Field foo" (Same as default behavior).
    # As L10n, first one is recommanded because the order of subject,verb and others are not same in languages.
    def full_messages_with_gettext
      full_messages = []
      errors = localize_error_messages
      errors.each_key do |attr|
        errors[attr].each do |msg|
	  next if msg.nil?
	  full_messages << msg
	end
      end
      full_messages
    end
    alias_method_chain :full_messages, :gettext 
  end
end