
module Pagify
  # pager is something help you paginate the data,
  # through the fetcher and counter you pass in.
  # e.g., for a array, the fetcher would be array[offset, per_page],
  # the counter would be array.size. and for data mapper's resource,
  # fetcher would be Model.all :offset => offset, :limit => per_page.
  # see ArrayPager and DataMapperPager, ActiveRecordPager, etc.
  # if you just simply need them or have a look at the source code
  # for example usage for Pager.
  class BasicPager
    include Enumerable
    attr_reader :fetcher, :counter
    attr_accessor :opts

    [:per_page, :null_page].each{ |attr|
      define_method(attr) do
        opts[attr]
      end

      define_method("#{attr}=") do |new_value|
        opts[attr] = new_value
      end
    }

    #  fetcher is function that fetch the data,
    #  counter is function that count the data,
    #  null_page indicates that pager sohuld
    # the default per_page is 20. you can reset per_page property later.
    def initialize opts = {}
      raise ArgumentError.new('missing fetcher and/or counter.') if
        !opts[:fetcher] || !opts[:counter]

      raise ArgumentError.new('fetcher or counter does not respond to call.') if
        !opts[:fetcher].respond_to?(:call) || !opts[:counter].respond_to?(:call)

      @opts     = {}
      @fetcher  = opts[:fetcher]
      @counter  = opts[:counter]

      self.per_page  = opts[:per_page]  || 20
      self.null_page = opts[:null_page] || true
    end

    # if two paginators are equal, then the properties of
    # per_page, fetcher, counter are all equal.
    def == rhs
      return false unless rhs
      self.per_page == rhs.per_page and
      self.fetcher  == rhs.fetcher  and
      self.counter  == rhs.counter
    end

    # for each page...
    def each; 1.upto(size){ |i| yield page(i) }; end

    # return all pages in an array
    def to_a; map{ |e| e }; end
    alias_method :pages, :to_a

    def page_exists? page
      page = normalize_page(page)
      offset = (page - 1) * per_page
      page > 0 && offset < entries_count
    end

    # create page instance by page number.
    # if page number you specified was not existed,
    # nil would be returned. note, page start at 1, not zero.
    def page page
      if page_exists?(page)
        return BasicPage.new(self, normalize_page(page))

      else
        if null_page
          return null_page_instance
        else
          return nil
        end

      end

    end
    alias_method :[], :page

    # return the amount of pages
    def size; (entries_count / per_page.to_f).ceil; end

    # simply call @counter.call
    def entries_count; counter.call; end

    # get the offset property about the page.
    # it is simply (page-1)*@per_page
    def offset page
      (normalize_page(page) - 1) * per_page
    end

    protected
    def reject_pager_opts opts
      opts.reject{ |key, value| [:per_page, :null_page].member?(key) }
    end

    # do nothing for basic pager
    def normalize_page page
      page
    end

    # return a null pager that stubs anything to 0
    def null_page_instance
      @null_page_instance ||= NullPage.new(self)
    end

  end # of BasicPager
end # of Pagify
