#!/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__

=encoding UTF-8

=head1 NAME

stubby - Command-line tool for generating and inspecting Perl programs derived from Sub::Genius plans

=head1 SYNOPSIS

C<stubby> is a development and tooling utility for working with
L<Sub::Genius>. It can generate Perl code, enumerate valid execution
orders, visualize plans, and pre-cache compiled automata.

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

Generate a Perl script that invokes Sub::Genius at runtime.
The example below redirects output to C<./spawn-combine.pl>:

    $ stubby init --run once \
        -p "subA subB subC" \
        > ./spawn-combine.pl

The generated script includes C<use Sub::Genius ();> and executes the
plan dynamically.

=head2 Generate code with no dependency on L<Sub::Genius>

Generate a fully expanded, dependency-free Perl script. The execution
order is made explicit and Sub::Genius is not required at runtime:

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

Note: although this necessarily causes the PRE to be compiled and cached,
the generated code does not C<use Sub::Genius ();>. In this mode, caching
only serves to speed up the C<stubby> invocation itself.

For intentional or distributable caching, see the C<precache> subcommand
below.

=head2 Pre-cache PREs (a compiler-like step)

The C<precache> subcommand compiles a PRE into a minimized DFA and stores
it in the cache directory. On success, it prints the absolute path to the
cache file:

    $ CACHEFILE=$(stubby precache \
        -p "init ( subA & subB ) middle ( subC & subD ) fin")

    $ echo "Cache file is ${CACHEFILE}"

PREs may also be supplied from a file:

    $ CACHEFILE=$(stubby precache \
        -f ./my-preplan.preplan \
        -d ./cache)

The following file contents produce the same checksum and cache entry as
the inline PRE above:

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

C<Sub::Genius> normalizes PREs aggressively (stripping whitespace,
comments, and adding explicit symbol delimiters) so semantically
equivalent PREs produce the same cache key.

=head1 DESCRIPTION

C<stubby> is a general-purpose command-line tool intended to support
development, debugging, and distribution of applications built on
L<Sub::Genius>.

It provides several subcommands, each addressing a different stage of
the workflow.

=head2 Subcommands Overview

=over 4

=item C<export>

Dumps the minimized DFA produced from a PRE in GraphViz C<dot> format.
This is useful for visualization with tools such as C<xdot>, C<dot>,
C<circo>, or other GraphViz renderers.

=item C<init>

Generates boilerplate Perl code to help start a script, module, or
project that uses Sub::Genius. Depending on options, the generated code
may invoke Sub::Genius dynamically or be fully dependency-free.

=item C<list>

Enumerates all I<valid> execution orders (strings) accepted by the PRE.
This is useful for verifying correctness assumptions and understanding
the full range of sequentially consistent orderings.

=item C<precache>

Compiles a PRE into a DFA and stores it in the cache directory, allowing
Sub::Genius to skip the potentially expensive PRE-to-DFA conversion step
at runtime.

=back

=head1 OPTIONS FOR SUBCOMMAND C<export>

=over 4

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

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

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

Provide the PRE directly on the command line. Mutually exclusive with
C<-f|--prefile>.

=back

=head1 OPTIONS FOR SUBCOMMAND C<init>

The C<init> subcommand does not enumerate execution orders. Instead, it
extracts symbol names from the PRE (or list of subroutines) and generates
Perl code stubs suitable for manual editing.

=over 4

=item C<-x | --run>

Selects the execution style embedded in the generated code.
Default is C<once>.

Supported values:

=over 4

=item C<once>

Generate code that invokes C<Sub::Genius::run_once>.

=item C<any>

Generate code that invokes C<Sub::Genius::run_any>.

=item C<all>

Generate code that invokes C<Sub::Genius::run_once> inside a loop to
iterate over all valid execution orders.

=item C<nodeps>

Generate code with no dependency on Sub::Genius. All subroutine calls
are explicit and sequentialized.

=back

=item C<-p | --preplan>

Provide the PRE inline.

When used with C<once>, C<any>, or C<all>, the PRE is parsed only to
extract symbol names; no DFA is built.

When used with C<nodeps>, the PRE is necessarily compiled and a valid
sequential ordering is emitted.

=item C<-f | --prefile>

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

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

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

Provide the PRE directly on the command line.

=back

=head1 OPTIONS FOR SUBCOMMAND C<precache>

The C<precache> subcommand explicitly prepares cached DFAs for reuse.
This is especially useful for large or highly shuffled PREs.

=over 4

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

Directory in which to store cached DFAs. Passed directly to the
C<cachedir> parameter of C<Sub::Genius->new>.

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

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

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

Provide the PRE directly on the command line.

=item C<--force>

Force regeneration of the cache entry even if a matching checksum
already exists in the cache directory.

=back

=head1 SEE ALSO

L<Sub::Genius>, L<Sub::Genius::Util>, L<Sub::Genius::Example>

=head1 BUGS

I<Probably.> If you find one, it is almost certainly real.

=head1 COPYRIGHT AND LICENSE

Same terms as Perl itself.

=head1 AUTHOR

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