package Distribution::Delivery;

use strict;

use lib '/app/tools/common/lib';
use Common::Assert;

use lib '/app/tools/distribution/lib';
use Distribution::DistributionProcess;

use File::Basename;
use Data::Dumper;

use base 'Distribution::DistributionObject';

sub _init {
    my $self = shift;
    my %args = @_;

    $self->SUPER::_init(@_);

    assert( $args{source_path} );

    $self->{_sourcePath} = $args{source_path} if ( $args{source_path} );
    $self->{_packageDir} = $args{package}     if ( $args{package} );
    $self->{_testing}    = $args{testing}     if ( $args{testing} );
    $self->{_dontRetry}  = $args{dont_retry};

    $self->{_confirmationFile} = $self->{_sourcePath} . "/delivery.log";

    # For some reason this was coming in quoted occasionally
    $self->{_sourcePath} =~ s/^"//;
    $self->{_sourcePath} =~ s/"$//;

    $self->{_packageDir} =~ s/^"//;
    $self->{_packageDir} =~ s/"$//;

    return $self;
}

sub transferFailed { shift->{_transferFailed} }

sub run {
    my $self = shift;

    unless ( $self->_preValidate() && $self->_validate() && $self->_postValidate() ) {
        $self->_processFailure( "Failed delivery validation", 10 );
    }

    unless ( $self->_preDeliver() && $self->_deliver() && $self->_postDeliver() ) {
        $self->_processFailure( "Failed delivery", 20 );
    }

    unless ( $self->_preManifest() && $self->_manifest() && $self->_postManifest() ) {
        $self->_processFailure( "Failed manifest delivery", 30 );
    }

    unless ( $self->_preSemaphore() && $self->_semaphore() && $self->_postSemaphore() ) {
        $self->_processFailure( "Failed semaphore delivery", 40 );
    }

    unless ( $self->_preVerify() && $self->_verify() && $self->_postVerify() ) {
        $self->_processFailure( "Failed delivery verification", 50 );
    }

    unless ( $self->_preFinalize() && $self->_finalize() && $self->_postFinalize() ) {
        $self->_processFailure( "Failed delivery finalization", 60 );
    }

    return 1;
}

###
#   Delivery over-ridable constants
###

sub _noResume   { }
sub _passiveFTP { 1 }

sub _manifestFile  { }
sub _semaphoreFile { }
sub _checksumFile  { }
sub _omitBaseDir   { }

sub _transportAgent {
    my $self = shift;

    assert( $self->{_acctInfo}{protocol} );

    if ( $self->{_acctInfo}{protocol} eq 'sftp' ) {
        return 'Distribution::TransportAgent::SFTP';
    } elsif ( $self->{_acctInfo}{protocol} eq 'ftp' ) {
        return 'Distribution::TransportAgent::FTP';
    } elsif ( $self->{_acctInfo}{protocol} eq 'itms' ) {
        return 'Distribution::TransportAgent::ITMS';
    } else {
        die "Unknown protocol: $self->{_acctInfo}{protocol}";
    }
}

###
#   Delivery States
###
sub _preValidate {
    my $self = shift;

    Common::Log::Print("**** Pre Validate ****");

    return 1;
}

sub _validate {
    my $self = shift;

    Common::Log::Print("**** Validate ****");

    return 1;
}

sub _postValidate {
    my $self = shift;

    Common::Log::Print("**** Post Validate ****");

    return 1;
}

sub _preDeliver {
    my $self = shift;

    Common::Log::Print("**** Pre Deliver ****");

    return 1;
}

sub _deliver {
    my $self = shift;

    Common::Log::Print("**** Deliver ****");

    my $transportAgent  = $self->_getTransportAgent();
    my $destinationPath = $self->_getRemotePackagePath();
    my $sourcePath      = $self->_getLocalPackagePath();

    Common::Log::Debug(" - Source: $sourcePath, Dest: $destinationPath");

    unless ( $transportAgent->deliver( $sourcePath, $destinationPath ) ) {
        Common::Log::Print("Delivery Failed!");
        $self->{_transferFailed} = 1;
        return unless $self->_retryDelivery();
    }

    return 1;
}

sub _postDeliver {
    my $self = shift;

    Common::Log::Print("**** Post Deliver ****");

    return 1;
}

sub _preManifest {
    my $self = shift;

    Common::Log::Print("**** Pre Manifest ****");

    return 1;
}

sub _manifest {
    my $self = shift;

    Common::Log::Print("**** Manifest ****");

    return 1 if ( $self->transferFailed() );
    return 1 unless ( $self->_manifestFile() );

    my $transportAgent  = $self->_getTransportAgent();
    my $destinationPath = $self->_getRemoteManifestPath();
    my $sourcePath      = $self->_getLocalManifestPath();

    Common::Log::Debug(" - Source: $sourcePath, Dest: $destinationPath");

    unless ( $transportAgent->deliver( $sourcePath, $destinationPath ) ) {
        Common::Log::Print("Delivery Failed!");
        $self->{_transferFailed} = 1;
        return unless $self->_retryDelivery();
    }

    return 1;
}

sub _postManifest {
    my $self = shift;

    Common::Log::Print("**** Post Manifest ****");

    return 1;
}

sub _preSemaphore {
    my $self = shift;

    Common::Log::Print("**** Pre Semaphore ****");

    return 1;
}

sub _semaphore {
    my $self = shift;

    Common::Log::Print("**** Semaphore ****");

    return 1 if ( $self->transferFailed() );
    return 1 unless ( $self->_semaphoreFile() );

    my $transportAgent  = $self->_getTransportAgent();
    my $destinationPath = $self->_getRemoteSemaphorePath();
    my $sourcePath      = $self->_getLocalSemaphorePath();

    Common::Log::Debug(" - Source: $sourcePath, Dest: $destinationPath");

    # Create the semaphore file if it doesn't exist already
    unless ( -f $sourcePath ) {
        open( FILE, ">$sourcePath" );
        close(FILE);
    }

    if ( !-f $sourcePath ) {
        print STDERR "Could not create semaphore file: $sourcePath\n";
        return 0;
    }

    unless ( $transportAgent->deliver( $sourcePath, $destinationPath ) ) {
        Common::Log::Print("Delivery Failed!");
        $self->{_transferFailed} = 1;
        return unless $self->_retryDelivery();
    }

    return 1;
}

sub _postSemaphore {
    my $self = shift;

    Common::Log::Print("**** Post Semaphore ****");

    return 1;
}

sub _preVerify {
    my $self = shift;

    Common::Log::Print("**** Pre Verify ****");

    return 1;
}

sub _verify {
    my $self = shift;

    Common::Log::Print("**** Verify ****");

    return 1;
}

sub _postFinalize {
    my $self = shift;

    Common::Log::Print("**** Post Finalize ****");

    return 1;
}

sub _preFinalize {
    my $self = shift;

    Common::Log::Print("**** Pre Finalize ****");

    return 1;
}

sub _finalize {
    my $self = shift;

    Common::Log::Print("**** Finalize ****");

    Distribution::DB::Item::DistributionProcess->SetDeliveryDate( $self->{_distributionProcessID} );

    return 1;
}

sub _postVerify {
    my $self = shift;

    Common::Log::Print("**** Post Verify ****");

    return 1;
}

###
#   Helper Functions
###

sub _retryDelivery {
    my $self = shift;

    return if ( $self->{_dontRetry} );

    my $process = Distribution::DistributionProcess->new( distributionProcessID => $self->{_distributionProcessID} );

    return $process->retryDelivery(
        client_id => $self->{_clientID},
        base_dir  => $self->_getLocalBasePath(),
        package   => $self->_getPackageDir()
    );
}

sub _getTransportAgent {
    my $self = shift;
    my %args = @_;

    my $agent = $self->_transportAgent();
    eval "require $agent";
    die "oops, can't require $agent\n$@\n" if $@;

    my $agentObj = $agent->new(
        account_name => $self->{_acctInfo}{code},
        port         => $self->{_acctInfo}{port},
        protocol     => $self->{_acctInfo}{protocol},
        user         => $self->{_acctInfo}{username},
        passwd       => $self->{_acctInfo}{password},
        host         => $self->{_acctInfo}{hostname},
        authfile     => $self->{_acctInfo}{authfile},
        confirm_file => $self->{_confirmationFile},
        testing      => $self->{_testing},
        dont_retry   => $self->{_dontRetry},
        passive      => $self->_passiveFTP(),
        no_resume    => $self->_noResume()
    );

    return $agentObj;
}

sub _getRemotePackagePath {
    my $self = shift;
    return $self->_getRemoteBasePath();
}

sub _getRemoteManifestPath {
    my $self = shift;
    return $self->_getRemoteBasePath() . '/' . $self->_getPackageDir();
}

sub _getRemoteSemaphorePath {
    my $self = shift;
    return $self->_getRemoteBasePath() . '/' . $self->_getPackageDir();
}

sub _getLocalPackagePath {
    my $self = shift;
    my $dir  = $self->_getLocalBasePath() . "/" . $self->_getPackageDir();

    $self->_processFailure( "Source dir does not exist: $dir", 12 )
      unless ( -d $dir );

    $dir .= "/*" if ( $self->_omitBaseDir() );

    return $dir;
}

sub _getLocalManifestPath {
    my $self = shift;

    my $file = $self->_getLocalBasePath() . "/" . $self->_manifestFile();

    assert( -e $file, "Manifest file missing: $file" );

    return $file;
}

sub _getLocalSemaphorePath {
    my $self = shift;
    my $file = $self->_getLocalBasePath() . "/" . $self->_semaphoreFile();

    return $file;
}

sub _getLocalBasePath {
    my $self = shift;

    assert( $self->{_sourcePath} );

    return $self->{_sourcePath};
}

sub _getRemoteBasePath {
    my $self = shift;

    #assert( $self->{_acctInfo}{basepath} );

    return $self->{_acctInfo}{basepath} || ".";
}

sub _getPackageDir {
    my $self = shift;

    return $self->{_packageDir};
}

sub _processFailure {
    my $self = shift;
    my $msg  = shift || "Unknown error";
    my $code = shift || 99;

    my $error = "Delivery Issue ($code) - RoyaltyShare resolving";
    my $id    = $self->{_distributionProcessID};

    assert( $id, "Distribution process id required" );

    print STDERR "Delivery Issue ($code) $msg\n";

    Distribution::DB::Item::DistributionProcess->setDenied( $id, $error );

    exit $code;
}

1;
