module Wice
  class << self

    # a flag storing whether the saved query class is a valid storage for saved queries
    @@model_validated = false

    def assoc_list_to_hash(assocs) #:nodoc:
      head = assocs[0]
      tail = assocs[1..-1]

      if tail.blank?
        head
      elsif tail.size == 1
        {head => tail[0]}
      else
        {head => assoc_list_to_hash(tail)}
      end
    end

    def build_includes(existing_includes, new_assocs) #:nodoc:
      new_includes = if new_assocs.blank?
        existing_includes
      else
        existing_includes = if existing_includes.is_a?(Symbol)
          [existing_includes]
        elsif existing_includes.nil?
          []
        else
          existing_includes
        end

        assocs_as_hash = assoc_list_to_hash(new_assocs)
        build_includes_int(existing_includes, assocs_as_hash)
      end

      if new_includes.is_a?(Array) && new_includes.size == 1
        new_includes[0]
      else
        new_includes
      end
    end

    def build_includes_int(includes, assocs) #:nodoc:
      if includes.is_a?(Array)
        build_includes_includes_is_array(includes, assocs)
      elsif includes.is_a?(Hash)
        build_includes_includes_is_hash(includes, assocs)
      end
    end

    # TODO: refactor
    def build_includes_includes_is_hash(includes, assocs) #:nodoc:

      includes_key   = includes.keys[0]
      includes_value = includes.values[0]

      if assocs.is_a?(Hash)
        assocs_key   = assocs.keys[0]
        assocs_value = assocs.values[0]

        if includes_value.is_a?(Symbol) && includes_value == assocs_key
          {includes_key => assocs}
        elsif includes_value.is_a?(Hash)
          if includes_value.keys[0] == assocs_key
            if includes_value.values[0] == assocs_value
              {includes_key => assocs}
            else
              {includes_key => [includes_value.values[0], assocs_value]}
            end
          end
        end
      elsif includes_value == assocs
        {includes_key => assocs}
      else
        includes
      end
    end

    def build_includes_includes_is_array(includes, assocs) #:nodoc:

      hash_keys = Hash[
        *(
          includes
          .each_with_index
          .to_a
          .select{ |e, _idx| e.is_a?(Hash)}
          .map{ |hash, idx| [ hash.keys[0], idx ] }
          .flatten
        )
      ]

      key_to_search, finished = if assocs.is_a?(Hash)
        [assocs.keys[0], false]
      else
        [assocs, true]
      end

      if idx = includes.index(key_to_search)
        if finished #  [:a, :b, :c]  vs  :a
          includes
        else        #  [:a, :b, :c]  vs  {:a => x}
          includes[idx] = assocs
          includes
        end

      elsif hash_keys.key?(key_to_search)
        if finished # [{a: :x}, :b, :c, :d, :e] vs :a
          includes
        else
          hash_idx = hash_keys[key_to_search]
          assocs_value = assocs[key_to_search]
          includes[hash_idx] = build_includes_int(includes[hash_idx], assocs_value)
          includes
        end

      else  # [:a, :b, :c]  vs  :x
            # [:a, :b, :c]  vs  {:x => y}
        includes + [assocs]
      end

    end

    # checks whether the class is a valid storage for saved queries
    def validate_query_model(query_store_model)  #:nodoc:
      unless query_store_model.respond_to?(:list)
        raise ::Wice::WiceGridArgumentError.new("Model for saving queries #{query_store_model.class.name} is invalid - there is no class method #list defined")
      end
      arit = query_store_model.method(:list).arity
      unless arit == 2
        raise ::Wice::WiceGridArgumentError.new("Method list in the model for saving queries #{query_store_model.class.name} has wrong arity - it should be 2 instead of #{arit}")
      end
      @@model_validated = true
    end

    # Retrieves and constantizes (if needed ) the Query Store model
    def get_query_store_model #:nodoc:
      query_store_model = Wice::ConfigurationProvider.value_for(:QUERY_STORE_MODEL)
      query_store_model = query_store_model.constantize if query_store_model.is_a? String
      raise ::Wice::WiceGridArgumentError.new('Defaults::QUERY_STORE_MODEL must be an ActiveRecord class or a string which can be constantized to an ActiveRecord class') unless query_store_model.is_a? Class
      validate_query_model(query_store_model) unless @@model_validated
      query_store_model
    end

    def get_string_matching_operators(model)   #:nodoc:
      if defined?(Wice::Defaults::STRING_MATCHING_OPERATORS) && (ops = Wice::ConfigurationProvider.value_for(:STRING_MATCHING_OPERATORS)) &&
          ops.is_a?(Hash) && (str_matching_operator = ops[model.connection.class.to_s])
        str_matching_operator
      else
        Wice::ConfigurationProvider.value_for(:STRING_MATCHING_OPERATOR)
      end
    end

    def deprecated_call(old_name, new_name, opts) #:nodoc:
      if opts[old_name] && !opts[new_name]
        opts[new_name] = opts[old_name]
        opts.delete(old_name)
        STDERR.puts "WiceGrid: Parameter :#{old_name} is deprecated, use :#{new_name} instead!"
      end
    end

    def log(message) #:nodoc:
      ActiveRecord::Base.logger.info('WiceGrid: ' + message) if ActiveRecord::Base.logger
    end
  end

  module NlMessage #:nodoc:
    class << self
      def [](key) #:nodoc:
        I18n.t(key, scope: 'wice_grid')
      end
    end
  end

  module ConfigurationProvider #:nodoc:
    class << self
      def value_for(key, strict: true) #:nodoc:
        if Wice::Defaults.const_defined?(key)
          Wice::Defaults.const_get(key)
        else
          if strict
            raise WiceGridMissingConfigurationConstantException.new("Could not find constant #{key} in the configuration file!" \
                ' It is possible that the version of WiceGrid you are using is newer than the installed configuration file in config/initializers. ' \
                "Constant Wice::Defaults::#{key} is missing  and you need to add it manually to wice_grid_config.rb or run the generator task=:\n" \
                '   rails g wice_grid:install')
          end # else nil
        end
      end
    end
  end

  module Defaults  #:nodoc:
  end

  module ExceptionsMixin  #:nodoc:
    def initialize(str)  #:nodoc:
      super('WiceGrid: ' + str)
    end
  end
  class WiceGridArgumentError < ArgumentError #:nodoc:
    include ExceptionsMixin
  end
  class WiceGridException < Exception #:nodoc:
    include ExceptionsMixin
  end
  class WiceGridMissingConfigurationConstantException < Exception #:nodoc:
    include ExceptionsMixin
  end
end
