#!/usr/bin/env perl

use strict;
use warnings;
use Sub::Genius::Util ();
use Getopt::Long      ();
use Util::H2O::More qw/ddd h2o/;

my $VERSION = 1.1;

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

# 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,
    export   => \&run_export,
    init     => \&run_init,
    list     => \&run_list,
    precache => \&run_precache,
};

my $usages = {
    help     => \&usage_helf,
    export   => \&usage_export,
    init     => \&usage_init,
    list     => \&usage_list,
    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_export {
    return qq{stubby export --as [dot] [-f path/to/preplan.preplan] | [-p|--preplan "p&r&e&plan"] };
}

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

sub usage_list {
    return qq{stubby list [-f path/to/preplan.preplan] | [-p|--preplan "p&r&e&plan"]};
}

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 perldoc/);
    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_export {
    my $argv = shift;
    my $opts = { a => undef, f => undef, p => undef, s => undef };
    my $gol  = Getopt::Long::GetOptionsFromArray(
        $argv,
        $opts,             # container for all processed options
        q/a|as=s/,         # specify export kind (e.g., "dot" dumps PRE in graphviz "dot" format)
        q/f|prefile=s/,    # specify file containing PRE
        q/p|preplan=s/,    # define PRE (required if -f is not specified
        q/s|stage=s/,      # default is "min-dfa" because that's what is used internally and available most easily
                           #  other options are: pfa, nfa, dfa.
    );
    h2o $opts;
    my $sq = Sub::Genius::Util->export_as( as => $opts->a, preplan => $opts->p, prefile => $opts->f, stage => $opts->s );
    return EXIT_SUCCESS;
}

sub run_init {
    my $argv = shift;
    my $opts = { f => undef, p => undef, x => q{once} }; 
    my $gol  = Getopt::Long::GetOptionsFromArray(
        $argv,
        $opts,                                      # container for all processed options
        q/f|prefile=s/,                             # specify file containing PRE
        q/p|preplan=s/,                             # define PRE (currently for -x nodeps, only)
        q/x|run=s/,                                 # specify invocation method: once, any, all, once
    );
    h2o $opts;

    # no %opts checking here, relying on exceptions from Sub::Genius::Util
    my $x = $opts->x;
    if ( grep { /$x/ } (qw/once any all/) ) {
        print Sub::Genius::Util->subs2perl( q{preplan} => $opts->p, q{prefile} => $opts->f, q{with-run} => $opts->x );
    }
    elsif ( grep { /$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 );
    }
    return EXIT_SUCCESS;
}

sub run_list {
    my $argv = shift;
    my $opts = { f => undef, p => undef };
    my $gol  = Getopt::Long::GetOptionsFromArray(
        $argv,
        $opts,             # container for all processed options
        q/f|prefile=s/,    # specify file containing PRE
        q/p|preplan=s/,    # define PRE (required if -f is not specified
    );
    h2o $opts;
    my $sq = Sub::Genius::Util->list( preplan => $opts->p, prefile => $opts->f );
    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|prefile=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 -p "can bejust 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<export> - dumps the minimized DFA resulting from the C<PRE> in GraphViz's
C<dot> format. Useful with programs such as C<xdot>, which is a viewer; in
addition to the rendering programs provided by GraphViz, such as C<dot>, C<circo>,
etc.

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

C<list> - enumerates all I<valid> strings accepted by the PRE that is provided;
this is useful for verifying all of the orderings in which the PRE describes the
serialized execution plan.

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<export>

=over 4

=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.

=back

=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>.

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

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

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

=item C<-p|--preplan>

An actual PRE, when used with C<-x|--run> C<[ once | any | all ]>, the subroutine
names are stripped and used, but the PRE is not converted to a DFA. 

when used with C<-x|--run nodeps>, the PRE is necessarily generates subroutine calls
that must necessarily be I<sequentially consistent>.

=item C<-f|--prefile>

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

=back

=head1 OPTIONS FOR SUBCOMMAND C<list>

=over 4

=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.

=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>
