#------------------------------------------------------------
# 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;
