module CouchPotato
  module View
    class BaseViewSpec
      attr_reader :reduce_function, :lib, :list_name, :list_function, :design_document, :view_name, :klass, :options, :language
      attr_accessor :view_parameters

      private :klass, :options

      def initialize(klass, view_name, options, view_parameters)
        normalized_view_parameters = normalize_view_parameters view_parameters

        @list_name = normalized_view_parameters.delete(:list) || options[:list]
        @language = options[:language] || Config.default_language

        assert_valid_view_parameters normalized_view_parameters
        assert_sorted_false_not_with_startkey_endkey normalized_view_parameters
        @klass = klass
        @options = options
        @view_name = compute_view_name(view_name,
          options.key?(:digest_view_name) ? options[:digest_view_name] : Config.digest_view_names)
        @design_document = translate_to_design_doc_name(klass.to_s, @view_name, @list_name)
        @list_params = normalized_view_parameters.delete :list_params

        @list_function = klass.lists(@list_name) if @list_name
        @view_parameters = {}
        [:group, :include_docs, :descending, :group_level, :limit].each do |key|
          @view_parameters[key] = options[key] if options.include?(key)
        end
        @view_parameters.merge!(normalized_view_parameters)
        @view_parameters.merge!(@list_params) if @list_params
      end

      def process_results(results)
        if (filter = options[:results_filter])
          filter.call results
        else
          results
        end
      end

      private

      def compute_view_name(view_name, digest)
        if digest
          "#{view_name}-#{Digest::MD5.hexdigest(map_function + reduce_function.to_s)}"
        else
          view_name
        end
      end

      def normalize_view_parameters(params)
        hash = wrap_in_hash params
        remove_nil_stale(replace_range_key(hash))
      end

      def remove_nil_stale(params)
        params.reject{|name, value| name.to_s == 'stale' && value.nil?}
      end

      def wrap_in_hash(params)
        if params.is_a?(Hash)
          params
        else
          {:key => params}
        end
      end

      def replace_range_key(params)
        if((key = params[:key]).is_a?(Range))
          params.delete :key
          params[:startkey] = key.first
          params[:endkey] = key.last
        end
        params
      end

      def assert_valid_view_parameters(params)
        params.keys.each do |key|
          fail ArgumentError, "invalid view parameter: #{key}" unless valid_view_parameters.include?(key.to_s)
        end
      end

      def assert_sorted_false_not_with_startkey_endkey(params)
        used_key_params = params.keys & [:startkey, :endkey, :startkey_docid, :endkey_docid]
        if params[:sorted] == false && used_key_params.any?
          fail ArgumentError, "view parameter: `sorted: false` can not be combined with #{used_key_params.join(', ')}"
        end
      end

      def valid_view_parameters
        %w(list_params key keys startkey startkey_docid endkey endkey_docid limit stale descending skip group group_level reduce include_docs inclusive_end sorted)
      end

      def translate_to_design_doc_name(klass_name, view_name, list_name)
        klass_name = klass_name.dup
        klass_name.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
        klass_name.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
        klass_name.tr!('-', '_')
        doc_name = klass_name.downcase

        if CouchPotato::Config.split_design_documents_per_view
          doc_name += "_view_#{view_name}"  if view_name.present?
          doc_name += "_list_#{list_name}" if list_name.present?
        end
        doc_name
      end
    end
  end
end
