use strict;
use warnings;

package Footprintless::Deployment;
$Footprintless::Deployment::VERSION = '1.13';
# ABSTRACT: A deployment manager
# PODNAME: Footprintless::Deployment

use parent qw(Footprintless::MixableBase);

use Carp;
use File::Path qw(
    make_path
);
use Footprintless::Mixins qw (
    _clean
    _download
    _extract_resource
    _resource
    _sub_entity
);
use Footprintless::Util qw(
    agent
    rebase
    temp_dir
);
use Log::Any;

my $logger = Log::Any->get_logger();

sub clean {
    my ( $self, @options ) = @_;
    $self->_clean(@options);
}

sub deploy {
    my ( $self, %options ) = @_;

    if ( $options{to_dir} ) {
        $self->_deploy_resources( $options{to_dir},
            ( $options{names} ? ( names => $options{names} ) : () ) );
        &{ $options{extra} }( $options{to_dir} ) if ( $options{extra} );
    }
    else {
        $self->_local_template(
            sub {
                my ( $to_dir, $resource_dir ) = @_;
                $self->_deploy_resources( $resource_dir,
                    ( $options{names} ? ( names => $options{names} ) : () ) );
                &{ $options{extra} }($to_dir) if ( $options{extra} );
            },
            rebase => $options{rebase}
        );
    }

    $logger->debug("deploy complete");
}

sub _deploy_resources {
    my ( $self, $to_dir, %options ) = @_;
    my $resources = $self->_sub_entity( 'resources', 1 );

    my @names =
        $options{names}
        ? @{ $options{names} }
        : keys(%$resources);

    $logger->debugf( "deploy %s to %s", \@names, $to_dir );
    foreach my $resource_name (@names) {
        my $resource = $resources->{$resource_name};
        if ( ref($resource) eq 'HASH' && $resource->{extract_to} ) {
            my $extract_dir = File::Spec->catdir( $to_dir, $resource->{extract_to} );
            $logger->tracef( 'extract [%s] to [%s]', $resource, $to_dir );
            $self->_extract_resource( $resource, $extract_dir );
        }
        else {
            $logger->tracef( 'download [%s] to [%s]', $resource, $to_dir );
            $self->_download( $resource, $to_dir );
        }
    }
}

sub _local_template {
    my ( $self, $local_work, @options ) = @_;
    $self->Footprintless::Mixins::_local_template(
        sub {
            my ($to_dir) = @_;

            my $resource_dir = $self->_sub_entity('resource_dir');
            $resource_dir =
                $resource_dir
                ? File::Spec->catdir( $to_dir, $resource_dir )
                : $to_dir;
            make_path($resource_dir);

            &$local_work( $to_dir, $resource_dir );
        },
        @options
    );
}

1;

__END__

=pod

=head1 NAME

Footprintless::Deployment - A deployment manager

=head1 VERSION

version 1.13

=head1 SYNOPSIS

    # Standard way of getting a deployment
    use Footprintless;
    my $deployment = Footprintless->new()->deployment('deployment');

    # Standard deploy procedure
    $deployment->clean();
    $deployment->deploy();

    # Deploy to temp instead of the entity configured location
    my $rebase = {
        from => '/opt/tomcat', 
        to => '/tmp/tomcat'
    };
    $deployment->clean(rebase => $rebase);
    $deployment->deploy(rebase => $rebase);

    # Only deploy selected resources
    $deployment->deploy(names => ['bar']);

=head1 DESCRIPTION

Manages deployments.  A deployment is a set of files and directories that
are all associated with a single component.  For example, if you are using
tomcat, a deployment might refer to all of the webapps deployed to the 
container, and the folders and files that are I<NOT> part of the tomcat
container itself.  

=head1 ENTITIES

A simple deployment:

    deployment => {
        clean => ['/opt/app/'],
        resources => {
            foo => 'http://download.com/foo.exe',
            bar => 'http://download.com/bar.exe'
        },
        to_dir => '/opt/app'
    }

A more complex situation, perhaps a tomcat instance:

    deployment => {
        'Config::Entities::inherit' => ['hostname', 'sudo_username'],
        clean => [
            '/opt/tomcat/conf/Catalina/localhost/',
            '/opt/tomcat/temp/',
            '/opt/tomcat/webapps/',
            '/opt/tomcat/work/'
        ],
        resources => {
            bar => '/home/me/.m2/repository/com/pastdev/bar/1.2/bar-1.2.war',
            baz => {
                coordinate => 'com.pastdev:baz:war:1.0',
                'as' => 'foo.war',
                type => 'maven'
            },
            foo => {
                url => 'http://pastdev.com/resources/foo.war',
                extract_to => 'ROOT'
            }
        },
        to_dir => '/opt/tomcat/webapps'
    }

=head1 CONSTRUCTORS

=head2 new($entity, $coordinate)

Constructs a new deployment configured by C<$entities> at C<$coordinate>.  

=head1 METHODS

=head2 clean(%options)

Cleans the deployment.  Each path in the C<configuration.clean> entity, 
will be removed from the destination.  If the path ends in a C</>, then 
after being removed, the directory will be recreated.  The supported 
options are:

=over 4

=item rebase

A hash containing C<from> and C<to> where the paths for each item in the
clean entity will have the C<from> portion of their path substituted by 
C<to>.  For example, if the path is C</foo/bar> and rebase is
C<{from => '/foo', to => '/baz'}>, then the resulting path would be 
C</baz/bar>.

=back

=head2 deploy(%options)

Deploys all the resources listed in the C<resource> entity to the location
specified in the C<configuration.to_dir> entity. The supported options 
are:

=over 4

=item extra 

A subroutine that is called during deployment allowing you to add to
what is deployed before it is pushed to its destination.  This subroutine
will be called with a single argument containing the (possibly temporary)
directory that you can write additional files to.

=item names

A list of names of resources that should be deployed.  If this option is
provided, any names not in this list will be ignored.

=item rebase

A hash containing C<from> and C<to> where the paths for each item in the
clean entity will have the C<from> portion of their path substituted by 
C<to>.  For example, if the path is C</foo/bar> and rebase is
C<{from => '/foo', to => '/baz'}>, then the resulting path would be 
C</baz/bar>.

=back

=head1 AUTHOR

Lucas Theisen <lucastheisen@pastdev.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by Lucas Theisen.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 SEE ALSO

Please see those modules/websites for more information related to this module.

=over 4

=item *

L<Footprintless|Footprintless>

=item *

L<Config::Entities|Config::Entities>

=item *

L<Footprintless|Footprintless>

=item *

L<Footprintless::Mixins|Footprintless::Mixins>

=back

=cut
