The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
=head1 NAME

Clustericious::App -- base class for clustericious apps

=head1 DESCRIPTION

Inherits from Mojolicious, add adds the following functionality :

=cut

package Clustericious::App;

use List::Util qw/first/;
use List::MoreUtils qw/uniq/;
use MojoX::Log::Log4perl;
use Mojo::UserAgent;
use Clustericious::Templates;
use Mojo::ByteStream qw/b/;
use Data::Dumper;
use Clustericious::Log;
use Mojo::URL;
use JSON::XS;
use Scalar::Util qw/weaken/;
use Mojo::Base 'Mojolicious';
use File::HomeDir ();

use Clustericious::Controller;
use Clustericious::Renderer;
use Clustericious::RouteBuilder::Common;
use Clustericious::Config;
use Clustericious::Commands;

sub _have_rose {
    return 1 if Rose::Planter->can("tables");
}

has commands => sub {
  my $commands = Clustericious::Commands->new(app => shift);
    weaken $commands->{app};
    return $commands;
};

use warnings;
use strict;

our @Confdirs = $ENV{TEST_HARNESS} ?
   ($ENV{CLUSTERICIOUS_TEST_CONF_DIR}) :
   (File::HomeDir->my_home, File::HomeDir->my_home . "/etc", "/util/etc", "/etc" );

{
no warnings 'redefine';
sub Math::BigInt::TO_JSON {
    my $val = shift;
    my $copy = "$val";
    my $i = 0 + $copy;
    return $i;
}
}

=head2 $app-E<gt>startup

Adds the autodata_handler plugin, common routes,
and sets up logging for the client using log::log4perl.

=cut

sub startup {
    my $self = shift;

    $self->controller_class('Clustericious::Controller');
    $self->renderer(Clustericious::Renderer->new());
    $self->renderer->classes([qw/Clustericious::Templates/]);
    my $home = $self->home;
    $self->renderer->paths([ $home->rel_dir('templates') ]);

    $self->init_logging();
    $self->secret( (ref $self || $self) );

    $self->plugins->namespaces(['Mojolicious::Plugin','Clustericious::Plugin']);
    my $config = Clustericious::Config->new(ref $self);
    my $auth_plugin;
    if(my $auth_config = $config->plug_auth(default => '')) {
        $self->log->info("Loading auth plugin plug_auth");
        $auth_plugin = $self->plugin('plug_auth', plug_auth => $auth_config);
    } elsif($auth_config = $config->simple_auth(default => '')) {
        $self->log->info("Loading auth plugin simple_auth [deprecated]");
        $auth_plugin = $self->plugin('plug_auth', plug_auth => $auth_config);
    } else {
        $self->log->info("No auth configured");
    }
    
    my $r = $self->routes;
    # "Common" ones are not overrideable.
    Clustericious::RouteBuilder::Common->add_routes($self);
    Clustericious::RouteBuilder->add_routes($self, $auth_plugin);
    # "default" ones are :
    # Clustericious::RouteBuilder::Default->add_routes($self);

    $self->plugin('AutodataHandler');
    $self->plugin('DefaultHelpers');
    $self->plugin('TagHelpers');
    $self->plugin('EPLRenderer');
    $self->plugin('EPRenderer');
    $self->plugin('RequestTimer');
    $self->plugin('PoweredBy');

    # Helpers
    if (my $base = $config->url_base(default => '')) {
        $self->helper( base_tag => sub { b( qq[<base href="$base" />] ) } );
    }
    my $url = $config->url(default => '') or do {
        $self->log->warn("Configuration file should contain 'url'.") unless $ENV{HARNESS_ACTIVE};
    };

    $self->helper( _clustericious_config => sub { $config } );
    $self->helper( url_with => sub {
        my $c = shift;
        my $q = $c->req->url->clone->query;
        my $url = $c->url_for->clone;
        $url->query($q);
        $url;
    });

    $self->helper( render_moved => sub {
        my $c = shift;
        $c->res->code(301);
        my $where = $c->url_for(@_)->to_abs;
        $c->res->headers->location($where);
        $c->render(text => "moved to $where");
    } );

    # See http://groups.google.com/group/mojolicious/browse_thread/thread/000e251f0748c997
    my $murl = Mojo::URL->new($url);
    my $part_count = @{ $murl->path->parts };
    if ($part_count > 0 ) {
        $self->hook(before_dispatch  => sub {
            my $c = shift;
            if (@{ $c->req->url->base->path->parts } > 0) {
                # subrequest
                my @extra = splice @{ $c->req->url->base->path->parts }, -$part_count;
            }
            push @{ $c->req->url->base->path->parts },
              splice @{ $c->req->url->path->parts }, 0, $part_count;
        });
    }

    $self->hook( before_dispatch => sub {
        Log::Log4perl::MDC->put(remote_ip => shift->tx->remote_address || 'unknown');
    });
}

=head2 $app-E<gt>init_logging

Initializing logging using ~/etc/log4perl.conf

=cut

sub init_logging {
    my $self = shift;

    my $logger = Clustericious::Log->init_logging(ref $self || $self);

    $self->log( $logger );
}

=head2 $app-E<gt>dump_api

Dump out the API for this REST server.

=cut

sub dump_api {
    my $self = shift;
    my $routes = shift || $self->routes->children;
    my @all;
    for my $r (@$routes) {
        my $pat = $r->pattern;
        $pat->_compile;
        my %placeholders = map { $_ => "<$_>" } @{ $pat->placeholders };
        my $method = uc join ',', @{ $r->via || ["GET"] };
        if (_have_rose() && $placeholders{table}) {
            for my $table (Rose::Planter->tables) {
                $placeholders{table} = $table;
                my $pat = $pat->pattern;
                $pat =~ s/:table/$table/;
                push @all, "$method $pat";
            }
        } elsif (_have_rose() && $placeholders{items}) {
            for my $plural (Rose::Planter->plurals) {
                $placeholders{items} = $plural;
                my $line = $pat->render(\%placeholders);
                push @all, "$method $line";
            }
        } elsif (defined($pat->pattern)) {
            push @all, join ' ', $method, $pat->pattern;
        } else {
            push @all, $self->dump_api($r->children);
        }
    }
    return uniq sort @all;
}

=head2 $app-E<gt>dump_api_table( $table )

Dump out the column information for the given table.

=cut

sub _dump_api_table_types
{
    my($rose_type) = @_;
    return 'datetime' if $rose_type =~ /^datetime/;
    state $types = {
        (map { $_ => 'string' } qw( character text varchar )),
        (map { $_ => 'numeric' } 'numeric', 'float', 'double precision','decimal'),
        (map { $_ => $_ } qw( blob set time interval enum bytea chkpass bitfield date boolean )),
        (map { $_ => 'integer' } qw( bigint integer bigserial serial )),
        (map { $_ => 'epoch' } 'epoch', 'epoch hires'),
        (map { $_ => 'timestamp' } 'timestamp', 'timestamp with time zone'),
    };
    return $types->{$rose_type} // 'unknown';
}

sub dump_api_table
{
    my($self, $table) = @_;
    return unless _have_rose();
    my $class = Rose::Planter->find_class($table);
    return unless defined $class;

    return {
        columns => {
            map {
                $_->name => {
                    rose_db_type => $_->type,
                    not_null     => $_->not_null,
                    type         => _dump_api_table_types($_->type),
                } } $class->meta->columns
            },
        primary_key => [
            map { $_->name } $class->meta->primary_key_columns
        ],
    };
}

=head2 $app-E<gt>config

Returns the config (an instance of L<Clustericious::Config>) for the application.

=cut

sub config {
    my $app = shift;
    if (my $what = shift) {
        # Mojo config interface
        $app->_clustericious_config(@_)->{$what};
    } else {
        $app->_clustericious_config(@_);
    }
}

1;