package Bigtop::Backend::Model::GantryCDBI;
use strict; use warnings;

use Bigtop::Backend::Model;
use File::Spec;
use Inline;
use Bigtop;

#-----------------------------------------------------------------
#   The Default Template
#-----------------------------------------------------------------

our $template_is_setup = 0;
our $default_template_text = <<'EO_TT_blocks';
[% BLOCK stub_table_module %]
package [% package_name %];
use strict; use warnings;

use base '[% base_class || base_class_default %]', 'Exporter';

use [% gen_package_name %];

our $[% package_alias %] = '[% package_name %]';

our @EXPORT_OK = ( '$[% package_alias %]' );

1;

=head1 NAME

[% package_name %] - model for [% table_name %] table (stub part)

=head1 DESCRIPTION

This model inherits from [% base_class || base_class_default %] and uses its generated
helper [% gen_package_name %].

It was generated by Bigtop, but is NOT subject to regeneration.

=cut
[% END %]

[% BLOCK gen_table_module %]
# NEVER EDIT this file.  It was generated and will be overwritten without
# notice upon regeneration of this application.  You have been warned.
package [% package_name %];
use strict; use warnings;

[% package_name %]->table   ( '[% real_table_name %]'     );
[% IF sequence_name %]
[% package_name %]->sequence( '[% sequence_name %]' );
[% END %]
[% IF primary_key %]
[% package_name %]->columns ( Primary   => qw/
[% IF primary_key.0 %]
    [% primary_key.join( ' ' ) +%]
[% ELSE %]
    [% primary_key +%]
[% END -%]
/ );
[% END -%]

[% package_name %]->columns ( All       => qw/
[% FOREACH column IN all_columns %]
    [% column +%]
[% END %]
/ );

[% package_name %]->columns ( Essential => qw/
[% FOREACH essential_column IN essential_columns %]
    [% essential_column +%]
[% END %]
/ );

[% FOREACH has_a IN has_a_list %]
[% package_name %]->has_a( [% has_a.column %] => '[% base_package_name %]::[% has_a.table %]' );
[% END +%]
sub get_foreign_display_fields {
    return [ qw( [% foreign_display_columns %] ) ];
}

sub get_foreign_tables {
    return qw(
[% FOREACH foreign_table IN foreign_tables %]
        [% base_package_name %]::[% foreign_table +%]
[% END %]
    );
}

sub foreign_display {
    my $self = shift;

[% foreign_display_body %]
}

1;

=head1 NAME

[% gen_package_name %] - model for [% table_name %] table (generated part)

=head1 DESCRIPTION

This model mixes into [% package_name %],
because Class::DBI bindings don't really allow a choice.
It was generated by Bigtop, and IS subject to regeneration.

=head1 METHODS

You may use all normal Class::DBI::Sweet methods and the ones listed here:

=over 4

=item get_foreign_display_fields

=item get_foreign_tables

=item foreign_display

=back

=cut
[% END %]
EO_TT_blocks

#-----------------------------------------------------------------
#   Methods in the Bigtop::Model::GantryCDBI package
#-----------------------------------------------------------------

sub what_do_you_make {
    return [
        [ 'lib/AppName/Model/*.pm'     =>
            'Class::DBI style model stubs [safe to change]'                 ],
        [ 'lib/AppName/Model/GEN/*.pm' =>
            'Class::DBI style model specifications [please, do not change]' ],
        [ 'note' =>
            'This backend is incompatible with other Model backends.' ],
    ];
}

sub backend_block_keywords {
    return [
        { keyword => 'no_gen',
          label   => 'No Gen',
          descr   => 'Skip everything for this backend',
          type    => 'boolean' },

        { keyword => 'model_base_class',
          label   => 'Models Inherit From',
          descr   => 'Defaults to Gantry::Utils::CDBI',
          type    => 'text' },

        { keyword => 'template',
          label   => 'Alternate Template',
          descr   => 'A custom TT template.',
          type    => 'text' },
    ];
}

sub setup_template {
    my $class         = shift;
    my $template_text = shift || $default_template_text;

    return if ( $template_is_setup );

    Inline->bind(
        TT                  => $template_text,
        POST_CHOMP          => 1,
        TRIM_LEADING_SPACE  => 0,
        TRIM_TRAILING_SPACE => 0,
    );

    $template_is_setup = 1;
}

sub gen_Model {
    my $class       = shift;
    my $build_dir   = shift;
    my $bigtop_tree = shift;

    # make sure the directories are ready for us
    my $model_name    = $bigtop_tree->get_appname() . '::Model';

    my ( $module_dir, @sub_dirs )
                      = Bigtop::make_module_path( $build_dir, $model_name );

    my $gen_dir       = File::Spec->catdir( $module_dir, 'GEN' );

    mkdir $gen_dir;

    # see if there is an alternate default base module
    my $config_block  = $bigtop_tree->get_config()->{ Model };

    # build the individual model packages
    $bigtop_tree->walk_postorder(
        'output_model',
        {
            module_dir       => $module_dir,
            model_name       => $model_name,
            lookup           => $bigtop_tree->{application}{lookup},
            model_base_class => $config_block->{model_base_class}
                                    || 'Gantry::Utils::CDBI',
        },
    );

}

#-----------------------------------------------------------------
#   Packages named in the grammar
#-----------------------------------------------------------------

# table_block
package # table_block
    table_block;
use strict; use warnings;

sub output_model {
    my $self         = shift;
    my $child_output = shift;
    my $data         = shift;

    # Skip sequences, etc.
    return unless ( $self->{__TYPE__} eq 'tables' );

    my $table_lookup = $data->{lookup}{tables}{ $self->{__NAME__} };

    if ( $table_lookup->{not_for} ) {
        foreach my $skipped_type ( @{ $table_lookup->{not_for}{__ARGS__} } ) {
            return if ( $skipped_type eq 'Model' );
        }
    }

    # get columns sets
    my $lookup       = $table_lookup->{fields};

    my $all        = $self->walk_postorder(
            'output_all_fields_cdbi', $lookup
    );
    my $essentials = $self->walk_postorder(
            'output_essential_fields_cdbi', $lookup
    );

    # deal with foreign keys
    my $foreign_tables = $self->walk_postorder(
            'output_foreign_tables_cdbi',       $lookup
    );

    my @foreign_table_names;
    my @has_a_list;

    foreach my $entry ( @{ $foreign_tables } ) {
        my $entry_hash    = { @{ $entry } };

        my $foreign_table = $entry_hash->{ table };
        $foreign_table    =~ s/\./_/;

        push @foreign_table_names, $foreign_table;

        push @has_a_list, {
            table  => $foreign_table,
            column => $entry_hash->{ column },
        };
    }

    # Gone Fishing.
    my $table           = $self->{__NAME__};
    $table              =~ s/\./_/;
    my $module_name     = $data->{model_name} . '::' . $table;
    my $gen_pack_name   = $data->{model_name} . '::GEN::' . $table;
    my $alias           = uc $table;
    my $sequence        = $table_lookup->{sequence};
    my $foreign_display = $table_lookup->{foreign_display};

    my $sequence_name;

    if ( $sequence ) {
        $sequence_name = $sequence->{__ARGS__}[0];
    }

    my $primary_key = $self->find_primary_key(
            $self->{__NAME__},
            $data->{ lookup },
    );

    my $foreign_display_columns;
    my $foreign_display_body;

    if ( $foreign_display ) {
        my $foreign_display_cols = $foreign_display->{__ARGS__}[0];

        my @field_names          = ( $foreign_display_cols =~ /%([\w\d_]*)/g );
        $foreign_display_columns = "@field_names";

        $foreign_display_body  = _build_foreign_display_body(
            $foreign_display_cols, @field_names
        );
    }

    my $base_class;
    
    if ( defined $table_lookup->{model_base_class} ) {
        $base_class = $table_lookup->{model_base_class}{__ARGS__}[0];
    }

    # generate output
    my $stub_content = Bigtop::Backend::Model::GantryCDBI::stub_table_module(
        {
            base_class              => $base_class,
            base_class_default      => $data->{model_base_class},
            base_package_name       => $data->{model_name},
            gen_package_name        => $gen_pack_name,
            package_name            => $module_name,
            package_alias           => $alias,
            table_name              => $table,
        }
    );

    my $gen_content = Bigtop::Backend::Model::GantryCDBI::gen_table_module(
        {
            base_package_name       => $data->{model_name},
            package_name            => $module_name,
            gen_package_name        => $gen_pack_name,
            package_alias           => $alias,
            table_name              => $table,
            real_table_name         => $self->{__NAME__},
            sequence_name           => $sequence_name,
            primary_key             => $primary_key,
            foreign_display_columns => $foreign_display_columns,
            foreign_display_body    => $foreign_display_body,
            all_columns             => $all,
            essential_columns       => $essentials,
            has_a_list              => \@has_a_list,
            foreign_tables          => \@foreign_table_names,
        }
    );

    # store it
    my $module_file = File::Spec->catfile( $data->{module_dir}, "$table.pm" );
    my $gen_dir     = File::Spec->catdir ( $data->{module_dir}, 'GEN' );
    my $gen_file    = File::Spec->catfile( $gen_dir, "$table.pm" );

    eval {
        no warnings qw( Bigtop );
        Bigtop::write_file( $module_file, $stub_content, 'no overwrite' );
    };
    warn $@ if $@;

    eval {
        Bigtop::write_file( $gen_file, $gen_content );
    };
    warn $@ if $@;
}

# table_element_block
package # table_element_block
    table_element_block;
use strict; use warnings;

sub output_all_fields_cdbi {
    my $self         = shift;
    shift;
    my $data         = shift;

    return unless ( ref( $self->{__BODY__} ) );

    my $field  = $data->{ $self->{__NAME__} };

    return if ( _not_for_model( $field ) );

    return [ $self->{__NAME__} ];
}

sub output_essential_fields_cdbi {
    my $self         = shift;
    shift;
    my $data         = shift;

    return unless ( ref( $self->{__BODY__} ) );

    my $field  = $data->{ $self->{__NAME__} };

    if ( $field->{non_essential} ) {
        my $non_essential_value = $field->{non_essential}{args}[0];

        return if ( $non_essential_value );
    }

    return if ( _not_for_model( $field ) );

    return [ $self->{__NAME__} ];
}

sub output_foreign_tables_cdbi {
    my $self         = shift;
    shift;
    my $data         = shift;

    return unless ( ref( $self->{__BODY__} ) );

    my $field  = $data->{ $self->{__NAME__} };

    if ( $field->{refers_to} ) {
        my $foreign_table_name = $field->{refers_to}{args}[0];

        if ( ref( $foreign_table_name ) eq 'HASH' ) {
            ( $foreign_table_name ) = %{ $foreign_table_name };
        }

        return [
            [ column => $self->{__NAME__}, table => $foreign_table_name ]
        ];
    }
    return;
}

1;

__END__

=head1 NAME

Bigtop::Backend::Model::GantryCDBI - Bigtop backend generating Class::DBI::Sweet models

=head1 SYNOPSIS

If your bigtop file looks like this:

    config {
        base_dir `/home/user`;
        ...
        Model GantryCDBI {}
    }
    app Name {...}

and there are tables in the app block, when you type:

    bigtop your.bigtop Model

or
    bigtop your.bigtop all

this module will make model modules which are subclasses of
Gantry::Utils::CDBI (which inherits from Class::DBI::Sweet in a
mod_perl safe way).

All modules will live in the lib subdirectory of the app's build directory.
See Bigtop::Init::Std for an explanation of how base_dir and the
build directory are related.

=head1 DESCRIPTION

This is a Bigtop backend which generates data model modules which are
subclasses of Gantry::Utils::CDBI.

=head1 KEYWORDS

This module does not register any keywords.  See Bigtop::Model for
a list of keywords models understand.

The default for the model_base_class keyword is Gantry::Utils::CDBI.

=head1 METHODS

To keep podcoverage tests happy.

=over 4

=item backend_block_keywords

Tells tentmaker that I understand these config section backend block keywords:

    no_gen
    model_base_class
    template
    
=item what_do_you_make
    
Tells tentmaker what this module makes.  Summary: Class::DBI models.

=item gen_Model

Called by Bigtop::Parser to get me to do my thing.

=item setup_template

Called by Bigtop::Parser so the user can substitute an alternate template
for the hard coded one here.

=back

=head1 AUTHOR

Phil Crow <crow.phil@gmail.com>

=head1 COPYRIGHT and LICENSE

Copyright (C) 2005 by Phil Crow

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.6 or,
at your option, any later version of Perl 5 you may have available.

=cut
