package Mojolicious::Plugin::Tables;
use Mojo::Base 'Mojolicious::Plugin';

use File::Basename 'dirname';
use File::Spec::Functions 'catdir';

use Mojolicious::Plugin::Tables::Model;

our $VERSION = '0.05';

sub register {
    my ($self, $app, $conf) = @_;

    my $log = $app->log;

    $app->helper(add_stash => sub {
        my ($c, $slot, $val) = @_;
        push @{$c->stash->{$slot} ||= []}, $val;
    });

    $app->helper(add_flash => sub {
        my ($c, $slot, $val) = @_;
        push @{$c->session->{new_flash}{$slot} ||= []}, $val;
    });

    $app->helper(shipped => sub {
        # special stash slot for sending json-able structs to js;
        # get/set logic here cloned from Mojo::Util::_stash.
        my $c = shift;
        my $shipped = $c->stash->{shipped} ||= {};
        return $shipped unless @_;
        return $shipped->{$_[0]} unless @_>1 || ref $_[0];
        my $values = ref $_[0]? $_[0] : {@_};
        @$shipped{keys %$values} = values %$values;
    });

    $app->config(default_theme=>'redmond');
    $app->defaults(layout => $conf->{layout} || 'tables');

    my $model_class = $conf->{model_class} ||= 'Mojolicious::Plugin::Tables::Model';
    eval "require $model_class" or die;

    $model_class->log($log);
    my $schema = $model_class->setup($conf);
    my $model = $schema->model;
    $app->config(model=>$model);

    my $plugin_resources = catdir dirname(__FILE__), 'Tables', 'resources';
    push @{$app->routes->namespaces}, 'Mojolicious::Plugin::Tables::Controller';
    push @{$app->renderer->paths}, catdir($plugin_resources, 'templates');
    push @{$app->static->paths},   catdir($plugin_resources, 'public');

    # Arrange for custom table-specific template overrides, when present.
    # We do this by wrapping the standard ep-handler with a
    # pre-processor to test for existence of the custom template.
    # Any internal caching by EP renderer is preserved even for overrides.

    my $handlers = $app->renderer->handlers;
    my $base_ep  = $handlers->{ep};
    $handlers->{ep} = sub {
        my ($renderer, $c, $output, $options) = @_;
        {
            my $table           = $c->stash('table') || last;
            my $custom_template = "$table/$options->{template}";
            my $custom_path     = $renderer->template_path ({%$options,
                                             template=>$custom_template}) || last;
            $options->{template} = $custom_template;
            $log->debug("custom 'tables' template at $custom_path") if $ENV{TABLES_DEBUG};
        }
        $base_ep->(@_)
    };

    my $r = $app->routes;
    $r->get('/' => sub{shift->redirect_to('tables')}) unless $conf->{nohome};

    my $crud_gets  = [qw/view edit del nuke navigate/];
    my $crud_posts = [qw/save/];

    for ($r->under('tables')                   ->to('auth#ok'   )) {
        for ($_->under()                       ->to('tables#ok' )) {
            $_->get                            ->to('#page'     );
            for ($_->under(':table')           ->to('#table_ok' )) {
                $_->any                        ->to('#table'    );
                $_->get('add')                 ->to('#add'      );
                $_->post('save')               ->to('#save'     );
                for ($_->under(':id')          ->to('#id_ok'    )) {
                    $_->get(':action'=>[action=>$crud_gets]     );
                    $_->post(':action'=>[action=>$crud_posts]   );
                    $_->get('add_child/:child')->to('#view'     );
                    $_->any(':children')       ->to('#children' );
                }
            }
        }
    }

    my @tablist = @{$model->{tablist}};
    $log->info ("'Tables' Framework enabled.");
    $log->debug("Route namespaces are..");
    $log->debug("--> $_") for @{$app->routes->namespaces};
    $log->debug("Renderer paths are..");
    $log->debug("--> $_") for @{$app->renderer->paths};
    $log->debug("Static paths are..");
    $log->debug("--> $_") for @{$app->static->paths};
    $log->info ("/tables routes are in place. ".scalar(@tablist)." tables available");
    $log->debug("--> $_") for @tablist;

    $log->debug($app->dumper($model)) if $ENV{TABLES_DEBUG};

}

1;
__END__

=encoding utf8

=head1 NAME

Mojolicious::Plugin::Tables -- Quickstart and grow a Tables-Maintenance application

=head1 SYNOPSIS

    # in a new Mojolicious app..
 
    $app->plugin( Tables => {connect_info=>['dbi:Pg:dbname="mydatabase"', 'uid', 'pwd']} );

    # when ready to grow..

    $app->plugin( Tables => {model_class => 'StuffDB'} );

=head1 DESCRIPTION

L<Mojolicious::Plugin::Tables> is a L<Mojolicious> plugin which helps you grow a high-featured web app by
starting with basic table / relationship maintenance and growing by overriding default behaviours.

By supplying 'connect_info' (DBI-standard connect parameters) you get a presentable database maintenance
web app, featuring full server-side support for paged lists of large tables using
'Datatables' (datatables.net), plus ajax handling for browsing 1-many relationships, plus
JQueryUI-styled select lists for editing many-to-1 picklists.

By supplying your own Model Class you can override most of the default behaviours
and build an enterprise-ready rdbms-based web app.

By supplying your own templates for context-specific calls you can start to give your app a truly 
specialised look and feel.

=head1 STATUS

This is an early release.  Guides and more override-hooks coming Real Soon Now.

=head1 GROWTH PATH

=head2 Ground Zero Startup

    tables dbi-connect-params..

The 'tables' script is supplied with this distribution.  Give it standard DBI-compatible parameters on the 
commandline and it will run a minimal web-server at localhost:3000 to allow maintenance on the named database.

=head2 Day One

In your Mojolicious 'startup'..

    $self->plugin(Tables => { connect_info => [ connect-param, connect-param, .. ] };

Add this line to a new Mojolicious app then run it using any Mojo-based server (morbo, prefork, hypnotoad..) to achieve exactly the same functionality as the 'tables' script.

=head2 Day Two

    # templates/:table/{view,edit,dml,page}.html.ep
    # e.g. 
    # templates/artist/view.html.ep
    <h1>Artist: <%= $row %></h1>

For any :table in your database, create override-templates as required.
e.g. The above code will give a very different look-and-feel when viewing a single Artist, but all other
pages are unchanged.  For better examples and to see which stash-variables are available, start by 
copying the distribution templates from ../Plugin/Tables/resources/templates into your own template area.

=head2 Infinity and Beyond

    $self->plugin(Tables => { model_class => 'MyDB' } );

Prepare your own model_class to override the default database settings which "Tables" has determined from the 
database.  This class (and its per-table descendants) can be within or without your Mojolicious application.
C<model_class> implements the specification given in L<Mojolicious::Plugin::Tables::Model>.
This lets you start to customise and grow your web app.

=head1 CONFIGURATION

    $app->plugin(Tables => $conf) 

Where the full list of configuration options is:

=head3 layout

Provide an alternative 'wrapper' layout; typically the one from your own application.  If you prepare one of these you will need
to include most of the layout arrangements in the Day-One layout, i.e. the one at resources/templates/layouts/tables.html.ep.
The easiest approach is to start by copying the packaged version into your own application and then change its look and feel to
suit your requirements.

=head3 nohome

Unless you supply a true value for this, an automatic redirection will be in place from the root of your app to the '/tables' path.
The redirection is there to make day-one functionality easy to find, but once your app grows you will not want this redirection.

=head3 model_class

See 'Customising Model Class'

=head3 connect_info

See 'Day One'

=head3 default_theme

To experiment with completely different colour themes, choose any standard JQueryUI theme name or "roll" your own as described here 
L<http://jqueryui.com/themeroller/>.   Our default is 'Redmond'.

=head1 DEBUGGING

To generate detailed trace info into the server log, export TABLES_DEBUG=1.

=head1 CAVEAT

We use dynamically-generated DBIx::Class classes.  This technique does not scale well for very large numbers
of tables.  Previous (private) incarnations of this Framework used specially prepared high-performance versions of 
Class::DBI::Loader to speed this up.  So that speeding-up at start-time is a TODO for this DBIx::Class-based release.

=head1 SOURCE and ISSUES REPOSITORY

Open-Sourced at Github: L<https://github.com/frank-carnovale/Mojolicious-Plugin-Tables>.  Please use the Issues register there.

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2016, Frank Carnovale <frankc@cpan.org>

This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.

=head1 SEE ALSO

L<Mojolicious>, L<DBIx::Class::Schema::Loader>, L<Mojolicious::Plugin::Tables::Model>

=cut
