#---------------------------------------------------------------
# ____                   _ _         ____  _
#|  _ \ ___  _   _  __ _| | |_ _   _/ ___|| |__   __ _ _ __ ___
#| |_) / _ \| | | |/ _` | | __| | | \___ \| '_ \ / _` | '__/ _ \
#|  _ < (_) | |_| | (_| | | |_| |_| |___) | | | | (_| | | |  __/
#|_| \_\___/ \__, |\__,_|_|\__|\__, |____/|_| |_|\__,_|_|  \___|
#            |___/             |___/
#
# 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 unsigned          | NO   | PRI | NULL    | auto_increment |
#| artist_royalty_album_id       | int unsigned          | NO   | MUL | 0       |                |
#| album_id                      | int unsigned          | NO   | MUL | 0       |                |
#| track_id                      | int unsigned          | NO   | MUL | 0       |                |
#| upc_ean                       | varchar(25)           | NO   |     |         |                |
#| artist_contract_term_id       | int unsigned          | NO   | MUL | 0       |                |
#| income_source_id              | int unsigned          | YES  |     | NULL    |                |
#| region_id                     | tinyint unsigned      | YES  |     | NULL    |                |
#| channel_id                    | tinyint unsigned      | YES  |     | NULL    |                |
#| price_level_id                | tinyint unsigned      | YES  |     | NULL    |                |
#| contract_rate_type_id         | tinyint unsigned      | YES  |     | NULL    |                |
#| base_rate                     | decimal(8,4) unsigned | YES  |     | NULL    |                |
#| prorate_track_count           | tinyint unsigned      | NO   |     | 0       |                |
#| 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                   | NO   |     | 0       |                |
#| returns                       | int                   | NO   |     | 0       |                |
#| revenue                       | decimal(16,4)         | YES  |     | NULL    |                |
#| units_reserved                | int                   | NO   |     | 0       |                |
#| revenue_reserved              | decimal(16,4)         | NO   |     | 0.0000  |                |
#| units_liquidated              | int                   | NO   |     | 0       |                |
#| revenue_liquidated            | decimal(16,4)         | NO   |     | 0.0000  |                |
#| net_units                     | bigint                | 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    |                |
#+-------------------------------+-----------------------+------+-----+---------+----------------+
#31 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];

    # Note: depending on term type, $self->[kNetUnits] may not be available until
    # the income item is output, so we look at the item sales and revenue.
    my $itemNetUnits = $self->[kSales] - $self->[kReturns];
    my $dataNetUnits = $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kSales] - $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kReturns];

    # 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->isRevenueBased()
            && $self->[kPrice] != $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kPrice] )

        # Break on price if a digital sale would result in a zero net unit income item.
        #
        || (   $data->[RPS::ArtistRoyalty::Fast::Mapped::Data::kIsDigital]
            && 0 == $itemNetUnits + $dataNetUnits
            && $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.

    # We need to swap in \N for empty region IDs, so that mysqlimport won't convert it into a 0.
    $self->[kRegionID] = ($self->[kRegionID] =~ /\S/) ? $self->[kRegionID] : '\N';


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

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

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

    my $initialUnits   = $self->[kUnitsReserved];
    my $initialRevenue = $self->[kRevenueReserved];
    if ( $schedule
        && ( ( $initialUnits > 0 && !$isRevenueBased ) || ( $initialRevenue > 0 && $isRevenueBased ) ) ) {
        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 ($isRevenueBased) {
                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;

