package Class::DBI::Pageset;

###########################################################################
# Class::DBI::Pageset
# Mark Grimes
# $Id: NAMESPACE.pm,v 1.3 2006/11/29 02:40:22 mgrimes Exp $
#
# A flexible pager utility for Class::DBI
# Copyright (c) 2008 Mark Grimes (mgrimes@cpan.org).
# All rights reserved. This program is free software; you can redistribute
# it and/or modify it under the same terms as Perl itself.
#
# Heavily inspired by (read: stolen from) Class::DBI::Pager and
# DBIx::Class::ResultSet::Data::Pageset. Thanks!
#
###########################################################################

use strict;
use warnings;

our $VERSION = '0.10';
our $AUTOLOAD;

use Class::DBI 0.90;
our $PAGER_IMPLEMENTATION = 'Data::Pageset';

sub _croak { require Carp; Carp::croak(@_); }

sub import {
    my $class = shift;
    my $pkg   = caller(0);
    no strict 'refs';
    *{"$pkg\::pager"} = \&_pager;

    $PAGER_IMPLEMENTATION = shift if @_;
    ( my $pager_implementation_file = "$PAGER_IMPLEMENTATION.pm" ) =~ s{::}{/}g;
    eval { require $pager_implementation_file; };
    _croak "Unable to use $PAGER_IMPLEMENTATION: $@" if $@;
}

sub _pager {
    my $pkg  = shift;
    my @args = qw( entries_per_page current_page pages_per_set mode );
    my $opts = {};
    while ( @_ and not ref $_[0] ) {
        $opts->{ shift @args } = shift;
    }
    my $ref = ref $_[0] eq 'HASH' ? shift : {};
    return bless {
        pkg        => $pkg,
        pager      => undef,
        pager_opts => { %$opts, %$ref, },
      },
      __PACKAGE__;
}

sub AUTOLOAD {
    my $self = shift;
    ( my $method = $AUTOLOAD ) =~ s/.*://;
    if ( ref($self) && $self->{pkg}->can($method) ) {
        my $iter = $self->{pkg}->$method(@_);
        UNIVERSAL::isa( $iter, 'Class::DBI::Iterator' )
          or _croak("$method doesn't return Class::DBI::Itertor");
        my $pager_opts = $self->{pager_opts};
        my $pager = $self->{pager} = $PAGER_IMPLEMENTATION->new( {
                total_entries => $iter->count,
                %$pager_opts,
        } );
        return $iter->slice( $pager->first - 1, $pager->last - 1 );
    } elsif ( $PAGER_IMPLEMENTATION->can($method) ) {
        $self->{pager} or _croak("Can't call pager methods before searching");
        return $self->{pager}->$method(@_);
    } else {
        _croak(
            qq(Can't locate object method "$method" via package ) . ref($self)
              || $self );
    }
}

sub DESTROY { }

1;

__END__

=head1 NAME

Class::DBI::Pageset - A flexible pager utility for Class::DBI using Data::Pageset

=head1 SYNOPSIS

  package CD;
  use base qw(Class::DBI);
  use Class::DBI::Pageset;        # just use it
  __PACKAGE__->set_db(...);

  # then, in client code!
  package main;
  use CD;
  my $pager = CD->pager( {
            entries_per_page => 20,
            current_page     => 1,
            pages_per_set    => 10,
        } );
  my @disks = $pager->retrieve_all;

=head1 DESCRIPTION

C<Class::DBI::Pageset> is a plugin for C<Class::DBI> that integrates 
C<Data::Pageset> into C<Class::DBI> with minimal fuss. This enables you
to search via C<Class::DBI> and grouping results into pages and page sets.

This module is very similar to Tatsuhiko Miyagawa's very nice
C<Class::DBI::Pager> module, but uses C<Data::Pageset> to create the pager. 
C<Data::Pageset> provides a more flexible pager, which is better suited to
searches that return many pages. This is not necessarily very efficient (see 
C<NOTE> below for more).

=head1 EXAMPLE

  # Controller: (MVC's C)
  my $query    = CGI->new;
  my $template = Template->new;

  my $pager    = Film->pager({ 
    entries_per_page => 20,
    current_page     => $query->param('page') || 1,
    pages_per_set    => 5, 
  });
  my $movies   = $pager->retrieve_all;
  $template->process($input, {
      movies => $movies,
      pager  => $pager,
  });

  # View: (MVC's V)
  Matched [% pager.total_entries %] items.

  [% WHILE (movie = movies.next) %]
  Title: [% movie.title | html %]
  [% END %]

  ### navigation like:   ... 5 [6] 7 8 9 ...
  [% IF pager.previous_set %] 
    <a href="display?page=[% pager.previous_set %]">...</a>
  [% END %]
  [% FOREACH num = [ pager.pages_in_set ] %]
  [% IF num == pager.current_page %] [[% num %]]
  [% ELSE %]<a href="display?page=[% num %]">[% num %]</a>[% END %]
  [% END %]
  [% IF pager.next_set %]
    <a href="display?page=[% pager.next_set %]">...</a>
  [% END %]

=head1 METHODS

=over 4

=item pager()

    my $pager = Film->pager({ 
        entries_per_page => 20,
        current_page     => $query->param('page') || 1,
        pages_per_set    => 5, 
        mode             => 'slide',
    });

This is the constructor for the pager. See C<Data::Pageset> for more on the
parameters. The C<$pager> object can then be used as a normal C<Class::DBI>
object for searching.

=item total_entries()

=item entries_per_page()

=item current_page()

=item entries_on_this_page()

=item first_page()

=item last_page()

=item first()

=item last()

=item previous_page()

=item next_page()

=item pages_in_navigatio()

=item pages_per_set()

=item previous_set()

=item next_set()

=item pages_in_set()

See C<Data::Pageset>.

=back

=head1 NOTE

This modules internally retrieves itertors, then creates Data::Page object
for paging utility. Using SQL clauses LIMIT and/or OFFSET with DBIx::Pager
might be more memory efficient. As this module is geared to searches that
return many pages of results, it maybe more prone to inefficiencies than
C<Class::DBI::Pager>.

=head1 SEE ALSO

C<Class::DBI>, C<Data::Pageset>

Or for alternatives: C<Class::DBI::Pager>, C<DBIx::Class>,
C<DBIx::Class::ResultSet::Data::Pageset>

=head1 AUTHOR

Most of this code was shamelessly taken from the very nice
C<Class::DBI::Pager> by Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>.

Mark Grimes E<lt>mgrimes@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2008 by mgrimes

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.2 or,
at your option, any later version of Perl 5 you may have available.

=cut
