#------------------------------------------------------------
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------

package RPS::Mechanical::Process;
use strict;
use POSIX qw(ceil);

use Data::Dumper;

use lib '/app/tools/rps/lib';
use lib '/app/tools/common/lib';
use Common::Process;
use Common::Assert;
use Common::Log;
use RPS::RoyaltyRun::Process;
use RPS::License::RateType;
use RPS::License::RateBasis;
use RPS::DB::Item::RegionCountryMap;
use RPS::Mechanical::Process::Exception;

use base 'RPS::RoyaltyRun::Process';

#     Adding caching accessors here, so that we have the same code
#     available for both the CreateStatement and RunController classes.
#
#     Note that these methods are still all abstract  -  Derrived classes
#     will still need to override '_getStatRates' to return the DB::ItemCollection
#     that represents their particular stat rate table.
#

sub _getStatRateTable {
    my ($self) = @_;

    if ( !$self->{_statRateTable} ) {
        my $statRates = $self->_getStatRates();
        while ( my $statRate = $statRates->next() ) {
            $self->{_statRateTable}{ $statRate->stat_rate_id } = $statRate;
        }
    }

    return $self->{_statRateTable};
}

# Generally we are going to be asking for the same stat rate for the same
# date over and over and over and over... so it makes sense to cache it.
#
sub _getStatRateID {
    my ( $self, $endDate ) = @_;

    if ( !$self->{_statRateByDate} ) {
        my $table = $self->_getStatRateTable();

        # get the stat rates, sorted by date
        #
        my @sortedRateIDs = sort { $table->{$b}->date_effective cmp $table->{$a}->date_effective } keys %$table;

        my @sortedStatRates;
        my $ringtoneStatRateType = $self->_ringtoneStatRateType();

        foreach my $sortedID (@sortedRateIDs) {
            if ( $table->{$sortedID}->stat_rate_type_id != $ringtoneStatRateType ) {
                push @sortedStatRates, $table->{$sortedID};
            }
        }
        $self->{_statRateByDate} = \@sortedStatRates;
    }

    my $id;
    my $sortedStatRates = $self->{_statRateByDate};
    foreach my $testRate (@$sortedStatRates) {
        if ( $testRate->date_effective le $endDate ) {
            $id = $testRate->stat_rate_id;
            last;
        }
    }

    return $id;
}

sub _getRingStatRateID {
    my ( $self, $endDate ) = @_;

    if ( !$self->{_ringStatRateByDate} ) {
        my $table = $self->_getStatRateTable();

        # get the stat rates, sorted by date
        #
        my @sortedRateIDs = sort { $table->{$b}->date_effective cmp $table->{$a}->date_effective } keys %$table;

        my @sortedStatRates;
        my $ringtoneStatRateType = $self->_ringtoneStatRateType();

        foreach my $sortedID (@sortedRateIDs) {
            if ( $table->{$sortedID}->stat_rate_type_id == $ringtoneStatRateType ) {
                push @sortedStatRates, $table->{$sortedID};
            }
        }
        $self->{_ringStatRateByDate} = \@sortedStatRates;

        Common::Log::Print( "_getRingStatRateID - Build this sorted table: " . Dumper( $self->{_ringStatRateByDate} ) );
    }

    my $id;
    my $sortedStatRates = $self->{_ringStatRateByDate};
    foreach my $testRate (@$sortedStatRates) {
        if ( $testRate->date_effective le $endDate ) {
            $id = $testRate->stat_rate_id;
            last;
        }
    }

    #Common::Log::Print("_getRingStatRateID - returned id $id for date $endDate");

    return $id;
}

sub _getStatRate {
    my ( $self, $statRateID ) = @_;

    my $table    = $self->_getStatRateTable();
    my $statRate = $table->{$statRateID};
    assert( $statRate, "no stat rate found with id $statRateID" );

    return ( $statRate->rate, $statRate->minute_rate );
}

sub _ringtoneStatRateType {
    my ($self) = @_;
    assert( 0, 'override this' );
}

sub _prevalidateRun {
    my $self = shift;

    $self->_report("Begin Mechanical Pre Run Validation.");

    return $self->_prevalidateLicenseShare()
      && $self->SUPER::_prevalidateRun();
}

sub _prevalidateLicenseShare {
    my $self = shift;

    my $collection = $self->_getLicenseShareErrors();

    if ( defined($collection) ) {
        if ( $collection->hasNext() ) {
            while ( my $row = $collection->next() ) {
                $self->_report( "Track license share == 0: " . Dumper $row );
            }

            return;
        } else {
            $self->_report("License share test: Passed");
        }
    } else {
        $self->_report("*****  _getLicenseShareErrors not overloaded. Not validating license share! *****");
    }

    return 1;
}

sub _calculateEffectiveRate {
    my (
        $self,          $issueStatRateID, $saleStatRateID, $rateBasis, $rateType, $pennyRate,
        $percentOfRate, $duration,        $share,          $lockDate,  $trackLicense
    ) = @_;

    $self->_report(
"_calculateEffectiveRate : issueStatRateID = $issueStatRateID, saleStatRateID = $saleStatRateID, rateBasis = $rateBasis, pennyRate = $pennyRate, percentOfRate = $percentOfRate, duration = $duration, share = $share",
        3
    );

    # Calculate the effective rate per unit to pay.
    # rate basis - either sale or release
    # rate term - full, partial, or penny
    #
    my $appliedStatRateID;
    my ( $baseStatRate, $minuteRate );
    my $effectiveRate;

    if ( RPS::License::RateType::kPenny == $rateType ) {
        $effectiveRate     = $pennyRate;
        $appliedStatRateID = $saleStatRateID;
        $self->_report( "penny rate : minimum, pennyRate = $pennyRate", 3 );
    } else {
        if ( RPS::License::RateBasis::kSale == $rateBasis ) {
            ( $baseStatRate, $minuteRate ) = $self->_getStatRate($saleStatRateID);
            $self->_report( "rate basis = sale, baseRate = $baseStatRate, minute rate = $minuteRate", 3 );
            $appliedStatRateID = $saleStatRateID;
        }

        # !!! For this to convert to 'lock date', I'm going to _NEED_ to know the lock date. duh!
        elsif ( RPS::License::RateBasis::kLock == $rateBasis ) {
            my $lockStatRateID;

            if ( $trackLicense->type == RPS::DB::Item::TrackLicense::kRingtone ) {
                $lockStatRateID = $self->_getRingStatRateID($lockDate);
            } else {
                $lockStatRateID = $self->_getStatRateID($lockDate);
            }

            ( $baseStatRate, $minuteRate ) = $self->_getStatRate($lockStatRateID);
            $self->_report( "rate basis = lock, baseRate = $baseStatRate, minute rate = $minuteRate", 3 );
            $appliedStatRateID = $lockStatRateID;
        } else {
            die RPS::Mechanical::Process::Exception->new( "Unknown rate basis: " . $rateBasis );
        }

        if ( RPS::License::RateType::kFull == $rateType ) {

            # Use the base stat rate for songs of 5 minutes or less.
            # Use the minute rate for longer songs.
            #

            # JPK - If we don't actually _know_ the duration of this track, we'll end up using the
            # min_stat rate.   Now, that seems wrong to me.  But, the unfortunate reality here is that
            # many songs in our data won't have timings.   And we will encounter this code in that
            # situation fairly frequently - For example, when performing the 'album rate' calculation
            # for a controlled comp license, if one of the tracks on that album is full stat without
            # timings, we have to do _something_.   Although I'd prefer to actually blow up and get
            # that data fixed, we've decided to push ahead with the min stat rate.  If in the future
            # they update that track's timing data (because they noticed that they've been accruing
            # on that track because we've been ignoring that 'bad' track), an adjustment would ensue.
            #
            $effectiveRate = $baseStatRate;
            if ( $duration > 300 ) {
                my $minutes      = ceil( $duration / 60 );
                my $minBasedRate = ( $minutes * $minuteRate );

                $self->_report( "full rate. minutes = $minutes, duration = $duration, minute based rate = $minBasedRate", 3 );
                $effectiveRate = ( $minBasedRate > $effectiveRate ? $minBasedRate : $effectiveRate );
            }
        } elsif ( RPS::License::RateType::kMinimum == $rateType ) {
            $effectiveRate = $baseStatRate;
            $self->_report( "min rate : minimum, baseStatRate = $baseStatRate", 3 );
        } else {
            die RPS::Mechanical::Process::Exception->new( "Unknown rate type: " . $rateType );
        }
    }

    my $baseRate = $effectiveRate;

    # 0 %  is not an option...
    #
    if ( $percentOfRate > 0 && RPS::License::RateType::kPenny != $rateType ) {
        $effectiveRate = $effectiveRate * ( $percentOfRate / 100 );
    }
    assert( $effectiveRate > 0 );

    # Now that we have the effective rate, cut it according to the share percentage.
    #
    if ( RPS::License::RateType::kPenny != $rateType ) {
        $effectiveRate = $effectiveRate * ( $share / 100 );
    }

    # For my final trick - round these down to 4 places right of the decimal point.
    #
    $effectiveRate = Common::RSMath::round( $effectiveRate, 4 );
    $baseRate      = Common::RSMath::round( $baseRate,      4 );

    $self->_report( "returning effective rate = $effectiveRate", 3 );
    return ( $effectiveRate, $baseRate, $appliedStatRateID );
}

sub _productIsDigital {
    my ( $self, $product ) = @_;
    assert( 'RPS::DB::Item::Product' eq ref($product) );
    my $typeID = $product->product_type_id;

    if (   RPS::DB::Item::Product::kProductTypeDigital == $typeID
        || RPS::DB::Item::Product::kProductTypeDigitalTrack == $typeID ) {
        return 1;
    }

    return 0;
}

sub _findRegionsForCountryCode {
    my ( $self, $countryCode ) = @_;

    # Probably worth caching this info.
    #
    if ( !$self->{_regionCountryMap} ) {
        my $allMappings = RPS::DB::Item::RegionCountryMap->GetAll();
        while ( my $map = $allMappings->next() ) {
            push @{ $self->{_regionCountryMap}{ $map->country_code } }, $map->region_id;
        }
    }

    return $self->{_regionCountryMap}{$countryCode};
}

sub _getYear {
    my ( $self, $sale ) = @_;
    my ( $year, $month, $day ) = split( '-', $sale->date_end );
    return $year;
}

1;
