#---------------------------------------------------------------
# ____                   _ _         ____  _                    
#|  _ \ ___  _   _  __ _| | |_ _   _/ ___|| |__   __ _ _ __ ___ 
#| |_) / _ \| | | |/ _` | | __| | | \___ \| '_ \ / _` | '__/ _ \
#|  _ < (_) | |_| | (_| | | |_| |_| |___) | | | | (_| | | |  __/
#|_| \_\___/ \__, |\__,_|_|\__|\__, |____/|_| |_|\__,_|_|  \___|
#            |___/             |___/                            
#
# Copyright (C) 2012 RoyaltyShare, Inc.   All Rights Reserved
#---------------------------------------------------------------

package RPS::ArtistRoyalty::Fast::Final::ArtistRoyaltyIncomeItem;

use strict;
use Data::Dumper;
use File::Path;
use POSIX qw(ceil);

use lib '/app/tools/common/lib';
use lib '/app/tools/rps/lib';
use lib '/app/tools/raptor/lib';
use Common::RSApp;

use Data::Dumper;
use DB_File;

use Common::Log;
use Common::Assert;

use RPS::ArtistRoyalty::Fast::Mapped::Data;
use RPS::ArtistRoyalty::Fast::Static::Terms;
use RPS::ArtistRoyalty::Fast::Static::LiquidationSchedules;

use RPS::ArtistRoyalty::Fast::Final::SaleRunMap;
use RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve;
use RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserveRun;

use base 'RPS::ArtistRoyalty::Fast::Final';


use constant kArtistRoyaltyIncomeItemID     => 0;
use constant kArtistRoyaltyAlbumID          => 1;
use constant kAlbumID                       => 2;
use constant kTrackID                       => 3;
use constant kUpcEan                        => 4;
use constant kArtistContractTermID          => 5;
use constant kIncomeSourceID                => 6;
use constant kRegionID                      => 7;
use constant kChannelID                     => 8;
use constant kPriceLevelID                  => 9;
use constant kContractRateTypeID            => 10;
use constant kRate                          => 11;
use constant kRateReduction                 => 12;
use constant kPercentageOfSales             => 13;
use constant kPackagingDeduction            => 14;
use constant kFreeGoodsDeduction            => 15;
use constant kSales                         => 16;
use constant kReturns                       => 17;
use constant kRevenue                       => 18;
use constant kUnitsReserved                 => 19;
use constant kRevenueReserved               => 20;
use constant kUnitsLiquidated               => 21;
use constant kRevenueLiquidated             => 22;
use constant kNetUnits                      => 23;
use constant kNetRevenue                    => 24;
use constant kNetRate                       => 25;
use constant kUsesDefaultNetRate            => 26;
use constant kPrice                         => 27;
use constant kTotal                         => 28;

use constant kBaseRate                      => 29;
use constant kProrateTrackCount             => 30;

# These are 'private' constants...
use constant kProductID                     => 31;
use constant kIsDigital                     => 32;
use constant kTermRef                       => 33;
use constant kAlbumRef                      => 34;


my @gFields = 
(
'artist_royalty_income_item_id',
'artist_royalty_album_id',
'album_id',
'track_id',
'upc_ean',
'artist_contract_term_id',
'income_source_id',
'region_id',
'channel_id',
'price_level_id',
'contract_rate_type_id',
'rate',
'rate_reduction',
'percentage_of_sales',
'packaging_deduction',
'free_goods_deduction',
'sales',
'returns',
'revenue',
'units_reserved',
'revenue_reserved',
'units_liquidated',
'revenue_liquidated',
'net_units',
'net_revenue',
'net_rate',
'uses_default_net_rate',
'price',
'total',
'base_rate', # FB113
'prorate_track_count', # FB113
);

sub Fields { return \@gFields; }


my $gLiquidationSchedules;


#root@localhost [C_WELK]> desc artist_royalty_income_item;
#+-------------------------------+-----------------------+------+-----+---------+----------------+
#| Field                         | Type                  | Null | Key | Default | Extra          |
#+-------------------------------+-----------------------+------+-----+---------+----------------+
#| artist_royalty_income_item_id | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
#| artist_royalty_album_id       | int(10) unsigned      | NO   | MUL | 0       |                |
#| album_id                      | int(10) unsigned      | NO   | MUL | 0       |                |
#| track_id                      | int(10) unsigned      | NO   | MUL | 0       |                |
#| upc_ean                       | varchar(25)           | NO   |     |         |                |
#| artist_contract_term_id       | int(10) unsigned      | NO   | MUL | 0       |                |
#| income_source_id              | int(10) unsigned      | YES  |     | NULL    |                |
#| region_id                     | tinyint(3) unsigned   | YES  |     | NULL    |                |
#| channel_id                    | tinyint(3) unsigned   | YES  |     | NULL    |                |
#| price_level_id                | tinyint(3) unsigned   | YES  |     | NULL    |                |
#| contract_rate_type_id         | tinyint(3) unsigned   | YES  |     | NULL    |                |
#| rate                          | decimal(8,4) unsigned | YES  |     | NULL    |                |
#| rate_reduction                | decimal(8,4) unsigned | YES  |     | NULL    |                |
#| percentage_of_sales           | decimal(8,4) unsigned | YES  |     | NULL    |                |
#| packaging_deduction           | decimal(8,4) unsigned | YES  |     | NULL    |                |
#| free_goods_deduction          | decimal(8,4) unsigned | YES  |     | NULL    |                |
#| sales                         | int(10)               | NO   |     | 0       |                |
#| returns                       | int(10)               | NO   |     | 0       |                |
#| revenue                       | decimal(16,4)         | YES  |     | NULL    |                |
#| units_reserved                | int(10)               | NO   |     | 0       |                |
#| revenue_reserved              | decimal(16,4)         | NO   |     | 0.0000  |                |
#| units_liquidated              | int(10)               | NO   |     | 0       |                |
#| revenue_liquidated            | decimal(16,4)         | NO   |     | 0.0000  |                |
#| net_units                     | int(10)               | NO   |     | 0       |                |
#| net_revenue                   | decimal(16,4)         | YES  |     | NULL    |                |
#| net_rate                      | decimal(8,4)          | YES  |     | NULL    |                |
#| uses_default_net_rate         | tinyint(1)            | NO   |     | 0       |                |
#| price                         | decimal(16,4)         | YES  |     | NULL    |                |
#| total                         | decimal(16,4)         | YES  |     | NULL    |                |
#+-------------------------------+-----------------------+------+-----+---------+----------------+
#29 rows in set (0.00 sec)


sub FileName { 'artist_royalty_income_item' }
sub _MaxIDSql { 'SELECT MAX(artist_royalty_income_item_id) FROM artist_royalty_income_item' }


# This class will encapsulate two different files: the income items, and the sale run map.
# At least that's the plan...
#
# So I will need to override the class Init method to do some additional stuff.
#


sub Init
{
    my ($class, $outputDirectory, $dataPath, $runID) = @_;
    assert($dataPath, "MISSING dataPath");

    # Call the inherited method first.
    #
    $class->SUPER::Init($outputDirectory);

# JPK - Why do we do this here?  Why bury this within this class?  Seems unnecessary.
    # Initialize the SaleRunMap class.
    #
    RPS::ArtistRoyalty::Fast::Final::SaleRunMap->Init($outputDirectory, $runID);

    RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve->Init($outputDirectory, $dataPath, $runID);
    RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserveRun->Init($outputDirectory, $runID);


    # Load up the liquidation schedules.
    # !!! 
    #
    $gLiquidationSchedules = RPS::ArtistRoyalty::Fast::Static::LiquidationSchedules->GetLiquidationSchedules($dataPath);
}


# The actual _behavior_ of this class, in terms of how we accumulate, sum, and calculate
# the data, really is determined by the TermType.
# For example, it appears to me that the kRevenue value will be calculated differently for
# Net Revenue terms, compared to all the others.
#
# So, I'll probably move all that logic into TermType.  I can't collapse TermType into this class,
# because I also use TermType in the mapSales.pm script.

sub new
{
    my ($class, $data, $album, $term) = @_;

    my $item = $class->BlankItem();
    $class->_IncrementID();

    my $termID = int($data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kTermID]);

    my $rateTypeID =  $term->[RPS::ArtistRoyalty::Fast::Static::Terms::kContractRateTypeID];

    $item->[kArtistRoyaltyIncomeItemID] = $class->_CurrentID();
    $item->[kArtistRoyaltyAlbumID] = $album->id();

    $item->[kAlbumID] = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kAlbumID];
    $item->[kTrackID] = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kTrackID];
    $item->[kArtistContractTermID] = $termID;
    $item->[kPrice] = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kPrice];
    $item->[kIncomeSourceID] = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kIncomeSourceID];
    $item->[kRegionID] = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kRegionID];
    $item->[kChannelID] = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kChannelID];
    $item->[kPriceLevelID] = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kPriceLevelID];
    $item->[kContractRateTypeID] =  $rateTypeID;
    $item->[kRate] =  $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kRate];

    $item->[kBaseRate] =  $term->[RPS::ArtistRoyalty::Fast::Static::Terms::kRate]; # FB113
    $item->[kProrateTrackCount] =  $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kProrateTrackCount]; # FB113

    $item->[kRateReduction] = $term->[RPS::ArtistRoyalty::Fast::Static::Terms::kRateReduction];
    $item->[kPercentageOfSales] = $term->[RPS::ArtistRoyalty::Fast::Static::Terms::kPercentageOfSales];
    $item->[kPackagingDeduction] = $term->[RPS::ArtistRoyalty::Fast::Static::Terms::kPackagingDeduction];
    $item->[kFreeGoodsDeduction] = $term->[RPS::ArtistRoyalty::Fast::Static::Terms::kFreeGoodsDeduction];

    if ($term->[RPS::ArtistRoyalty::Fast::Static::Terms::kPriority] == 0)
    {
        $item->[kUsesDefaultNetRate] = 1;
    }


    # I need to remember the product id, so I can create the reserves.
    # Honestly, though, why do we freaking bother with it, since the income_item
    # doesn't contain that record anyway?  Seems like a total waste of time to me.
    #

    push @$item, $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kProductID];

    push @$item, 0;
    $term->accumulateData($data, $item);

    # Any extra crap we need 'secretly', we'll just push onto the end of the array.
    #

    # This is the 'is digital flag'.   We'll be setting this from the incoming MappedData.
    # We need to pass this along when calculating the final rate.
    # It's a bit of a hack, honestly, but I don't have product_id at that point, and
    # I need to know it...
    #

    push @$item, $term;
    push @$item, $album;


    # We'll keep an array of SaleRunMap items around.
    # !!! WHY?
    #
#    my @saleRunMaps;

    # !!! If the data is actually a Reserve (remember, we're using the same Final class for both sales and reserves), then
    #     we don't want to make a sale_run_map entry.   In that case, saleID == reserveID anyway.
    #
    # !!! Wait, why am I even doing this at all here?
    # !!! We are creating sale_run_map entries already when the term accumulates the data.   I think this is redundant and wrong.
    #
#    my $saleID = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kSaleID];
#    if (0 != $saleID)
#    {
#        my $saleRunMap = RPS::ArtistRoyalty::Fast::Final::SaleRunMap->new($saleID, $item->[kArtistRoyaltyIncomeItemID]);
## !!! Just let it die, let it write out now.
#    }

#    push @$item, \@saleRunMaps;

    return bless($item, $class);
}


sub process
{
    my ($self, $data) = @_;

    my $term = $self->[kTermRef];

    # Test whether we're still looking at data for 'this' object.
    #
    # !!! I believe I'll need to also consider kPrice here, but only if our term is a Document Price term...
    # !!! I think.
    #
    # !!! This type check is not correct.  We need to explicitly test for either type 1 or 2 (and use the damned constants).
    #

    if ( (RPS::ArtistRoyalty::Fast::Mapped::Data::kTypeSale != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kType]
       && RPS::ArtistRoyalty::Fast::Mapped::Data::kTypeReserve != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kType])
     || $self->[kTrackID] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kTrackID]
     || $self->[kArtistContractTermID] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kTermID]
     || $self->[kIncomeSourceID] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kIncomeSourceID]
     || $self->[kRegionID] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kRegionID]
     || $self->[kChannelID] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kChannelID]
     || $self->[kPriceLevelID] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kPriceLevelID]
     || $self->[kRate] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kRate]

     # Break on price if this is a document price-based term and the price is different
     #
     || ($term->isDocumentPriceBased() && $self->[kPrice] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kPrice] )     

     # Break on price if we're dealing with a reserve, the term isn't net revenue and the price is different
     #
     || (RPS::ArtistRoyalty::Fast::Mapped::Data::kTypeReserve == $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kType]
         && !$term->isNetRevenue()
         && $self->[kPrice] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kPrice] )     
    )

    {
        return undef;
    }

    # JPK - Not sure why we got rid of the term->isDocumentPriceBased test for that last bit.
    #       This may be causing issues with aggregation in situations where it does not matter,
    #       such as non-payable terms.
    # So, re-enable it, and we'll see what happens...

    # ES - Stop aggregating the income item due to price difference if:
    #         a) The term is document price-based; or
    #         b) We're dealing with a reserve liquidation.
    # (FB3998/FB5982).


    $term->accumulateData($data, $self);


    Log->info();

    return 1;
}




# Override _output to do some final calculations.
#
sub _output
{
    my ($self, $ITEMS) = @_;

#    my $saleRunMaps = pop @$self;
    my $album = pop @$self;
    my $term = pop @$self;
    my $isDigital = pop @$self;
    my $productID = pop @$self;


    # JPK - I would like to supress outputting line items if they don't actually contain any data.
    # This comes into play the only reason this object exists is because of a Reserve that we did not
    # actually process.
    #
    if (0 == $self->[kSales]
     && 0 == $self->[kReturns]
     && 0 == $self->[kRevenue]
     && 0 == $self->[kUnitsReserved]
     && 0 == $self->[kRevenueReserved]
     && 0 == $self->[kUnitsLiquidated]
     && 0 == $self->[kRevenueLiquidated]
     && 0 == $self->[kNetUnits]
     && 0 == $self->[kTotal]
    )
    {
        # this is a no-op
        #
        return;
    }




#    $saleRunMaps = undef;  # Force SaleRunMap to output state.

    $term->calculateFinalRateData($self, $isDigital);
    $term->updateAlbumSummary($self, $album);


    my $isNetRevenue = $term->isNetRevenue();

    my $schedule = $gLiquidationSchedules->{$term->[RPS::ArtistRoyalty::Fast::Static::Terms::kArtistContractID]};

    my $initialUnits = $self->[kUnitsReserved];
    my $initialRevenue = $self->[kRevenueReserved];
    if ($schedule
     && (($initialUnits > 0 && ! $isNetRevenue) || ($initialRevenue > 0 && $isNetRevenue)))
    {
        my $unitsLeft = $initialUnits;
        my $revenueLeft = $initialRevenue;


        my $max = scalar @$schedule;
        for (my $i = 0; $i < $max; $i++)
        {
            my $periodPercent = $schedule->[$i];

            next unless $periodPercent->[1];

            my $reserve = RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve->new();

            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kArtistContractTermID] = $self->[kArtistContractTermID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kIncomeSourceID] = $self->[kIncomeSourceID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kRegionID] = $self->[kRegionID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kChannelID] = $self->[kChannelID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kPriceLevelID] = $self->[kPriceLevelID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kOriginalStatementItemID] = $self->[kArtistRoyaltyIncomeItemID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kAlbumID] = $self->[kAlbumID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kTrackID] = $self->[kTrackID];
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kEffectiveRate] = $self->[kRate]; # !!! This may be wrong
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kPrice] = $self->[kPrice]; # !!! This may be wrong
            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kPeriodsRemaining] = $periodPercent->[0] + 1;


            if ($isNetRevenue)
            {
                my $periodRevenue = Common::RSMath::round($initialRevenue * ($periodPercent->[1] / 100), 2);

                # If this is the last period, make sure we don't leave any money left over...
                #
                $periodRevenue = $revenueLeft unless ($periodRevenue < $revenueLeft && $i < ($max - 1));
                $revenueLeft -= $periodRevenue;
                $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kRevenueBased] = 1;
                $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kRevenue] = $periodRevenue;
            }
            else
            {
                my $periodUnits = ceil($initialUnits * ($periodPercent->[1] / 100));

                $periodUnits = $unitsLeft unless $periodUnits < $unitsLeft;
                $unitsLeft -= $periodUnits;

                $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kUnits] = $periodUnits;
            }

            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kProductID] = $productID;

            $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kProrateTrackCount] = $self->[kProrateTrackCount]; # FB113


            # JPK - We also need to create a 'reserve_run' entry to make sure we decrement the periods remaining when we commit.
            # !!! But, do we KNOW the id yet?  No, we don't.
            #
            my $reserveID = $reserve->[RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve::kArtistContractTermReserveID];
            my $newReserveMap = RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserveRun->new($reserveID);

        }
    }


    $self->SUPER::_output($ITEMS);
}


# Override to force the SaleRunMap to also commit.
#
sub Commit
{
    my ($class, $dataPath) = @_;
    RPS::ArtistRoyalty::Fast::Final::SaleRunMap->Commit($dataPath);
    RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserve->Commit($dataPath);
    RPS::ArtistRoyalty::Fast::Final::ArtistContractTermReserveRun->Commit($dataPath);

    # !!! Going to also need to do this for ArtistContractTermReserveRun

    $class->SUPER::Commit($dataPath);
}


1;

