module Garage
  module PaginatingResponder
    def display(resource, *args)
      if @options[:paginate]
        resource = paginate resource
      end
      super(resource, *args)
    end

    def max_per_page=(count)
      @max_per_page = count
    end

    def reveal_total!
      @options.delete(:hard_limit)
    end

    private

    def distinct?
      !!@options[:distinct_by]
    end

    def hide_total?
      !!@options[:hard_limit]
    end

    def hard_limit
      @options[:hard_limit]
    end

    def max_per_page
      @options[:max_per_page] || @max_per_page || 100
    end

    def set_total_count(rs, per_page)
      if hard_limit
        limit = hard_limit
        rs.instance_variable_set(:@total_count, limit)
      end
    end

    def total_count(rs)
      if distinct?
        rs.total_count(@options[:distinct_by], distinct: true)
      else
        rs.total_count
      end
    end

    def paginate(rs)
      @options[:hard_limit] ||= 1000 if @options[:hide_total] # backward compat for hide_total

      per_page = [ max_per_page, (controller.params[:per_page] || @options[:per_page] || 20).to_i ].min

      rs = rs.page(controller.params[:page] || 1).per(per_page)

      set_total_count(rs, per_page)

      unless hide_total?
        controller.response.headers['X-List-TotalCount'] = total_count(rs).to_s
      end

      # FIXME construct_links must be called after calling rs.total_count to avoid invalid count cache
      construct_links(rs, per_page)

      if hide_total?
        if rs.offset_value > hard_limit
          rs = []
        elsif rs.offset_value + per_page > hard_limit
          rs = rs.slice 0, (hard_limit - rs.offset_value) # becomes Array here, and hope it's ok
        end
      end

      rs
    end

    def construct_links(rs, per_page)
      build_link_hash(rs, links={})
      add_link_header(links, per_page) unless links.empty?
    end

    def build_link_hash(rs, links)
      unless rs.first_page?
        links[:first] = 1
        links[:prev]  = rs.current_page - 1
      end

      if rs.current_page < rs.total_pages
        links[:next] = rs.current_page + 1
      end

      unless rs.last_page? || hide_total? || rs.total_pages.zero?
        links[:last] = rs.total_pages
      end
    end

    def build_path_for(params)
      parameters = controller.request.query_parameters.merge(params).tap {|p| p.delete(:access_token) }
      "#{controller.request.path}?#{parameters.to_query}"
    end

    def add_link_header(links, per_page)
      headers = []
      links.each do |rel, page|
        url = build_path_for(:page => page, :per_page => per_page)
        headers << "<#{url}>; rel=\"#{rel}\"; page=\"#{page}\""
      end
      controller.response.headers['Link'] = headers.join ', '
    end
  end
end

