#!/usr/bin/env perl

use strict;
use warnings;
use Sub::Genius::Util ();
use Getopt::Long      ();

my $VERSION = 1.0;

use constant {
    EXIT_SUCCESS => 0,
    EXIT_ERROR   => 1,
};

require Data::Dumper;

# maybe switch to App::Cmd later for sub commands,
# but it's options processing is super tard
my $subcommand = shift @ARGV;

my $subcommands = {
    help     => \&run_helf,
    init     => \&run_init,
    precache => \&run_precache,
};

my $usages = {
    help     => \&usage_helf,
    init     => \&usage_init,
    precache => \&usage_precache,
};

# enforce subcommand
if ( not $subcommand or not exists $subcommands->{$subcommand} ) {
    warn qq{\n(fatal) stubby requires valid subcommand ...\n\n};
    print_usage($usages);
    exit EXIT_ERROR;
}

exit $subcommands->{$subcommand}->( \@ARGV );

sub print_usage {
    my $usages = shift;
    print qq{All commands:\n};

    # force "help" to be first
    foreach my $usage ( ( qw/help/, grep { !m/help/ } sort keys %$usages ) ) {
        my $use = $usages->{$usage}->();
        print qq{'$usage'\n};
        print qq{\t$use\n};
    }
    print qq{\n};
}

sub usage_helf {
    return qq{stubby help};
}

sub usage_init {
    return qq{stubby init [-r|--run [once|any|all|nodeps] -s|--subroutines "sub1 sub2 sub3 ..." > my-script.pl};
}

sub usage_precache {
    return qq{stubby precache [-d path/to/cachedir [-f path/to/preplan.preplan] | [-p|--preplan "p&r&e&plan"]};
}

sub run_helf {
    my $has_perldoc = system(qw/which perdoc/);
    if ( $has_perldoc > EXIT_SUCCESS ) {
        print qq{(fatal) 'perldoc' utility required, please install & resubmit your request...\n};
        return $has_perldoc;
    }
    return system( qw/perldoc/, $0 );
}

sub run_init {
    my $argv = shift;
    my %opts = ( R => q{once} );                    # default is 'once', as in C<Sub::Genius::run_once>
    my $gol  = Getopt::Long::GetOptionsFromArray(
        $argv,
        \%opts,                # container for all processed options
        q/p|preplan=s/,        # define PRE (currently for -x nodeps, only)
        q/s|subroutines=s/,    # specify name of subroutines; NOT a PRE!
        q/x|run=s/,            # specify invocation method: once, any, all, once
    );

    # no %opts checking here, relying on exceptions from Sub::Genius::Util
    if ( grep { /$opts{x}/ } (qw/once any all/) ) {
        print Sub::Genius::Util->subs2perl( q{subs} => [ split( /[\s&|,]/, $opts{s} ) ], q{with-run} => $opts{x} );
    }
    elsif ( grep { /$opts{x}/ } (qw/nodeps/) ) {

        # options validation is done in S::G::Util
        # this method unrolls the preplan into series of sequentially
        # consistent subroutine calls, eliminating the need for Sub::Genius
        # as a dependency in the final code
        print Sub::Genius::Util->plan2nodeps( preplan => $opts{p}, prefile => $opts{f}, cachedir => $opts{d} );
    }
    return EXIT_SUCCESS;
}

sub run_precache {
    my $argv = shift;
    my %opts = ( d => undef );
    my $gol  = Getopt::Long::GetOptionsFromArray(
        $argv,
        \%opts,             # container for all processed options
        q/d|cachedir=s/,    # specify cachedir, defaults to Sub:Genius' default
        q/f|file=s/,        # specify file containing PRE
        q/force/,           # forces re-caching (sends 'reset=>1' to 'init_plan')
        q/o=s/,             # specify Storable file for DFA being cached
        q/p|preplan=s/,     # define PRE (required if -f is not specified
    );

    # be kind, create cachedir but let user know
    if ( defined $opts{d} and not -d $opts{d} ) {
        mkdir $opts{d}, 0700
          and print qq{(info) Created '$opts{d}\n};
    }

    my $sq = Sub::Genius::Util->precache(
        preplan  => $opts{p},
        cachedir => $opts{d},
        prefile  => $opts{f},
        reset    => $opts{force},
    );
    print $sq->cachefile . qq{\n};
    return EXIT_SUCCESS;
}

exit;
__END__

=head1 NAME

stubby - commandline tool for dumping stub Perl programs sequentialized using L<Sub::Genius>.

=head1 SYNOPSIS

=head2 Generate code that is dependent on L<Sub::Genius>:

Example redirects to a file, C<./spawn-combine.pl>

    $ stubby init --run once -s "just a list of subroutines" > ./spawn-combine.pl

=head2 Generate code that is I<not> dependent on L<Sub::Genius>:

Example redirects to a file, C<./spawn-combine.pl>

    $ stubby init --run nodeps -p "init ( subA & subB ) middle ( subA & suB ) fin" > ./spawn-combine.pl

Note: while this necessarily causes a PRE to be cached, the code
generated also doesn't C<use Sub::Genius ();>, so the caching effect on
this subcommand is only as useful for making this command faster. See
C<precache> subcommand below for a more useful approach for caching.

=head2 Pre-cache PREs (may be reasonably called, a I<compile> step):

C<precache> outputs the absolute path to the cache file on completion.

    $ CACHEFILE=$(stubby precache -p "init (subA & subB ) middle (subC & subD) fin")
    $ echo Cache file is ${CACHEFILE}

    $ CACHEFILE=$(stubby precache -f ./my-preplan.preplan -d put/in/here)
    $ echo Cache file is ${CACHEFILE}

Note: C<./my-preplan.preplan> should generate the same checksum (and cache)
as the PRE specified using C<-p> in the first example.

    init
      (subA & subB )
    middle
      (subC & subD)
    fin

C<Sub::Genius> tries very hard to generate a consistent representation
of the PREs, basically by minifying them.

=head1 DESCRIPTION

General commandline tool for doing usefuly things while developing or
distributing applications that use L<Sub::Genius>.

C<init> -  generates boilerplate Perl to help start a script, module,
or project that is using C<Sub::Genius>.

C<precache> - provides a compiler-I<like> tool for precaching PREs
that L<Sub::Genius> is able to use natively for skipping the potentially
expensive step of convering a PRE (I<preplan>) to a DFA (the thing L<FLAT>
uses to generate valid (i.e., I<sequentially consistent>) execution plans.

=head1 OPTIONS FOR SUBCOMMAND C<init>

The C<init> subcommand does not convert a PRE to a DFA, it simply takes a
space delimited list of subroutine names, that are then used to generate
minimal subroutine stubs. It is then a starting point for the developer
of the code to manually edit the PRE (contained as a scalar string,
C<$preplan>) and implement the bodies of the subroutines.

=over 4

=item C<-x|--run>

Choose invocation method to use in the code. The default is C<once>.

Options include:

C<once> - generates code that invokes C<Sub::Genius::run_once>.

Requires C<-s|--subroutines>.

C<any>  - generates code that invokes C<Sub::Genius::run_any>.

Requires C<-s|--subroutines>.

C<all>  - generates code that invokes C<Sub::Genius::run_once>, in a C<do { ... } while ()> loop.

Requires C<-s|--subroutines>.

C<nodeps> - generates code that is free of any dependencies, including C<Sub::Genius>.

Requires C<-p|--preplan>.

=item C<-s|--subroutines>

Valid only for C<-x|--run>: C<once> (default), C<any>, and C<all>.

Specify a space delimited list of subroutine names to generate stubs and a
unannotated PRE. It's I<just a list of subroutines>.

Invalid for use with C<-x|--run nodeps>.

=item C<-p|--preplan>

An actual PRE, used with C<-x|--run nodeps>, since this option necessarily
generates subroutine calls that must necessarily be I<sequentially consistent>.

=back

=head1 OPTIONS FOR SUBCOMMAND C<precache>

Effectively compiles a PRE using L<Sub::Genius>'s caching feature. As
long as PREs that produced the same C<checksum> by C<Sub::Genius> and
the C<cachedir> is the same, this allows one to prepare and distribute
any number of pre-compiled DFAs. For sufficiently large or highly shuffled
PREs, this can be leveraged to virtually eliminate the overhead associated
with using C<Sub::Genius> in deployed code..

=over 4

=item C<-d|--cachedir> C<path/to/cachedir>

Specifies the C<cachedir> to use for saving the cached DFAs. See
L<Sub::Genius> for more information about caching. This option is passed
to the C<cachedir> parameter of the constructor.

=item C<-f|--prefile> C<path/to/PRE.preplan>

Specify the PRE from a file. Mutually exclusive with C<-p|--preplan>.

=item C<-p|--preplan> C<PRE>

An the actual PRE that is to be cached. Mutually exclusive with
C<-f|--prefile>, which can be used to specify a PRE in a text file.

=item C<--force>

Forces re-caching of previously cached DFAs, or at least for those matching
the C<checksum> and are located in the same C<cachedir>.

=back

=head1 SEE ALSO

L<Sub::Genius>

=head1 BUGS

I<Probably>

=head1 COPYRIGHT AND LICENSE

perl5

=head1 AUTHOR

OODLER 577 E<lt>oodler@cpan.orgE<gt>
