# frozen_string_literal: true

require 'fast_gettext/cache'

module FastGettext
  # Responsibility:
  #  - store data threadsafe
  #  - provide error messages when repositories are unconfigured
  #  - accept/reject locales that are set by the user
  module Storage
    class NoTextDomainConfigured < RuntimeError
      def to_s
        "Current textdomain (#{FastGettext.text_domain.inspect}) was not added, use FastGettext.add_text_domain !"
      end
    end

    DEFAULT_PLURALIZATION_RULE = ->(i) { i != 1 }

    [:available_locales, :_locale, :text_domain, :pluralisation_rule].each do |method_name|
      key = "fast_gettext_#{method_name}"
      eval <<-RUBY, nil, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
        def #{method_name}=(value)
          switch_cache if Thread.current[:#{key}] != Thread.current[:#{key}] = value
        end
      RUBY
    end

    def _locale
      Thread.current[:fast_gettext__locale]
    end
    private :_locale, :_locale=

    def available_locales
      locales = Thread.current[:fast_gettext_available_locales] || default_available_locales
      return unless locales

      locales.map(&:to_s)
    end

    # cattr_accessor with defaults
    [
      [:default_available_locales, "nil"],
      [:default_text_domain, "nil"],
      [:cache_class, "FastGettext::Cache"]
    ].each do |name, default|
      eval <<-RUBY, nil, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
        @@#{name} = #{default}
        def #{name}=(value)
          @@#{name} = value
          switch_cache
        end

        def #{name}
          @@#{name}
        end
      RUBY
    end

    def text_domain
      Thread.current[:fast_gettext_text_domain] || default_text_domain
    end

    # if overwritten by user( FastGettext.pluralisation_rule = xxx) use it,
    # otherwise fall back to repo or to default lambda
    def pluralisation_rule
      Thread.current[:fast_gettext_pluralisation_rule] || current_repository.pluralisation_rule || DEFAULT_PLURALIZATION_RULE
    end

    def cache
      Thread.current[:fast_gettext_cache] ||= cache_class.new
    end

    def reload!
      cache.reload!
      translation_repositories.values.each(&:reload)
    end

    # global, since re-parsing whole folders takes too much time...
    @@translation_repositories = {} # rubocop:disable Style/ClassVars
    def translation_repositories
      @@translation_repositories
    end

    def current_repository
      translation_repositories[text_domain] || raise(NoTextDomainConfigured)
    end

    def key_exist?(key)
      !!(cached_find key)
    end

    def cached_find(key)
      cache.fetch(key) { current_repository[key] }
    end

    def cached_plural_find(*keys)
      key = '||||' + keys * '||||'
      cache.fetch(key) { current_repository.plural(*keys) }
    end

    def expire_cache_for(key)
      cache.delete(key)
    end

    def locale
      _locale || (default_locale || (available_locales || []).first || 'en')
    end

    def locale=(new_locale)
      set_locale(new_locale)
    end

    # for chaining: puts set_locale('xx') == 'xx' ? 'applied' : 'rejected'
    # returns the current locale, not the one that was supplied
    # like locale=(), whoes behavior cannot be changed
    def set_locale(new_locale) # rubocop:disable Naming/AccessorMethodName
      new_locale = best_locale_in(new_locale)
      self._locale = new_locale
      locale
    end

    @@default_locale = nil # rubocop:disable Style/ClassVars
    def default_locale=(new_locale)
      @@default_locale = best_locale_in(new_locale) # rubocop:disable Style/ClassVars
      switch_cache
    end

    def default_locale
      @@default_locale
    end

    # Opera: de-DE,de;q=0.9,en;q=0.8
    # Firefox de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
    # IE6/7 de
    # nil if nothing matches
    def best_locale_in(locales)
      formatted_sorted_locales(locales).each do |candidate|
        return candidate unless available_locales
        return candidate if available_locales.include?(candidate)
        return candidate[0..1] if available_locales.include?(candidate[0..1]) # available locales include a langauge
      end
      nil # nothing found im sorry :P
    end

    # temporarily switch locale for a block
    # FastGettext.with_locale 'xx' { _('cars') }
    def with_locale(temp_locale)
      current_locale = locale
      set_locale temp_locale
      yield
    ensure
      set_locale current_locale
    end

    # turn off translation if none was defined to disable all resulting errors
    def silence_errors
      require 'fast_gettext/translation_repository/base'
      translation_repositories[text_domain] ||= TranslationRepository::Base.new('x', path: 'locale')
    end

    private

    # de-de,DE-CH;q=0.9 -> ['de_DE','de_CH']
    def formatted_sorted_locales(locales)
      found = weighted_locales(locales).reject(&:empty?).sort_by(&:last).reverse # sort them by weight which is the last entry
      found.flatten.map { |l| format_locale(l) }
    end

    # split the locale and seperate it into different languages
    # de-de,de;q=0.9,en;q=0.8 => [['de-de','de','0.5'], ['en','0.8']]
    def weighted_locales(locales)
      locales = locales.to_s.gsub(/\s/, '')
      found = [[]]
      locales.split(',').each do |part|
        if part.include? ';q=' # contains language and weight ?
          found.last << part.split(/;q=/)
          found.last.flatten!
          found << []
        else
          found.last << part
        end
      end
      found
    end

    # de-de -> de_DE
    def format_locale(locale)
      locale.sub(/^([a-zA-Z]{2,3})[-_]([a-zA-Z]{2,3})$/) { $1.downcase + '_' + $2.upcase }
    end

    def switch_cache
      cache.switch_to(text_domain, locale)
    end
  end
end
