#!perl
use strict;

# Bigtop Modules
use Bigtop;
use Bigtop::Parser;
use Bigtop::Deparser;
use Bigtop::ScriptHelp;
use Bigtop::ScriptHelp::Style;

# Standard Modules
use Getopt::Long;
use File::Spec;
use File::Find;

my $usage       = "usage: $0 [options] bigtop_file all|SQL|...\n";

my $create;
my $new;
my $add;
my $keeping_inline;
my $style;

GetOptions(
    'create|c'      => \$create,
    'new|n=s'       => \$new,
    'add|a=s'       => \$add,
    'keep_inline|k' => \$keeping_inline,
    'style|s=s'     => \$style,
    'help|h'        => \&full_usage,
    'pg_help'       => \&help_pg,
    'mysql_help'    => \&help_mysql,
);

my $action_flags = 0;
$action_flags++ if $new;
$action_flags++ if $create;
$action_flags++ if $add;

if ( $action_flags > 1 ) {
    die "--create, --new, and --add are incompatible, try: $0 --help\n";
}

if ( defined $new and not Bigtop::ScriptHelp::valid_ident( $new ) ) {
    die "invalid name for new app: $new\n";
}

my $script_help_style = Bigtop::ScriptHelp::Style->get_style( $style );

if ( $new ) {
    make_new( $script_help_style, @ARGV );
}
elsif ( $add ) {
    make_addition( $script_help_style, $add, @ARGV );
}
else { # default, use a file
    my $bigtop_file = shift or die $usage;

    unless ( @ARGV ) { die $usage; }

    my ( $new_app_name, $bigtop_dir ) =
            Bigtop::Parser->gen_from_file( $bigtop_file, $create, @ARGV );

    if ( $create ) {
        my $built_sqlite = build_sqlite( $new_app_name, $bigtop_dir );

        print_instructions( $new_app_name, $bigtop_dir, $built_sqlite );
    }
}

# Remove inline directory

if ( not $keeping_inline and -d '_Inline' ) {

    my $purger = sub {
        my $name = $_;

        if    ( -f $name ) { unlink $name; }
        elsif ( -d $name ) { rmdir $name;  }
    };

    finddepth( $purger, '_Inline' );
    rmdir '_Inline';
}

sub make_new {
    my $style  = shift;
    my @models = @_;
    my $bigtop_string;
    my $things_to_build = 'Init';
    my $made_tables     = 0;

    my $new_dir = $new;
    $new_dir    =~ s/::/-/g;

    # make sure build directory is not already there
    if ( -d $new_dir ) {
        die "cowardly refusing to build $new\n"
            .   "...the $new_dir directory already exists\n";
    }

    if ( @models ) {
        $bigtop_string   = Bigtop::ScriptHelp->get_big_default(
                $style, $new, @models
        );
        $things_to_build = 'all';
        $made_tables     = 1;
    }
    else {
        $bigtop_string   = Bigtop::ScriptHelp->get_minimal_default( $new );
    }

    # form names
    my $bigtop_dir  = $new;
    $bigtop_dir     =~ s/::/-/g;
    my $bigtop_name = lc $bigtop_dir . '.bigtop';

    # do the build
    eval {
        Bigtop::Parser->gen_from_string(
            {
                bigtop_string => $bigtop_string,
                bigtop_file   => $bigtop_name,
                create        => 'create',
                build_list    => [ $things_to_build ],
                flags         => "-n @models",
            }
        );
    };
    if ( $@ ) {
        if ( not -f 'tmp.bigtop' and open my $TMP, '>tmp.bigtop' ) {
            print $TMP $bigtop_string;
            close $TMP;
            warn "\nI had a fatal problem parsing my bigtop file.\n"
                .   "I wrote that bigtop file to tmp.bigtop\n\n";
        }
        die $@;
    }

    # make the .bigtop file in docs subdir of build dir
    my $bigtop_file
            = File::Spec->catfile( $bigtop_dir, 'docs', $bigtop_name );

    if ( open my $BIGTOP, ">", $bigtop_file ) {
        # We used to turn off all Init gens, now it thinks most of its
        # files are stubs, so the following is not needed:
        # $bigtop_string =~ s/\{\}/{ no_gen 1; }/;
        print $BIGTOP $bigtop_string;
        close $BIGTOP;
    }
    else {
        die "Couldn't write $bigtop_file: $!\n";
    }

    if ( $made_tables ) {
        my $built_sqlite = build_sqlite( $new, $bigtop_dir );

        print_instructions( $new, $bigtop_dir, $built_sqlite );
    }
}

sub make_addition {
    my $style       = shift;
    my $source_file = shift;
    my @models      = @_;

    unless ( @models ) {
        die "--add option requires a list of ASCII art table relations\n"
    }

    my $ast   = Bigtop::Parser->parse_file( $source_file );
    my $space = ' ';
    my $art   = join $space, @models;
    $art      =~ s/^\s+//;
    $art      =~ s/\s+$//;

    Bigtop::ScriptHelp->augment_tree( $style, $ast, $art );

    # write the file
    my $deparse = Bigtop::Deparser->deparse( $ast );

    Bigtop::write_file( $source_file, $deparse );

    # do the build
    Bigtop::Parser->gen_from_file( $source_file, 0, 'all' );
}

sub build_sqlite {
    my $app_name   = shift;
    my $bigtop_dir = shift;

    my $built_sqlite  = 0;
    my $sqlite_db     = File::Spec->catfile( $bigtop_dir, 'app.db' );
    my $sqlite_schema = File::Spec->catfile(
            $bigtop_dir, 'docs', 'schema.sqlite'
    );

    my $version   = `sqlite -version`;

    if ( -f $sqlite_schema and substr( $version, 0, 1 ) >= 3 ) {
        my $command   = `sqlite $sqlite_db < $sqlite_schema 2>&1`;
        my $failure   = $?;

        if ( $failure or $command ) {
            warn "I tried to make your database, sqlite said:\n\n$command";
        }
        else {
            $built_sqlite = 1;
        }
    }

    return $built_sqlite;
}

sub print_instructions {
    my $app_name     = shift;
    my $build_dir    = shift;
    my $built_sqlite = shift;

    my $heading   = << "EO_SQLite_Basic";

I have generated your '$app_name' application.  To run the application:

    cd $build_dir
    sqlite app.db < docs/schema.sqlite
    ./app.server [ port ]
EO_SQLite_Basic

    if ( $built_sqlite ) {
        $heading = << "EO_SQLite_Prebuilt";

I have generated your '$app_name' application.  I have also taken the liberty
of making an sqlite database for it to use.  To run the application:

    cd $build_dir
    ./app.server [ port ]
EO_SQLite_Prebuilt
    }

    print << "EO_Instructions";
$heading
The app.server runs on port 8080 by default.

Once the app.server starts, it will print a list of the urls it can serve.
Point your browser to one of those and enjoy.

If you prefer to run the app with Postgres or MySQL type one of these:

    bigtop --pg_help
    bigtop --mysql_help

EO_Instructions

}

sub help_pg {
    print << "EO_pg_help";

From your build directory:

    createdb app.db -U postgres
    psql app.db -U regular_user < docs/schema.postgres
    ./app.server --dbd=Pg --dbuser=regular_user --dppass='secret'

Supply passwords as prompted when creating and building the database.
You may abbreviate --dbd as -d, --dbuser as -u, and --dbpass as -p.

If you change the name from app.db, use --dbname (aka -n) on app.server.

EO_pg_help

    exit 0;
}

sub help_mysql {
    my $build_dir = shift;

    print << "EO_mysql_help";
For MySQL:

    cd App-Dir
    msyqladmin create app.db -u root -p
    mysql -u root -p app.db < docs/schema.mysql
    ./app.server --dbd=mysql --dbuser=regular_user --dppass='secret'

Supply passwords as prompted when creating and building the database.
You may abbreviate --dbd as -d, --dbuser as -u, and --dbpass as -p.

If you change the name from app.db, use --dbname (aka -n) on app.server.

EO_mysql_help

    exit 0;
}

sub full_usage {
    print << 'EO_HELP';
usage: bigtop [options] file.bigtop something_to_gen [something_to_gen...]

    options:

    -h  --help        - this message
    -c  --create      - initial build, makes directories
    -n  --new         - initial build, give it an app name (not a
                        file name), and a list of tables
        --pg_help     - steps to starting a brand new app with postgres
        --mysql_help  - steps to starting a brand new app with mysql
    -k  --keep_inline - does not remove _Inline

    somethings_to_gen is either 'all' or a list of
    backend types (like 'SQL' or 'Control')

EO_HELP
    exit 0;
}

=head1 NAME

bigtop - the parser/generater for the bigtop langauge

=head1 SYNOPSIS

For regnerating pieces of an existing app:

    bigtop [options] file.bigtop all

Or, for brand new apps:

    bigtop --new AppName 'ascii_art'

Or, to augment an existing app:

    bigtop --add app.bigtop 'ascii_art'

See L<ASCII Art> below for what ascii_art can be.

=head1 DESCRIPTION

To learn more about bigtop, consult Bigtop::Docs::TOC.  It has a list
of all the documentation along with suggestions of where to start.

This script usually takes a bigtop input file and a list of things to build.
The things you can build have the same names as the blocks in the config
section of your bigtop file.  You may also choose C<all> which will
build all of those things in the order they appear in the config section.

If you are starting a new app from scratch, you can get a jump start with
the --new flag (or -n):

    bigtop --new AppName table1 table2

If you already have a bigtop file, you can add to it with the --add (or
-a):

    bigtop --add file.bigtop table3 table4

But, see L<ASCII Art> below for more interesting options than a list of
table names.

Both new and add options do an all build when they finish making/updating
the bigtop file.  If you don't want an immediate all build, try tentmaker
with the same flags.

The new option will also try to build a database for the app to use
immediately using sqlite (if it can find it in your path).

=head1 NON-HELP OPTIONS

=over 4

=item --create (or -c)

Use this if you already have a bigtop source file and want to make
a brand new app from it.  Perhaps someone gave you a bigtop file, you
copied one from the examples directory of the bigtop distribution, or
you built one with tentmaker.

This will make an h2xs style path under the current directory for the
app described in your bigtop file.  It will even copy that bigtop file
into the docs directory while it builds whatever you ask for.

Without this option, if the current directory looks like a bad place to
build, a fatal error will result and you will have to use this option.
A bad place to build is a place where building seems not to have happened
before.  If any of these are missing, then the directory is bad:

    Build.PL
    Changes
    t/
    lib/

When create is in effect, the following bigtop config options affect the
location of the initial build:

=over 4

=item base_dir

the directory under which all building will happen. Defaults to the
current directory.

=item app_dir

the subdirectory of base_dir where Build.PL and friends will live.
Defaults to the h2xs style directory name based on your app's
name.  If your app section starts:

    app App::Name::SubName

then the default app_dir is:

    App-Name-SubName

=back

When create is not in effect, these config parameters are ignored WITH a
warning.

=item --new (or -n) App::Name [table_1...|ascii_art]

See L<ASCII Art> below for how to specify table relationships.

Use this option to create a working application from scratch.  If you only
provide an app name, it will use a minimal bigtop specification.  The
resulting app will not run (or have any code in it).  You must then
augment the bigtop file with tentmaker or a text editor and regenerate
to get a running app.

If you supply optional table names or ASCII art, enough additional items will
be added to the bigtop file to make a running app (except that you might
need to build the database).  Some of the extra items will be repeated for
each model you request.

In either case, when bigtop finishes, there will be an App-Name subdirectory
of the current directory.  In it will be all the usual pieces describing an
app.  The bigtop file will be in the docs directory.

If you have a working sqlite in your path -- and you specified tables
or ASCII art -- -n will also make an sqlite database called app.db in
the build directory.  As it will tell you, you can change to that directory
and start the app immediately.

If you don't have sqlite, a message will explain what to do to start the
app.  Mostly this boils down to changing into the new build directory,
creating a database called app.db, and running app.server with the proper
flags for your database engine.

=item --add (-a )

If you have an existing bigtop file and want to add tables and their
controllers to it, use this option like this:

    bigtop --add file.bigtop table...|ascii_art

See L<ASCII Art> below for how to specify table relationships.

This option reads an existing file.bigtop and adds tables and controllers
to it, before doing an all build.  (If you don't want an all build,
use the same options with tentmaker.)

Any new tables will be created.  All relationships in the ASCII art
will be applied, even if they affect existing tables.

Note that this option may disturb comments and whitespace in your original.
It uses Bigtop::Deparser, which cannonicalizes the whitespace.  Basically
extraneous whitespace is removed (and indenting is regularized).  When new
lines are removed, subsequent comments drift down in the revised file.

Revision control is always a good idea.  It is especially important
here.  Make sure file.bigtop is commited to your revision control system
prior to running bigtop in add mode.

=item --keep_inline (or -k)

Normally, this script removes all traces of the _Inline directory it
used while building your app.  Use this option if you want to save
a microscopic amount of time on each regeneration or if you have an
incurable curiosity.

Note that the directory will only be removed if it is really _Inline
in the current directory.  If you have a .Inline directory under
home directory etc., the script will not affect it.

=item --style (or -s)

Defaults to Original.  This can be the name of any Bigtop::ScriptHelp::Style
module.  These styles control how your command line args, and
standard input, turn into bigtop descriptions.  See the docs for you
style to see what input is legal and how it is treated.

For historical reasons, there is POD below describing the Original style.

=back

=head1 HELP OPTIONS

In addition to the flags that do useful things, there are help flags:

=over 4

=item --help or -h

Prints a multi-line usage message showing all the options.

=item --pg_help and --mysql_help

Print advice on how to start your app.server with a Postgres or MySQL
database instead of sqlite.  This includes instructions on creating and
building the database, as well as flags app.server needs in order to
reach that database.

=back

=head1 ASCII Art

This section applies only to --style Original (which is the default).
If you use some other style, see the docs in
Bigtop::ScriptHelp::Style::ItsName.

Both --new and --add allow you to specify a list of tables or ASCII
art.  The ASCII art option allows you to quickly note relationships between
tables with simple operators.

Note well: Since the relationship operators use punctuation that your
shell probably loves, you must surround the art with single quotes.

It is easiest to understand the options by seeing an example.  So, suppose
we have a four table data model describing a bit of our personnel process:

    +-----------+       +----------+
    |    job    |<------| position |
    +-----------+       +----------+
          ^
          |
    +-----------+       +----------+
    | job_skill |------>|  skill   |
    +-----------+       +----------+

First, you'll be happy to know that bigtop's ASCII art is simpler to draw
than the above.

What our data model shows is that each position refers to a job (description),
each job could require many skills, and each skill could be associated with
many jobs.  The last two mean that job and skill share a many-to-many
relationship.

Here's how to specify this data model in bigtop ASCII art:

    bigtop --new HR 'job<-position job<->skill'

This indicates a foreign key from position to job and an implied
table, called job_skill, to hold the many-to-many relationship between
job and skill.

There are four art operators:

=over 4

=item <->

Many-to-many.  A new table will be made with foreign keys to each operand
table.  Each operand table will have a has_many relationship.  Note
that your Model backend may not understand these relationships.  At the
time of this writing only Model GantryDBIxClass did, by luck it happens
to be the default.

=item ->

The first table has a foreign key pointing to the second.

=item <-

The second table has a foreign key pointing to the first.  This is really
a convenience synonymn for ->, but the tables are put into generated SQL
in their overall order of appearance.  So, there is a slight differnce
between them.

=item -

The two tables have a one-to-one relationship.  Each of them will have
a foreign key pointing to the other.

=back

=head1 AUTHOR

Phil Crow E<lt>crow.phil@gmail.comE<gt>

=head1 COPYRIGHT and LICENSE

Copyright (C) 2005-6 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
