package SL::Controller::Helper::Paginated;

use strict;

use Exporter qw(import);
our @EXPORT = qw(make_paginated get_paginate_spec get_current_paginate_params _save_current_paginate_params _get_models_handler_for_paginated _callback_handler_for_paginated);

use constant PRIV => '__paginatedhelper_priv';

my $controller_paginate_spec;

sub make_paginated {
  my ($class, %specs)       = @_;

  $specs{MODEL}           ||=  $class->controller_name;
  $specs{MODEL}             =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
  $specs{PER_PAGE}        ||= "SL::DB::Manager::$specs{MODEL}"->default_objects_per_page;
  $specs{FORM_PARAMS}     ||= [ qw(page per_page) ];
  $specs{ONLY}            ||= [];
  $specs{ONLY}              = [ $specs{ONLY} ] if !ref $specs{ONLY};

  $controller_paginate_spec = \%specs;

  my %hook_params           = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
  $class->run_before('_save_current_paginate_params', %hook_params);

  SL::Controller::Helper::GetModels::register_get_models_handlers(
    $class,
    callback   => '_callback_handler_for_paginated',
    get_models => '_get_models_handler_for_paginated',
    ONLY       => $specs{ONLY},
  );

  # $::lxdebug->dump(0, "CONSPEC", \%specs);
}

sub get_paginate_spec {
  my ($class_or_self) = @_;

  return $controller_paginate_spec;
}

sub get_current_paginate_params {
  my ($self, %params)   = @_;

  my $spec              = $self->get_paginate_spec;

  my $priv              = $self->{PRIV()} || {};
  $params{page}         = $priv->{page}     unless defined $params{page};
  $params{per_page}     = $priv->{per_page} unless defined $params{per_page};

  my %paginate_params   =  (
    page                => ($params{page}     * 1) || 1,
    per_page            => ($params{per_page} * 1) || $spec->{PER_PAGE},
  );

  my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => {});
  %paginate_params      = (
    %paginate_params,
    num_pages    => $calculated_params->{max},
    common_pages => $calculated_params->{common},
  );

  # $::lxdebug->dump(0, "get_current_paginate_params: ", \%paginate_params);

  return %paginate_params;
}

#
# private functions
#

sub _save_current_paginate_params {
  my ($self)        = @_;

  my $paginate_spec = $self->get_paginate_spec;
  $self->{PRIV()}   = {
    page            => $::form->{ $paginate_spec->{FORM_PARAMS}->[0] } || 1,
    per_page        => $::form->{ $paginate_spec->{FORM_PARAMS}->[1] } * 1,
  };

  # $::lxdebug->message(0, "saving current paginate params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
}

sub _callback_handler_for_paginated {
  my ($self, %params) = @_;
  my $priv            = $self->{PRIV()} || {};

  if ($priv->{page}) {
    my $paginate_spec                             = $self->get_paginate_spec;
    $params{ $paginate_spec->{FORM_PARAMS}->[0] } = $priv->{page};
    $params{ $paginate_spec->{FORM_PARAMS}->[1] } = $priv->{per_page} if $priv->{per_page};
  }

  # $::lxdebug->dump(0, "CB handler for paginated; params nach modif:", \%params);

  return %params;
}

sub _get_models_handler_for_paginated {
  my ($self, %params)    = @_;
  $params{model}       ||= $self->get_paginate_spec->{MODEL};

  "SL::DB::Manager::$params{model}"->paginate($self->get_current_paginate_params, args => \%params);

  # $::lxdebug->dump(0, "GM handler for paginated; params nach modif:", \%params);

  return %params;
}

1;
__END__

=pod

=encoding utf8

=head1 NAME

SL::Controller::Helper::Paginated - A helper for semi-automatic handling
of paginating lists of database models in a controller

=head1 SYNOPSIS

In a controller:

  use SL::Controller::Helper::GetModels;
  use SL::Controller::Helper::Paginated;

  __PACKAGE__->make_paginated(
    MODEL       => 'BackgroundJobHistory',
    ONLY        => [ qw(list) ],
    FORM_PARAMS => [ qw(page per_page) ],
  );

  sub action_list {
    my ($self) = @_;

    my $paginated_models = $self->get_models;
    $self->render('controller/list', ENTRIES => $paginated_models);
  }

In said template:

  [% USE L %]

  <table>
   <thead>
    <tr>
     ...
    </tr>
   </thead>

   <tbody>
    [% FOREACH entry = ENTRIES %]
     <tr>
      ...
     </tr>
    [% END %]
   </tbody>
  </table>

  [% L.paginate_controls %]

=head1 OVERVIEW

This specialized helper module enables controllers to display a
paginatable list of database models with as few lines as possible. It
can also be combined trivially with the L<SL::Controller::Sorted>
helper for sortable lists.

For this to work the controller has to provide the information which
indexes are eligible for paginateing etc. by a call to
L<make_paginated> at compile time.

The underlying functionality that enables the use of more than just
the paginate helper is provided by the controller helper
C<GetModels>. See the documentation for L<SL::Controller::Sorted> for
more information on it.

A template can use the method C<paginate_controls> from the layout
helper module C<L> which renders the links for navigation between the
pages.

This module requires that the Rose model managers use their C<Paginated>
helper.

The C<Paginated> helper hooks into the controller call to the action via
a C<run_before> hook. This is done so that it can remember the paginate
parameters that were used in the current view.

=head1 PACKAGE FUNCTIONS

=over 4

=item C<make_paginated %paginate_spec>

This function must be called by a controller at compile time. It is
uesd to set the various parameters required for this helper to do its
magic.

The hash C<%paginate_spec> can include the following parameters:

=over 4

=item * C<MODEL>

Optional. A string: the name of the Rose database model that is used
as a default in certain cases. If this parameter is missing then it is
derived from the controller's package (e.g. for the controller
C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
C<BackgroundJobHistory>).

=item * C<PER_PAGE>

Optional. An integer: the number of models to return per page.

Defaults to the underlying database model's default number of models
per page.

=item * C<FORM_PARAMS>

Optional. An array reference with exactly two strings that name the
indexes in C<$::form> in which the current page's number (the first
element in the array) and the number of models per page (the second
element in the array) are stored.

Defaults to the values C<page> and C<per_page> if missing.

=item * C<ONLY>

Optional. An array reference containing a list of action names for
which the paginate parameters should be saved. If missing or empty then
all actions invoked on the controller are monitored.

=back

=back

=head1 INSTANCE FUNCTIONS

These functions are called on a controller instance.

=over 4

=item C<get_paginate_spec>

Returns a hash containing the currently active paginate
parameters. The following keys are returned:

=over 4

=item * C<page>

The currently active page number (numbering starts at 1).

=item * C<per_page>

Number of models per page (at least 1).

=item * C<num_pages>

Number of pages to display (at least 1).

=item * C<common_pages>

An array reference with one hash reference for each possible
page. Each hash ref contains the keys C<active> (C<1> if that page is
the currently active page), C<page> (the page number this hash
reference describes) and C<visible> (whether or not it should be
displayed).

=back

=item C<get_current_paginate_params>

Returns a hash reference to the paginate spec structure given in the call
to L<make_paginated> after normalization (hash reference construction,
applying default parameters etc).

=back

=head1 BUGS

Nothing here yet.

=head1 AUTHOR

Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>

=cut
