The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# TODO:
# - handle_stop
package Cog::App;
use Mouse;
extends 'Cog::Base';

use Class::Throwable qw(Error);
use Cog::Config;
use IO::All;
use Getopt::Long qw(:config pass_through);
use YAML::XS;
use Cwd 'abs_path';

# use XXX;

use constant Name => 'Cog';
use constant app_root => abs_path('.');
use constant command_script => 'cog';

# XXX Make these more dynamic so subclasses don't have to always override
use constant config_class => 'Cog::Config';
use constant content_class => 'Cog::Content';
use constant maker_class => 'Cog::Maker';
use constant store_class => 'Cog::Store';
use constant webapp_class => '';
use constant view_class => 'Cog::View';

sub plugins { [] };
# XXX - Is this still needed??
sub cog_classes {
    my $self = shift;
    return +{
        config => $self->config_class,
        content => $self->content_class,
        maker => $self->maker_class,
        store => $self->store_class,
        webapp => $self->webapp_class,
        view => $self->view_class,
    }
}

has action => ( is => 'rw' );
has time => ( is => 'ro', builder => sub { time() } );

sub get_app {
    my ($class, @argv) = @_;
    my $app_class;
    @ARGV = @argv;
    Getopt::Long::GetOptions(
        'app=s' => \$app_class,
    );
    $app_class ||= $ENV{COG_APP} || $class;
    unless ($app_class->can('new')) {
        eval "use $app_class; 1"
            or die $@;
    }
    die "$app_class is not a Cog::App application"
        unless $app_class->isa('Cog::App') and
            $app_class ne 'Cog::App';

    return $app_class;
}

sub BUILD {
    my ($self) = @_;
    my $config_class = $self->config_class;
    eval "require $config_class"
        unless UNIVERSAL::can($config_class, 'new');
    my $config_file = $self->config_file
        or die "Can't determine config file";

    my $app_root = '.';

    if ($config_file =~ /(.*)\/(.*)/) {
        ($app_root, $config_file) = ($1, $2);
    }

    my $hash = -e $config_file
        ? YAML::XS::LoadFile($config_file)
        : {};
    $hash = {
        %$hash,
        app_root => abs_path($app_root),
        config_file => $config_file,
        app_class => ref($self),
        maker_class => $self->maker_class,
        store_class => $self->store_class,
        command_script => $0,
        command_args => [@ARGV],
    };

    Cog::Base->initialize(
        $self,
        $config_class->new($hash),
    );
}

sub config_file {
    my $class = shift;
    return $class->app_root . '/' . lc($class->Name) . '.conf.yaml';
}

sub run {
    my $self = shift;

    $self->parse_command_args;

    my $action = $self->action;
    my $method = "handle_$action";

    my $function = $self->can($method)
        or throw Error "'$action' is an invalid action\n";

    $self->chdir_root()
        unless $action eq 'init';

    $function->($self);
    return 0;
}

sub parse_command_args {
    my $self = shift;
    my $argv = $self->config->command_args;
    my $script = $self->config->command_script;
    $script =~ s!.*/!!;
    my $action = '';
    if ($script =~ /^(pre-commit|post-commit)$/) {
        $script =~ s/-/_/;
        $self->action($script);
    }
    elsif ($script ne $self->command_script) {
        throw Error "unexpected script name '$script'\n";
    }
    elsif (@$argv and $argv->[0] =~ /^[\w\-]+$/) {
        $action = shift @$argv;
        $action =~ s/-/_/g;
    }
    elsif (not @$argv) {
        $action = 'help';
    }
    else {
        require XXX;
        warn "\nInvalid cog command. Can't parse these arguments:\n";
        XXX::XXX(@_);
    }
    $self->action($action);
}

#-----------------------------------------------------------------------------
sub handle_help {
    my $self = shift;
    print $self->usage;
}

sub usage {
    my $self = shift;
    return <<'...';
Usage: cog command

Commands:
    init   - Make current directory into a Cog app
    init Cog::Class
           - Make a Cog::Class app specifically
    update - Update the app with the latest assets
    make   - Prepare the app content for the web
    start  - Start the local app server
    stop   - Stop the server

    bless file-name - Turn a text file into a cog file
    edit name|id - Start an editor with the contents of the cog page

See:
    `perldoc cog` - Documentation on this command.
    `perldoc Cog::Manual` - Complete Cog documentation.
...
}

sub handle_init {
    my $self = shift;
    my $root = $self->config->app_root;
    throw Error "Can't init. Cog environment already exists.\n"
        if $self->config->is_init;

    $self->_copy_assets();

    my $config_file = "$root/config.yaml";
    if (not -e $config_file) {
        require Template::Toolkit::Simple;
        my $data = +{%$self};
        $data->{app_class} = ref($self);
        my $config = Template::Toolkit::Simple::tt()
            ->path(["$root/template/"])
            ->data($data)
            ->post_chomp
            ->render('config.yaml.tt');
        io($config_file)->print($config);
    }

    $self->chdir_root;

    $self->store->init;

    my $Name = $self->Name;
    my $name = $self->config->command_script;
    $name =~ s!.*/!!;

    print <<"...";
$Name was successfully initialized in:

    $root

The next step is to edit:

    $config_file
    
Then run:

    $name update

...
}

sub handle_update {
    my $self = shift;
    my $root = $self->config->app_root;

    $self->_copy_assets();

    my $Name = $self->Name;
    my $name = $self->config->command_script;
    $name =~ s!.*/!!;

    print <<"...";
$Name was successfully updated in the $root/ subdirectory.

Now run:

    $name make

...
}

sub _copy_assets {
    my $self = shift;
    my $files = $self->config->files_map;
    my $root = $self->config->app_root;

    for my $file (keys %$files) {
        my $source = $files->{$file};
        my $target = $file =~ m!^(js|css|image)/!
            ? "$root/static/$file"
            : "$root/$file";
        if ($ENV{COG_SYMLINK_INSTALL}) {
            unless (-l $target and readlink($target) eq $source) {
                unlink $target;
                io($target)->assert->symlink($source);
                print "> link $source => $target\n";
            }
        }
        else {
            unless (-f $target and not(-l $target) and io($target)->all eq io($source)->all) {
                unlink $target;
                io($target)->assert->print(io($source)->all);
                print "> copy $source => $target\n";
            }
        }
    }
}

sub handle_make {
    my $self = shift;
    $self->maker->make;
    print <<'...';
Cog is up to date and ready to use. To start the web server, run
this command:

    cog start

...
    
}

sub handle_start {
    my $self = shift;
    print <<'...';
Cog web server is starting up...

...
    my @args = @{$self->config->command_args};
    unshift @args, ('-p' => $self->config->server_port)
        if $self->config->server_port;
    $self->runner->run(@args);
}

sub handle_edit {
    # TODO
}

sub handle_clean {
    my $self = shift;
    $self->maker->make_clean;
    print <<'...';
Cog is clean. To rebuild, run this command:

    cog make

...
    
}

# Put the App in the context of its defined root directory.
sub chdir_root {
    my $self = shift;
    my $app_root = $self->config->app_root;
    chdir $app_root
      or die "Can't chdir into $app_root";
}

1;