# This patch performs 2 functions
#
# 1. It caches all translations which drastically improves
#    translation performance in an LRU cache
#
# 2. It patches I18n so it only loads the translations it needs
#    on demand
#
# This patch depends on the convention that locale yml files must be named [locale_name].yml

module I18n

  # this accelerates translation a tiny bit (halves the time it takes)
  class << self
    alias_method :translate_no_cache, :translate
    alias_method :exists_no_cache?, :exists?
    alias_method :reload_no_cache!, :reload!
    LRU_CACHE_SIZE = 300

    def init_accelerator!
      @overrides_enabled = true
      reload!
    end

    def reload!
      @loaded_locales = []
      @cache = nil
      @overrides_by_site = {}

      reload_no_cache!
      ensure_all_loaded!
    end

    LOAD_MUTEX = Mutex.new
    def load_locale(locale)
      LOAD_MUTEX.synchronize do
        return if @loaded_locales.include?(locale)

        if @loaded_locales.empty?
          # load all rb files
          I18n.backend.load_translations(I18n.load_path.grep(/\.rb$/))

          # load plural rules from plugins
          DiscoursePluginRegistry.locales.each do |locale, options|
            if options[:plural]
              I18n.backend.store_translations(locale, i18n: { plural: options[:plural] })
            end
          end
        end

        # load it
        I18n.backend.load_translations(I18n.load_path.grep(/\.#{Regexp.escape locale}\.yml$/))

        @loaded_locales << locale
      end
    end

    def ensure_all_loaded!
      backend.fallbacks(locale).each { |l| ensure_loaded!(l) }
    end

    def search(query, opts = nil)
      locale = opts[:locale] || config.locale

      load_locale(locale) unless @loaded_locales.include?(locale)
      opts ||= {}

      target = opts[:backend] || backend
      results = opts[:overridden] ? {} : target.search(config.locale, query)

      regexp = /#{query}/i
      (overrides_by_locale(locale) || {}).each do |k, v|
        results.delete(k)
        results[k] = v if (k =~ regexp || v =~ regexp)
      end
      results
    end

    def ensure_loaded!(locale)
      @loaded_locales ||= []
      load_locale(locale) unless @loaded_locales.include?(locale)
    end

    # In some environments such as migrations we don't want to use overrides.
    # Use this to disable them over a block of ruby code
    def overrides_disabled
      @overrides_enabled = false
      yield
    ensure
      @overrides_enabled = true
    end

    def translate_no_override(*args)
      return translate_no_cache(*args) if args.length > 1 && args[1].present?

      options  = args.last.is_a?(Hash) ? args.pop.dup : {}
      key      = args.shift
      locale   = options[:locale] || config.locale

      @cache ||= LruRedux::ThreadSafeCache.new(LRU_CACHE_SIZE)
      k = "#{key}#{locale}#{config.backend.object_id}"

      @cache.getset(k) do
        translate_no_cache(key, options).freeze
      end
    end

    def overrides_by_locale(locale)
      return unless @overrides_enabled

      site = RailsMultisite::ConnectionManagement.current_db

      by_site = @overrides_by_site[site]
      by_site ||= {}

      if !by_site.has_key?(locale)
        # Load overrides
        translations_overrides = TranslationOverride.where(locale: locale).pluck(:translation_key, :value, :compiled_js)

        if translations_overrides.empty?
          by_site[locale] = {}
        else
          translations_overrides.each do |tuple|
            by_locale = by_site[locale] ||= {}
            by_locale[tuple[0]] = tuple[2] || tuple[1]
          end
        end

        @overrides_by_site[site] = by_site
      end

      by_site[locale].with_indifferent_access
    end

    def client_overrides_json(locale)
      client_json = (overrides_by_locale(locale) || {}).select { |k, _| k[/^(admin_js|js)\./] }
      MultiJson.dump(client_json)
    end

    def translate(*args)
      options  = args.last.is_a?(Hash) ? args.pop.dup : {}
      key      = args.shift
      locale   = options[:locale] || config.locale

      load_locale(locale) unless @loaded_locales.include?(locale)

      if @overrides_enabled
        overrides = {}

        backend.fallbacks(locale).each do |l|
          overrides[l] = overrides_by_locale(l)
        end

        if overrides.present?
          if options.present?
            options[:overrides] = overrides

            # I18n likes to use throw...
            catch(:exception) do
              return backend.translate(locale, key, options)
            end
          else
            overrides.each do |_k, v|
              if result = v[key]
                return result
              end
            end
          end
        end
      end

      translate_no_override(key, options)
    end

    alias_method :t, :translate

    def exists?(key, locale = nil)
      locale ||= config.locale
      load_locale(locale) unless @loaded_locales.include?(locale)
      exists_no_cache?(key, locale)
    end

  end
end