module Gibberish
  
  class Language < ActiveRecord::Base
    has_many :translations
    acts_as_cached
    after_save :invalidate_cache

    # I'm just going to hold onto these really strongly
    # This will be a bug if we change the actual languages a lot
    @@languages_by_name = {}
    @@languages_by_id = {}

    def self.find_cached_by_name(name)
      @@languages_by_name[name] ||= get_cache("find_by_name:#{name}") do
        find(:first, :conditions => {:name => name.to_s})
      end
    end

    def self.find_cached_by_id(id)
      @@languages_by_id[id] ||= get_cache id
    end

    def invalidate_cache
      clear_cache "find_by_name:#{name}"
      self.class.invalidate_cache
    end

    def self.invalidate_cache
      @@languages_by_name = {}
      @@languages_by_id = {}
    end

  end
  
  class Translation < ActiveRecord::Base
    belongs_to :language
    acts_as_cached
    after_save :invalidate_cache
    after_destroy :invalidate_cache
    validates_length_of :key, :within => 1..100
    validates_uniqueness_of :key, :scope => :language_id
    validates_inclusion_of :format, :in => ['block','inline'], :on => :create

    attr_accessor :arguments

    def self.full_cached
      get_cache("everything") do
        returning({}) do |rv|
          RAILS_DEFAULT_LOGGER.info "Loading translations."
          Translation.find(:all, :include => :language).group_by{|l| [l.language_id, l.key]}.each do |p|
            rv[p.first] = p.last.last
            rv[[p.first.first, p.first.last.to_sym]] = p.last.last
          end
        end
      end
    end

    def self.find_cached_by_language_and_key(lang, key)
      full_cached[[lang.is_a?(Language) ? lang.id : lang, key]].first
    end

    def invalidate_cache
      self.class.clear_cache "everything"
      Gibberish::Localize::reset_translations
    end
    
    def method_missing(name, *args, &block)
      return super if @attributes.include?(name.to_s)
      if self.value.respond_to?(name)
        if block_given?
          self.interpolated_value.send(name, *args, &block)
        else
          self.interpolated_value.send(name, *args)
        end
      else
        super
      end
    end
    
    def interpolated_value
      args = (arguments || []).dup 
      if args.last.is_a? Hash
        interpolate_with_hash(self.value, args.last)
      else
        interpolate_with_strings(self.value, args)
      end
    end
    alias_method :to_s, :interpolated_value
    alias_method :to_str, :interpolated_value

    def interpolate_with_hash(string, hash)
      hash.inject(string) do |target, (search, replace)|
        if search and replace
          target.gsub("{#{search}}", replace)
        else
          RAILS_DEFAULT_LOGGER.warn "Problem interpolating #{string} with #{hash.inspect}"
          target
        end
      end 
    end

    def interpolate_with_strings(string, strings)
      string.gsub(/\{\w+\}/) { strings.shift }
    end
    
    def to_html
      if Translation.suppress_html_wrapper?
        interpolated_value
      else
        tagname = (self.format == "block") ? "div" : "span"
        %Q{<#{tagname} class="translated key_#{key}" lang="#{language.name}">#{interpolated_value}</#{tagname}>}
      end
    end
                    
    # Execute a block of code while suppressing the HTML wrapping that would
    # otherwise take place when invoking .to_html
    def self.suppressing_html_wrapping(&block)
      old_val = @suppress_html
      @suppress_html = true
      begin 
        yield
      ensure
        @suppress_html = old_val
      end
    end
    
    def self.suppress_html_wrapper?
      @suppress_html
    end    
  end
  
  # this is an adapter that exposes a Hash access method
  # but calls to the model for the correct language.
  class Translator

    def initialize(language)
      @language = language
    end

    def translate(key)
      all_translations[[@language.is_a?(Language) ? @language.id : @language, key]]
    end
    alias_method :[], :translate

    def all_translations
      @cached ||= Translation.full_cached
    end

    def reset_translations
      @cached = nil
    end
  end
  
  module Localize
    def load_languages_with_db!
      Language.find(:all).each do |lang|
        @@languages[lang.name.to_sym] = Translator.new(lang)
      end
    end
    alias_method_chain :load_languages!, :db
    def create_translation(string, key, *args)
      format = args.first.delete(:format) if args.first.is_a?(Hash)
      format ||= :inline
      returning Translation.create(:value => string,
                                  :key => key.to_s,
                                  :language_id => Language.find_cached_by_name(current_language).id,
                                  :format => format.to_s) do |translation|
        RAILS_DEFAULT_LOGGER.warn "Failed to create translation: #{translation.errors.full_messages}" unless translation.errors.empty?
      end
    end

    def translate_with_db(string, key, *args)
      return if reserved_keys.include? key
      target = translations[key] || create_translation(string,key,*args)
      arguments = extract_arguments(args)
      interpolate_string(target.dup, *arguments.dup)
    end
    alias_method_chain :translate, :db

    def self.reset_translations
      @@languages.each { |k,v| v.reset_translations }
      Language.invalidate_cache
    end

    private

      def extract_arguments(args)
         if (options = args.first).is_a?(Hash)
           [:format].each {|opt| options.delete(opt)}
         end
         return args
      end

      def interpolate_string_with_db(string, *args)
        if string.is_a? Translation
          string.arguments = args
          string
        else
          interpolate_string_without_db(string, *args)
        end
      end
      alias_method_chain :interpolate_string, :db
  end

end

class ActionMailer::Base
  class << self
    # We want .to_html to be active when invoking action mailer through the
    # create_XXX functions so that we can edit them in the menagerie. But,
    # we want it to be inactive through the deliver_XXX functions so that end
    # users don't see the extra markup. We make that happen here.
    def method_missing_with_html_suppression(method_symbol, *parameters)
      case method_symbol.id2name
      when /^deliver_([_a-z]\w*)/
        # No HTML for mails that are being delivered
        Gibberish::Translation.suppressing_html_wrapping do
          method_missing_without_html_suppression(method_symbol, *parameters)
        end
      else
        # Pass through for the others
        method_missing_without_html_suppression(method_symbol, *parameters)
      end
    end
    alias_method_chain :method_missing, :html_suppression
  end
end
