#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package Distribution::DistributionProcess;

use strict;
use warnings;

use lib '/app/tools/rps/lib';

use lib '/app/tools/common/lib';
use Common::RSApp;
use Common::Util;
use Common::Assert;
use Common::WriteXML;
use Common::FormObject;

use Data::Dumper;

use lib '/app/tools/distribution/lib';
use Distribution::DB::Item::DistributionJob;
use Distribution::DB::Item::DistributionJobStatus;
use Distribution::DB::Item::ProductDistribution;
use Distribution::DB::Item::DistributionProcess;
use Distribution::DB::Item::DistributionService;

use lib '/app/tools/rps/lib';
use RPS::Product::DistributionService;

use Distribution::Job::PackagerTest;
use Distribution::Job::Packager;
use Distribution::Job::Delivery;
use Distribution::Job::DeliveryTest;

use constant kDefaultJobPriority => 2000;

##############################################
# Retry Intervals for delivery.
#
# The resulting configuration gives us the
# following retry schedule:
#
# Delay Start     Total Time
# Minutes Hours   Minutes Hours
# 0       0.00    0       0.00
# 15      0.25    15      0.25
# 45      0.75    60      1.00
# 90      1.50    150     2.50
# 150     2.50    300     5.00
# 225     3.75    525     8.75
# 315     5.25    840     14.00
# 420     7.00    1260    21.00
# 540     9.00    1800    30.00
# 675     11.25   2475    41.25
##############################################

use constant MAX_RETRIES    => 9;
use constant RETRY_INTERVAL => 15;

use base 'Common::FormObject';

sub _init {
    my ( $self, %args ) = @_;

    $self->SUPER::_init(%args);

    my %properties;
    if ( $args{distributionProcessID} ) {
        $self->_propertiesFromDB( \%properties, $args{distributionProcessID} );
    } elsif ( $args{dbItem} ) {
        $self->_propertiesFromDBItem( \%properties, $args{dbItem} );
    } else {
        $self->_propertiesFromDefaults( \%properties );
    }

    $self->_initProperties( \%properties );

    return $self;
}

sub _propertiesFromDB {
    my ( $self, $hrProperties, $distributionProcessID ) = @_;

    my $dbItem = Distribution::DB::Item::DistributionProcess->Lookup( distribution_process_id => $distributionProcessID );

    $self->_propertiesFromDBItem( $hrProperties, $dbItem );
}

sub _propertiesFromDBItem {
    my ( $self, $hrProperties, $dbItem ) = @_;

    return unless ($dbItem);

    $hrProperties->{distribution_process_id} = $dbItem->distribution_process_id;
    $hrProperties->{xml_data}                = $dbItem->xml_data;
    $hrProperties->{created}                 = $dbItem->created;
}

sub _initProperties {
    my ( $self, $props ) = @_;

    $self->{DistributionProcessID} = Common::FormObject::Scalar->new( value => $props->{distribution_process_id}, readOnly => 1 );
    $self->{XMLData}               = Common::FormObject::Scalar->new( value => $props->{xml_data} );
    $self->{created}               = Common::FormObject::Scalar->new( value => $props->{created} );
}

sub _propertiesFromDefaults {
    my ( $self, $hrProperties ) = @_;

    assert( 0, "Distribution Process ID required" );
}

sub validate {
    my $self = shift;

    return "Services not unique for distribution process " . $self->DistributionProcessID()
      unless ( Distribution::DB::Item::ProductDistribution->isServiceIDUnique( $self->DistributionProcessID() ) );

    return undef;
}

###
# Queueing Methods
###

sub enqueuePackager {
    my $self     = shift;
    my %args     = @_;
    my $priority = $args{priority} || kDefaultJobPriority;

    assert($self);

    assert( $self->DistributionProcessID );
    my $dist_process_id = $self->DistributionProcessID;

    # Add Job to Job Queue
    Common::Log::Debug("Create process for dist_id: $dist_process_id, priority: $priority");

    my $job = Distribution::Job::Packager->new(
        distProcessID => $dist_process_id,
        serviceID     => $self->ServiceID,
        writePath     => $args{write_path}
    );

    $job->enqueue( priority => $priority );

    return 1;
}

sub enqueuePackagerTest {
    my $self     = shift;
    my %args     = @_;
    my $priority = $args{priority} || kDefaultJobPriority;

    assert($self);

    assert( $self->DistributionProcessID );
    my $dist_process_id = $self->DistributionProcessID;

    # Add Job to Job Queue
    Common::Log::Debug("Create process for dist_id: $dist_process_id, priority: $priority");

    my $job = Distribution::Job::PackagerTest->new(
        distProcessID => $dist_process_id,
        serviceID     => $self->ServiceID,
        writePath     => $args{write_path}
    );
    $job->enqueue( priority => $priority );

    return 1;
}

sub retryPackager {
    my $self     = shift;
    my %args     = @_;
    my $priority = $args{priority} || kDefaultJobPriority;

    assert($self);
    assert( $self->DistributionProcessID );

    assert( 0, "No retry available for Packagers" );

    return 1;
}

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

    my $priority = $args{priority}  || kDefaultJobPriority;
    my $dir      = $args{base_dir}  || die "Base package directory required";
    my $package  = $args{package}   || die "package directory required";
    my $clientID = $args{client_id} || $args{clientID};

    assert($self);
    assert( $self->DistributionProcessID );
    assert($clientID);

    # The enqueue interface accepts delay in _seconds_...
    #
    my $delayStart = ( $self->_getDelayStart($clientID) * 60 ) unless ( $args{force_delivery} );

    # Return failure if we have had too many retries
    return if ( $delayStart && $delayStart < 0 );

    # Add Job to Job Queue
    Common::Log::Debug( "Create process for dist_id: " . $self->DistributionProcessID . ", priority: $priority" );

    my $job = Distribution::Job::Delivery->new(
        distProcessID => $self->DistributionProcessID,
        serviceID     => $self->ServiceID,
        sourceDir     => $dir,
        packageDir    => $package
    );

    $job->enqueue( priority => $priority, delay => $delayStart );

    return 1;
}

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

    my $priority = $args{priority}  || kDefaultJobPriority;
    my $dir      = $args{base_dir}  || die "Base package directory required";
    my $package  = $args{package}   || die "package directory required";
    my $clientID = $args{client_id} || $args{clientID};

    assert($self);
    assert( $self->DistributionProcessID );
    assert($clientID);

    my $delayStart = $self->_getDelayStart($clientID) unless ( $args{force_delivery} );

    # Return failure if we have had too many retries
    return if ( $delayStart && $delayStart < 0 );

    # Add Job to Job Queue
    Common::Log::Debug( "Create process for dist_id: " . $self->DistributionProcessID . ", priority: $priority" );

    my $job = Distribution::Job::DeliveryTest->new(
        distProcessID => $self->DistributionProcessID,
        serviceID     => $self->ServiceID,
        sourceDir     => $dir,
        packageDir    => $package
    );

    $job->enqueue( priority => $priority, delay => $delayStart );

    return 1;
}

sub retryDelivery {
    my $self = shift;
    return $self->enqueueDelivery(@_);
}

###
#   Return how many minuntes we should wait until we restart a job.
#
#   Uses a fibernachi ratio to calculate retry wait times.
###
sub _getDelayStart {
    my $self     = shift;
    my $clientID = shift;

    assert($self);
    assert( $self->DistributionProcessID );

    my $retries = Distribution::DB::Item::DistributionJob::GetRetryDeliveryCount( $self->DistributionProcessID, $clientID );

    assert( defined $retries && $retries >= 0 );

    if ( MAX_RETRIES < $retries ) {
        print STDERR "FAILURE: Too many retries ($retries). Not requeuing for delivery\n";
        return -1;
    }

    my $delay = 0;

    for ( my $i = $retries ; $i > 0 ; $i-- ) {
        $delay += $i;
    }

    $delay *= RETRY_INTERVAL;

    Common::Log::Debug("Calculated retry time for $retries retries, $delay minutes");

    return $delay;
}

###
# Accessor Methods
###

sub ServiceID {
    my $self = shift;
    assert($self);

    return Distribution::DB::Item::DistributionProcess->LookupServiceID( $self->DistributionProcessID );
}

sub QueueDate {
    my $self = shift;
    assert($self);

    return unless ( $self->{ClientID} );

    return Distribution::DB::Item::DistributionProcess->LookupQueueDate( $self->DistributionProcessID, $self->{ClientID} );
}

sub StartDate {
    my $self = shift;
    assert($self);

    return unless ( $self->{ClientID} );

    return Distribution::DB::Item::DistributionProcess->LookupEndDate( $self->DistributionProcessID, $self->{ClientID} );
}

sub EndDate {
    my $self = shift;
    assert($self);

    return unless ( $self->{ClientID} );

    return Distribution::DB::Item::DistributionProcess->LookupEndDate( $self->DistributionProcessID, $self->{ClientID} );
}

sub Delivering {
    my $self = shift;
    assert($self);

    return unless ( $self->{ClientID} );

    return Distribution::DB::Item::DistributionProcess->LookupDelivering( $self->DistributionProcessID, $self->{ClientID} );
}

sub State {
    my $self = shift;
    assert($self);

    return unless ( $self->{ClientID} );

    my $state = Distribution::DB::Item::DistributionProcess->LookupState( $self->DistributionProcessID, $self->{ClientID} );

    # If we know the state just return it.
    return $state if ($state);

    # Otherwise derive the state and store it.
    $state = Distribution::DB::Item::DistributionProcess->GetState( $self->DistributionProcessID, $self->{ClientID} );

    my $obj = Distribution::DB::Item::DistributionJobStatus->Create();
    $obj->client_id( $self->{ClientID} );
    $obj->distribution_process_id( $self->DistributionProcessID );
    $obj->status($state);
    $obj->save();

    return $state;
}

sub Service {
    my $self = shift;

    return new RPS::Product::DistributionService( serviceID => $self->ServiceID );
}

sub markAsFixed {
    my $self = shift;

    my $clientID = Common::RSApp::GetClientID();
    my $distID   = $self->DistributionProcessID();

    assert( $clientID && $distID );

    my $dbItem = Distribution::DB::Item::DistributionJobStatus->Lookup( client_id => $clientID, distribution_process_id => $distID );

    if ($dbItem) {
        $dbItem->status('fixed');
        $dbItem->save();
    }
}

1;
