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

package RPS::Report::Dynamic::Contract::Artist;

use strict;
use warnings;

use IO::File;
use POSIX qw{tmpnam};

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::Client;
use Common::Exception;
use Common::Log;
use Common::RSMath;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::DynamicReport::Contract::Artist;
use RPS::DB::Item::ContractTermSource;
use RPS::DB::Item::PriceLevel;
use RPS::DB::Item::ContractRateType;
use RPS::DB::Item::AlbumContract;
use RPS::DB::Item::TrackContract;
use RPS::DB::Item::Album;
use RPS::DB::Item::Track;
use RPS::DB::Item::Label;
use RPS::DB::Item::Artist;
use RPS::DB::Item::Master;
use RPS::DB::Item::Expense;
use RPS::DB::Item::ExpenseType;
use RPS::DB::Item::ExpenseName;

use base 'RPS::Report::Dynamic::Contract::Base';

sub Type    { 'Artist & Producer' }
sub FilenameType { 'Artist Contracts' }


# defined at the end of this file
#
sub _ParamClass { 'RPS::Report::Dynamic::Contract::Artist::Parameters'; }

sub ClientTypeMask { return Common::Client::kArtist; }

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

    my $cols = [];

    push( @$cols, $self->_getNameColumnHash('contract-title', 'title' ) );
    push( @$cols, $self->_getNameColumnHash('artist', 'artist_description' ) );
    push( @$cols, $self->_getNameColumnHash('contract-id', 'client_contract_id' ) );
    push( @$cols, $self->_getNameColumnHash('payor', 'payor' ) );
    push( @$cols, $self->_getNameColumnHash('payee', 'payee' ) );
    push( @$cols, $self->_getNameColumnHash('rs-payee-id', 'artist_payee_id' ) );
    push( @$cols, $self->_getNameColumnHash('client-account-no', 'artist_client_account_id' ) );
    push @$cols, {
        header => {
            name => 'date-issued',
        },
        column => {
            field => 'date_issued',
            method => '_formatDate',  
            excel => 'date:Y-M-D',
        },
    };
#    push( @$cols, $self->_getNameColumnHash('term-start', 'term_start' ) );
#    push( @$cols, $self->_getNameColumnHash('term-end', 'term_end' ) );
    push @$cols, {
        header => {
            name => 'term-start',
        },
        column => {
            field => 'term_start',
            excel => 'date:Y-M-D',
        },
    };
    push @$cols, {
        header => {
            name => 'term-end',
        },
        column => {
            field => 'term_end',
            excel => 'date:Y-M-D',
        },
    };

    push( @$cols, $self->_getNameColumnHash('comments', 'comments' ) );


    push @$cols, {
        header => {
            name => 'contract-date-created',
        },
        column => {
            field => 'date_created',
            excel => 'date:Y-M-D',
        },
    };
    push @$cols, {
        header => {
            name => 'contract-created-by',
        },
        column => {
            field => 'created_by',
            method => '_userIDToUserName',
        },
    };
    push @$cols, {
        header => {
            name => 'contract-date-modified',
        },
        column => {
            field => 'date_modified',
            excel => 'date:Y-M-D',
        },
    };
    push @$cols, {
        header => {
            name => 'contract-modified-by',
        },
        column => {
            field => 'modified_by',
            method => '_userIDToUserName',
        },
    };
    push( @$cols, $self->_getNameColumnHash('rs-contract-id', 'rs_contract_id' ) );
    
    $self->_colsWithTerms( $cols );
    $self->_colsAttachedAssets( $cols );
    $self->_colsIncomeSources( $cols );    

    return $cols;
}

sub _colsWithTerms {
    my $self = shift;
    my $cols = shift;

    assert($self);
    return unless( $self->_withTerms() );

    push @$cols, { 
            header => { 
                name => 'reserve-rate', 
            },    
            column => { 
                field => 'reserve_rate', 
            } 
        },
        { 
            header => { 
                name => 'reserve-period-1', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls1',
            } 
        },
        { 
            header => { 
                name => 'reserve-period-2', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls2',
            } 
        },
        { 
            header => { 
                name => 'reserve-period-3', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls3',
            } 
        },
        { 
            header => { 
                name => 'reserve-period-4', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls4',
            } 
        },
        { 
            header => { 
                name => 'reserve-period-5', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls5',
            } 
        },
        { 
            header => { 
                name => 'reserve-period-6', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls6',
            } 
        },
        { 
            header => { 
                name => 'reserve-period-7', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls7',
            } 
        },
        { 
            header => { 
                name => 'reserve-period-8', 
            },    
            column => { 
                field => 'rs_contract_id',
                method => '_ls8',
            } 
        };

    push( @$cols, $self->_getNameColumnHash('priority', 'priority' ) );
#    push( @$cols, $self->_getNameColumnHash('source', 'source' ) );
    push @$cols, {
        header => {
            name => 'source',
        },
        column => {
            field => 'contract_term_source_id',
            method => '_idToContractTermSource',
        },
    };
    push( @$cols, $self->_getNameColumnHash('region', 'region' ) );
    push( @$cols, $self->_getNameColumnHash('channel', 'channel' ) );
#    push( @$cols, $self->_getNameColumnHash('price', 'price' ) );
    push @$cols, {
        header => {
            name => 'price',
        },
        column => {
            field => 'price_level_id',
            method => '_idToPriceLevel',
        },
    };
#    push( @$cols, $self->_getNameColumnHash('rate-type', 'rate_type' ) );
    push @$cols, {
        header => {
            name => 'rate-type',
        },
        column => {
            field => 'contract_rate_type_id',
            method => '_idToRateType',
        },
    };
    push( @$cols, $self->_getNameColumnHash('rate', 'rate' ) );
    push( @$cols, $self->_getNameColumnHash('rate-reduction', 'rate_reduction' ) );
    push( @$cols, $self->_getNameColumnHash('percentage-of-sales', 'percentage_of_sales' ) );
    push( @$cols, $self->_getNameColumnHash('packaging', 'packaging_deduction' ) );
    push( @$cols, $self->_getNameColumnHash('free-goods', 'free_goods_deduction' ) );
    push( @$cols, $self->_getNameColumnHash('term-status', 'term_status' ) );
    push( @$cols, $self->_getNameColumnHash('rs-term-id', 'artist_contract_term_id' ) );
}

sub _colsAttachedAssets {
    my $self = shift;
    my $cols = shift;

    assert($self);

    return unless(  $self->_withAttachedAssets() );

    # These all become placeholders.
    # 
    push( @$cols, $self->_getEmptyColumnHash('album-title') );
    push( @$cols, $self->_getEmptyColumnHash('catalog-no') );
    push( @$cols, $self->_getEmptyColumnHash('album-artist') );
    push( @$cols, $self->_getEmptyColumnHash('label-name') );
    push( @$cols, $self->_getEmptyColumnHash('rs-album-id') );
    push( @$cols, $self->_getEmptyColumnHash('track-title') );
    push( @$cols, $self->_getEmptyColumnHash('track-artist') );
    push( @$cols, $self->_getEmptyColumnHash('track-isrc') );
    push( @$cols, $self->_getEmptyColumnHash('track-minutes') );
    push( @$cols, $self->_getEmptyColumnHash('track-seconds') );
    push( @$cols, $self->_getEmptyColumnHash('rs-track-id') );
    push( @$cols, $self->_getEmptyColumnHash('proration') );
    push( @$cols, $self->_getEmptyColumnHash('contract-status') );
    push( @$cols, $self->_getEmptyColumnHash('cross-collateralize') );

#    if( $self->_withExpenses ) {
#        push( @$cols, $self->_getEmptyColumnHash('deduction-type') );
#        push( @$cols, $self->_getEmptyColumnHash('expense-type') );
#        push( @$cols, $self->_getEmptyColumnHash('percent') );
##        push( @$cols, $self->_getEmptyColumnHash('amount') );
##        push( @$cols, $self->_getEmptyColumnHash('effective-amount') );
#        push @$cols, {
#            header => {
#                name => 'amount',
#                method => '_appendCurrencyCode',
#            },
#            column => { value => '', excel => 'number:0.00'},
#        };
#        push @$cols, {
#            header => {
#                name => 'effective-amount',
#                method => '_appendCurrencyCode',
#            },
#            column => { value => '', excel => 'number:0.00'},
#        };
#        push( @$cols, $self->_getEmptyColumnHash('memo') );
#        push( @$cols, $self->_getEmptyColumnHash('processed') );
#    }
}


sub _colsIncomeSources {
    my $self = shift;
    my $cols = shift;

    assert($self);
    return unless( $self->_withIncomeSources() );

    push( @$cols, $self->_getNameColumnHash('source-type', 'source_type' ) );
    push( @$cols, $self->_getNameColumnHash('income-type', 'income_type' ) );
    push( @$cols, $self->_getNameColumnHash('net-revenue-rate', 'net_revenue_rate' ) );
    push( @$cols, $self->_getNameColumnHash('expense-type', 'expense_type' ) );
    push( @$cols, $self->_getNameColumnHash('recoupable-percent', 'recoupable_percent' ) );
}

sub _getNameColumnHash {
    my $self = shift;
    my ($name, $col) = @_;

    return {
            header => {
                name => $name,
            },
            column => {
                field => $col,
            }
    };
}

sub _getEmptyColumnHash 
{
    my ($self, $name) = @_;
    return {
        header => { name => $name, },
        column => { value => '', },
    };
}


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

    # Dereference the parameters, see if we're constraining
    # the query by date somehow.
    #
    # !!! Don't want to just pass the CGI parameter straight through - That could
    # !!! cause problems...
    #
    my $dateType;
    my $dateTypeParam = $self->parameters()->{Criteria}->DateType();
    if ($dateTypeParam)
    {
        if ('Date Created' eq $dateTypeParam)
        {
            $dateType = 'date_created';
        }
        elsif ('Date Modified' eq $dateTypeParam)
        {
            $dateType = 'date_modified';
        }
    }

    return RPS::DB::Item::DynamicReport::Contract::Artist->ReportQuery
    (
        dateType => $dateType,
        startDate => $self->parameters()->{Criteria}->StartDate(),
        endDate => $self->parameters()->{Criteria}->EndDate(),
        withTerms => $self->_withTerms(),
#        withAttached => $self->_withAttachedAssets(),
        withIncomeSources => $self->_withIncomeSources(),
#        processedExpenses => $self->parameters()->{OptionalData}->ShowProcessedExpenses(),
#        unprocessedExpenses => $self->parameters()->{OptionalData}->ShowUnprocessedExpenses(),
    );
}

sub _withTerms {
    my $self = shift;
    my $reportType = $self->_reportType;

    return $reportType && $reportType eq 'WithTerms' ? 1 : 0;
}

sub _withAttachedAssets {
    my $self = shift;
    my $reportType = $self->_reportType;

    return $reportType && $reportType eq 'AttachedAssets' ? 1 : 0;
}

sub _withIncomeSources {
    my $self = shift;
    my $reportType = $self->_reportType;

    return $reportType && $reportType eq 'WithIncomeSources' ? 1 : 0;
}

#sub _withExpenses {
#    my $self = shift;
#    my $processedExpense = $self->parameters()->{OptionalData}->ShowProcessedExpenses();
#    my $unprocessedExpense = $self->parameters()->{OptionalData}->ShowUnprocessedExpenses();
#
#    if( ( $processedExpense && $processedExpense eq 'on' ) ||
#        ( $unprocessedExpense && $unprocessedExpense eq 'on' ) ) {
#            return 1;
#    }
#
#    return ( $processedExpense && $processedExpense != 0 ) ||
#           ( $unprocessedExpense && $unprocessedExpense != 0 );
#}

sub _reportType {
    shift->parameters()->{OptionalData}->ReportType();
}

my $gRateTypeCache;
sub _idToRateType
{
    my ($self, %args) = @_;
    my $rowItem = $args{row};
    my $fieldName = $args{field};

    my $id = $rowItem->$fieldName();

    my $out;
    if ($id)
    {
        if (! $gRateTypeCache)
        {
            $gRateTypeCache = {};
            my $types = RPS::DB::Item::ContractRateType->GetAll();
            while (my $type= $types->next())
            {
                $gRateTypeCache->{$type->contract_rate_type_id()} = $type->name();
            }
        }
        $out = $gRateTypeCache->{$id};
    }
    return $out;
}


my $gContractTermSourceCache;
sub _idToContractTermSource
{
    my ($self, %args) = @_;
    my $rowItem = $args{row};
    my $fieldName = $args{field};

    my $id = $rowItem->$fieldName();

    my $source;
    if ($id)
    {
        if (! $gContractTermSourceCache)
        {
            $gContractTermSourceCache = {};
            my $sources = RPS::DB::Item::ContractTermSource->GetAll();
            while (my $source = $sources->next())
            {
                $gContractTermSourceCache->{$source->contract_term_source_id()} = $source->name();
            }
        }
        $source = $gContractTermSourceCache->{$id};
    }
    else
    {
        # If the priority is 0, this is a default term, and the source should be listed as 'all'.
        if (0 == $rowItem->priority())
        {
            $source = 'All';
        }
    }

    return $source;
}


my $gPriceLevelCache;
sub _idToPriceLevel
{
    my ($self, %args) = @_;
    my $rowItem = $args{row};
    my $fieldName = $args{field};

    my $id = $rowItem->$fieldName();

    my $price;
    if (defined $id)
    {
        if (! $gPriceLevelCache)
        {
            $gPriceLevelCache = {};
            my $collection = RPS::DB::Item::PriceLevel->GetAll();
            while (my $item = $collection->next())
            {
                $gPriceLevelCache->{$item->price_level_id()} = $item->name();
            }
        }
        $price = $gPriceLevelCache->{$id};
        $price = 'all' if 0 == $id;
    }
    else
    {
        # We want the default term to display 'all' here.  The default term never actually explicitly
        # has a price level id.
        #
        if (0 == $rowItem->priority())
        {
            $price = 'all';
        }
    }
    return $price;
}


# Here's where we get wacky with this report.
# If we need to include the track and album contract stuff, we'll make a series of additional
# queries for each line (and turn each line into multiple lines if necessary).
# I'll use a 'state machine' to do this in a way that hopefully is understandable.
#
# !!! The painful bit here is the default term, or rather the lack thereof.
# !!! We're already going through a hell of a lot of trouble to fetch the expenses, and now
# !!! it seems that I will need to product 'virtual terms' to represent the missing, inactive
# !!! default terms.    Might be a bit tricky...
# !!! Well, happily, the 'withTerms' report is distinct from the 'withExpenses' report, so they have
# !!! totally different state machine needs.
#
sub _nextReportLineData
{
    my ($self) = @_;

    if ( $self->_withAttachedAssets() )
    {
        return $self->_nextReportLineDataWithAttachedAssets();
    }

    if ( $self->_withTerms() )
    {
        return $self->_nextReportLineDataWithTerms();
    }

    return $self->SUPER::_nextReportLineData();
}


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

    if (! $self->{_arrayIndex})
    {
        $self->_buildArrayIndex();
    }

    # So, here is the plan:
    # We will call the inherited method to retrieve the next line.
    # If this next line has a different contract id that the last line,
    # and that last line did not have a priority of 'default', we need to
    # make up a new default term line.
    # And of course we'll need to hang on to that next line to send out after this
    # fake line is sent.
    #

    # If we've held a line (because we needed to make up a fake default term line), send it out now.
    # 
    if ($self->{_heldLine})
    {
        my $outLine = $self->{_heldLine};
        $self->{_heldLine} = undef;
        $self->{_lastLine} = $outLine;
        return $outLine;
    }


    my $newLine = $self->SUPER::_nextReportLineData();
    return undef if (! $newLine && ! $self->{_lastLine});
    #
    # $newLine might be undefined if we've hit the end of the collection.
    # But we need to possibly product a fake default term line.


    if (! $self->{_lastLine})
    {
        # First time through we should land here.
        #
        $self->{_lastLine} = $newLine;
        return $newLine;
    }


    my $lastArtistContractID = $self->{_lastLine}->[ $self->{_arrayIndex}{'rs-contract-id'} ];
    my $currentArtistContractID = (defined $newLine ? $newLine->[ $self->{_arrayIndex}{'rs-contract-id'} ] : undef);
    my $lastPriority = $self->{_lastLine}->[ $self->{_arrayIndex}{'priority'} ];

    if ((! defined $currentArtistContractID || $lastArtistContractID != $currentArtistContractID)
     && $lastPriority ne 'default')
    {
        # We need to product a virtual inactive default term line.
        # We'll just mangle the last line.
        #
        $self->{_heldLine} = $newLine;

        my $outLine = $self->{_lastLine};
        $self->{_lastLine} = undef;

        $outLine->[ $self->{_arrayIndex}{'source'} ] = '';
        $outLine->[ $self->{_arrayIndex}{'priority'} ] = 'default';
        $outLine->[ $self->{_arrayIndex}{'rate-type'} ] = '% Net Revenue';
        $outLine->[ $self->{_arrayIndex}{'region'} ] = 'all';
        $outLine->[ $self->{_arrayIndex}{'price'} ] = 'all';
        $outLine->[ $self->{_arrayIndex}{'source'} ] = 'all';
        $outLine->[ $self->{_arrayIndex}{'rate'} ] = '0.0000';
        $outLine->[ $self->{_arrayIndex}{'rate-reduction'} ] = '100.0000';
        $outLine->[ $self->{_arrayIndex}{'percentage-of-sales'} ] = '0.0000';
        $outLine->[ $self->{_arrayIndex}{'packaging'} ] = '0.0000';
        $outLine->[ $self->{_arrayIndex}{'free-goods'} ] = '0.0000';
        $outLine->[ $self->{_arrayIndex}{'term-status'} ] = 'inactive';

        return $outLine;
    }


    $self->{_lastLine} = $newLine;
    return $newLine;
}


use constant kFetchArtistContract => 1;
use constant kFetchAlbumContract => 2;
use constant kFetchTrackContract => 3;
use constant kFetchAlbumContractExpense => 4;
use constant kFetchTrackContractExpense => 5;
sub _nextReportLineDataWithAttachedAssets
{
    my ($self) = @_;

    # We need to know the array index for each column.
    # This can shift around depending on what options were selected.
    # So we'll build a hash that maps column title to array index.
    #
    if (! $self->{_arrayIndex})
    {
        $self->_buildArrayIndex();
    }

    if (! $self->{_state})
    {
        # First time through...
        #
        $self->{_state} = kFetchArtistContract;
        $self->{_lastOutArtistContractID} = 0;
    }

    my $outLine;
    do
    {
        if (kFetchArtistContract == $self->{_state})
        {
            $outLine = $self->_handleStateFetchArtistContract();
        }
        elsif (kFetchAlbumContract == $self->{_state})
        {
            $outLine = $self->_handleStateFetchAlbumContract();
        }
        elsif (kFetchTrackContract == $self->{_state})
        {
            $outLine = $self->_handleStateFetchTrackContract();
        }
        elsif (kFetchAlbumContractExpense == $self->{_state})
        {
            $outLine = $self->_handleStateFetchAlbumContractExpense();
        }
        elsif (kFetchTrackContractExpense == $self->{_state})
        {
            $outLine = $self->_handleStateFetchTrackContractExpense();
        }
        else
        {
            die Common::Exception->new("ERROR - Unexpected state !!!");
        }
        if (! $outLine)
        {
            # It could be that there are no track or album contracts.  In which case we just want
            # to return the current line with the artist contract data.
            # However, we might have already output a line, and this just means that we didn't have
            # any track contracts.  In which case we want to go around again and get another artist contract.
            #
            # So we need to know whether the 'current line' has already been sent out.
            #
            my $outArtistContractID = $self->{_currentLine}->[ $self->{_arrayIndex}{'rs-contract-id'} ];
            last unless $outArtistContractID;
            if ($outArtistContractID != $self->{_lastOutArtistContractID})
            {
                $outLine = $self->{_currentLine};
            }
        }
    } while (! $outLine);


    # Remember the id of the line we're returning.
    # We use this to detect when we've sent a line out already.
    #
    $self->{_lastOutArtistContractID} = $outLine->[ $self->{_arrayIndex}{'rs-contract-id'} ];

    return $outLine;
}

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


    # Call the inherited method to retrieve the artist contract data.
    #
    $self->{_currentLine} = $self->SUPER::_nextReportLineData();
    return undef unless $self->{_currentLine};


    $self->{_state} = kFetchAlbumContract;
    return $self->_handleStateFetchAlbumContract();
}


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



    # Fetch the collection of album contracts if we haven't done that yet.
    #
    if (! $self->{_albumContracts})
    {
        my $contractIDIndex = $self->{_arrayIndex}{'rs-contract-id'};
        assert($contractIDIndex);

        my $contractID = $self->{_currentLine}->[$contractIDIndex];
        assert($contractID);

        $self->{_albumContracts} = RPS::DB::Item::AlbumContract->GetByContractID($contractID);

        #  Might not get a collection, which means we move on to the next state.
        #
        if (! $self->{_albumContracts} || $self->{_albumContracts}->size() == 0)
        {
            $self->{_albumContracts} = undef;
            $self->{_state} = kFetchTrackContract;
            return $self->_handleStateFetchTrackContract();
        }
    }

    

    
    my $albumContract = $self->{_albumContracts}->next();
    if (! $albumContract)
    {
        # No more album contracts, so move to the next state.
        #
        $self->{_albumContracts} = undef;
        $self->{_state} = kFetchTrackContract;
        return $self->_handleStateFetchTrackContract();
    }


    # !!! Do more stuff
    #
    $self->_fillContractColumns($albumContract);

    my $albumData = $self->_getAlbumData($albumContract->album_id());
    $self->_fillAlbumColumns($albumData);


    # If we're also supposed to be exporting Expenses, we'll need another potential state to manage here.
    #
#    if ($self->_withExpenses())
#    {
#        $self->{_currentAlbumContractID} = $albumContract->album_contract_id();
#        $self->{_state} = kFetchAlbumContractExpense;
#        return $self->_handleStateFetchAlbumContractExpense();
#    }

    return $self->{_currentLine};
}


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


    $self->_clearExpenseColumns();

    # Fetch the collection of album contracts if we haven't done that yet.
    #
    if (! $self->{_albumContractExpenses})
    {
        my $contractIDIndex = $self->{_arrayIndex}{'rs-contract-id'};
        assert($contractIDIndex);

        my $contractID = $self->{_currentLine}->[$contractIDIndex];
        assert($contractID);

        # If we leave 'processFlag' undefined, we'll get both processed and unprocessed.
        # We know at least one of these following optional data parameters is true (or we wouldn't be in this method).
        #
        my $processedFlag;
        if (! $self->parameters()->{OptionalData}->ShowProcessedExpenses())
        {
            $processedFlag = 0;
        }
        elsif (! $self->parameters()->{OptionalData}->ShowUnprocessedExpenses())
        {
            $processedFlag = 1;
        }

        $self->{_albumContractExpenses} = RPS::DB::Item::Expense->GetByParent($self->{_currentAlbumContractID}, RPS::DB::Item::Expense::kParentAlbumContract, $processedFlag);


        # If there are no album contract expenses, then I need to go ahead and output the current line, which contains
        # the album contract data.  Then move on to possibly get another album contract.
        #
        if (! $self->{_albumContractExpenses} || $self->{_albumContractExpenses}->size() == 0)
        {
            $self->{_albumContractExpenses} = undef;
            $self->{_state} = kFetchAlbumContract;
            return $self->{_currentLine};
        }
    }

    
    
    my $albumContractExpense = $self->{_albumContractExpenses}->next();
    if (! $albumContractExpense)
    {
        # No more album contracts, so move to the next state.
        #
        $self->{_albumContractExpenses} = undef;
        $self->{_state} = kFetchAlbumContract;
        return $self->_handleStateFetchAlbumContract();
    }


    # !!! Do more stuff
    #
    $self->_fillExpenseColumns($albumContractExpense);

    return $self->{_currentLine};
}


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

    # We might have had some album contract expenses in the last line.
    # Clear out the expense columns (because we might not have any track contract expenses).
    #
    $self->_clearExpenseColumns();

    # Fetch the collection of track contracts if we haven't done that yet.
    #
    if (! $self->{_trackContractExpenses})
    {
        my $contractIDIndex = $self->{_arrayIndex}{'rs-contract-id'};
        assert($contractIDIndex);

        my $contractID = $self->{_currentLine}->[$contractIDIndex];
        assert($contractID);

        # If we leave 'processFlag' undefined, we'll get both processed and unprocessed.
        # We know at least one of these following optional data parameters is true (or we wouldn't be in this method).
        #
        my $processedFlag;
        if (! $self->parameters()->{OptionalData}->ShowProcessedExpenses())
        {
            $processedFlag = 0;
        }
        elsif (! $self->parameters()->{OptionalData}->ShowUnprocessedExpenses())
        {
            $processedFlag = 1;
        }

        $self->{_trackContractExpenses} = RPS::DB::Item::Expense->GetByParent($self->{_currentTrackContractID}, RPS::DB::Item::Expense::kParentTrackContract, $processedFlag);


        # We need to output the current line.
        # And set the state back to fetch another track contract.
        #
        if (! $self->{_trackContractExpenses} || 0 == $self->{_trackContractExpenses}->size())
        {
            $self->{_trackContractExpenses} = undef;
            $self->{_state} = kFetchTrackContract;
            return $self->{_currentLine};
        }
    }

    
    my $trackContractExpense = $self->{_trackContractExpenses}->next();
    if (! $trackContractExpense)
    {
        # No more track contract expenses, so move to the next track contract (if any).
        #
        $self->{_trackContractExpenses} = undef;
        $self->{_state} = kFetchTrackContract;
        return $self->_handleStateFetchTrackContract();
    }


    # !!! Do more stuff
    #
    $self->_fillExpenseColumns($trackContractExpense);

    return $self->{_currentLine};
}

sub _fillExpenseColumns
{
    my ($self, $expense) = @_;

    my $deductionType;
    if ($expense->pre_process())
    {
        $deductionType = 'Net Revenue Deduction';
    }
    else
    {
        if ($expense->parent_type() == RPS::DB::Item::Expense::kParentAlbumContract)
        {
            $deductionType = 'Album Expense';
        }
        else
        {
            $deductionType = 'Track Expense';
        }
    }
    $self->{_currentLine}->[ $self->{_arrayIndex}{'deduction-type'} ] = $deductionType;
    
    my $expenseType = $self->_getExpenseTypeData($expense->expense_type_id());
    my $expenseName = $self->_getExpenseNameData($expenseType->expense_name_id());
    $self->{_currentLine}->[ $self->{_arrayIndex}{'expense-type'} ] = $expenseName->name();
    
    $self->{_currentLine}->[ $self->{_arrayIndex}{'percent'} ] = $expense->percent();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'amount'} ] = sprintf("%0.02f", $expense->amount());
    $self->{_currentLine}->[ $self->{_arrayIndex}{'effective-amount'} ] = sprintf("%0.02f", Common::RSMath::round($expense->amount() * ($expense->percent() / 100), 2));
    $self->{_currentLine}->[ $self->{_arrayIndex}{'memo'} ] = $expense->memo();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'processed'} ] = $expense->processed() ? 'yes' : 'no';
    
}


# For most state transitions we overwrite all the columns from the last state.
# For example, when we move from album contracts to track contracts, we can rely on
# the 'album-title', 'catalog-no', and those sorts of columns being completely overwritten.
# However, we can't say the same for the _expense_ columns.   We may have album expenses, but
# no track expenses.  Therefore we need to have a mechanism to clear out these expense columns, so
# we don't have redundant data going out.
# 
sub _clearExpenseColumns
{
    my ($self, $expense) = @_;

    $self->{_currentLine}->[ $self->{_arrayIndex}{'deduction-type'} ] = '';
    $self->{_currentLine}->[ $self->{_arrayIndex}{'expense-type'} ] = '';
    $self->{_currentLine}->[ $self->{_arrayIndex}{'percent'} ] = '';
    $self->{_currentLine}->[ $self->{_arrayIndex}{'amount'} ] = '';
    $self->{_currentLine}->[ $self->{_arrayIndex}{'effective-amount'} ] = '';
    $self->{_currentLine}->[ $self->{_arrayIndex}{'memo'} ] = '';
    $self->{_currentLine}->[ $self->{_arrayIndex}{'processed'} ] = '';
    
}


sub _fillContractColumns
{
    my ($self, $contract) = @_;
    $self->{_currentLine}->[ $self->{_arrayIndex}{'contract-status'} ] = _contractStatus($contract->status());
    $self->{_currentLine}->[ $self->{_arrayIndex}{'cross-collateralize'} ] = $contract->cross_collateralized() ? 'yes' : 'no';
}

sub _fillAlbumColumns
{
    my ($self, $albumData) = @_;
    $self->{_currentLine}->[ $self->{_arrayIndex}{'rs-album-id'} ] = $albumData->album_id();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'album-title'} ] = $albumData->title();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'catalog-no'} ] = $albumData->catalog_number();

    my $labelData = $self->_getLabelData($albumData->label_id());
    $self->{_currentLine}->[ $self->{_arrayIndex}{'label-name'} ] = $labelData->label_name() if $labelData;

    my $artistData = $self->_getArtistData($albumData->artist_id());
    $self->{_currentLine}->[ $self->{_arrayIndex}{'album-artist'} ] = $artistData->name() if $artistData;
}

sub _fillTrackColumns
{
    my ($self, $trackData) = @_;

    $self->{_currentLine}->[ $self->{_arrayIndex}{'track-title'} ] = $trackData->title();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'rs-track-id'} ] = $trackData->track_id();

    # Get the timing data from the master table.
    #
    my $masterData = $self->_getMasterData($trackData->master_id());
    if ($masterData)
    {
        my $minutes = int($masterData->duration() / 60);
        my $seconds = int($masterData->duration() % 60);
        $self->{_currentLine}->[ $self->{_arrayIndex}{'track-minutes'} ] = $minutes;
        $self->{_currentLine}->[ $self->{_arrayIndex}{'track-seconds'} ] = $seconds;
        $self->{_currentLine}->[ $self->{_arrayIndex}{'track-isrc'} ] = $masterData->isrc();
    }

    my $artistData = $self->_getArtistData($trackData->artist_id());
    $self->{_currentLine}->[ $self->{_arrayIndex}{'track-artist'} ] = $artistData->name() if $artistData;
}

sub _contractStatus
{
    my ($status) = @_;
    return 'active' if 1 == $status;
    return 'inactive' if 2 == $status;
    return 'unknown';
}


# !!! Probably will cache at some point.
sub _getAlbumData
{
    my ($self, $albumID) = @_;

    my $album = RPS::DB::Item::Album->Lookup(album_id => $albumID);
    return $album;
}

sub _getTrackData
{
    my ($self, $trackID) = @_;

    my $track = RPS::DB::Item::Track->Lookup(track_id => $trackID);
    return $track;
}

sub _getLabelData
{
    my ($self, $id) = @_;

    my $data = RPS::DB::Item::Label->Lookup(label_id => $id);
    return $data;
}

sub _getArtistData
{
    my ($self, $id) = @_;

    my $data = RPS::DB::Item::Artist->Lookup(artist_id => $id);
    return $data;
}

sub _getMasterData
{
    my ($self, $id) = @_;

    my $data = RPS::DB::Item::Master->Lookup(master_id => $id);
    return $data;
}

sub _getExpenseTypeData
{
    my ($self, $id) = @_;

    my $data = RPS::DB::Item::ExpenseType->Lookup(expense_type_id => $id);
    return $data;
}

sub _getExpenseNameData
{
    my ($self, $id) = @_;

    my $data = RPS::DB::Item::ExpenseName->Lookup(expense_name_id => $id);
    return $data;
}

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

    # Fetch the collection of track contracts if we haven't done that yet.
    #
    if (! $self->{_trackContracts})
    {
        my $contractIDIndex = $self->{_arrayIndex}{'rs-contract-id'};
        assert($contractIDIndex);

        my $contractID = $self->{_currentLine}->[$contractIDIndex];
        assert($contractID);
        $self->{_trackContracts} = RPS::DB::Item::TrackContract->GetByContractID($contractID);


        # !!! Might not get a collection, which means we move on to the next state.
        #
        if (! $self->{_trackContracts} || 0 == $self->{_trackContracts}->size())
        {
            $self->{_trackContracts} = undef;
            # !!! Don't want to fall into an infinite loop...
            #
            # Back to the top.
            #
            $self->{_state} = kFetchArtistContract;
#            return $self->_handleStateFetchArtistContract();
            return undef;
        }
    }

    
    my $trackContract = $self->{_trackContracts}->next();
    if (! $trackContract)
    {
        # No more track contracts, so move to the next state.
        #
        $self->{_trackContracts} = undef;
        $self->{_state} = kFetchArtistContract;
#        return $self->_handleStateFetchArtistContract();
        return undef;
    }


    # !!! Do more stuff
    $self->_fillContractColumns($trackContract);
    $self->{_currentLine}->[ $self->{_arrayIndex}{'proration'} ] = $trackContract->prorate_track_count();


    my $trackData = $self->_getTrackData($trackContract->track_id());
    $self->_fillTrackColumns($trackData);


    my $albumData = $self->_getAlbumData($trackData->album_id());
    $self->_fillAlbumColumns($albumData);

    # If we're also supposed to be exporting Expenses, we'll need another potential state to manage here.
    #
#    if ($self->_withExpenses())
#    {
#        $self->{_currentTrackContractID} = $trackContract->track_contract_id();
#        $self->{_state} = kFetchTrackContractExpense;
#        return $self->_handleStateFetchTrackContractExpense();
#    }

    return $self->{_currentLine};
}



sub _buildArrayIndex
{
    my ($self) = @_;
    $self->{_arrayIndex} = {};

    my $columns = $self->_columns();
    for (my $i = 0; $i < scalar @$columns; $i++)
    {
        my $col = $columns->[$i];

        my $colName = $col->{header}{name};
        assert(! defined $self->{_arrayIndex}{$colName});
        $self->{_arrayIndex}{$colName} = $i;
    }
}

sub _ls1
{
    my ($self, %args) = @_;
    return $self->_ls(1, %args);
}
sub _ls2
{
    my ($self, %args) = @_;
    return $self->_ls(2, %args);
}
sub _ls3
{
    my ($self, %args) = @_;
    return $self->_ls(3, %args);
}
sub _ls4
{
    my ($self, %args) = @_;
    return $self->_ls(4, %args);
}
sub _ls5
{
    my ($self, %args) = @_;
    return $self->_ls(5, %args);
}
sub _ls6
{
    my ($self, %args) = @_;
    return $self->_ls(6, %args);
}
sub _ls7
{
    my ($self, %args) = @_;
    return $self->_ls(7, %args);
}
sub _ls8
{
    my ($self, %args) = @_;
    return $self->_ls(8, %args);
}


my $gLiquidationScheduleCache;
sub _ls
{
    my ($self, $period, %args) = @_;
    my $rowItem = $args{row};
    my $fieldName = $args{field};

    my $outString;
    my $id = $rowItem->$fieldName();

    if ($id)
    {
        # Just going to cache the entire freaking table.
        #
        if (! $gLiquidationScheduleCache)
        {
            $gLiquidationScheduleCache = {};

            my $items = RPS::DB::Item::ReserveLiquidation->GetAll();
            while (my $item = $items->next())
            {
                next unless RPS::DB::Item::ReserveLiquidation::kTypeArtistContract == $item->type();
                $gLiquidationScheduleCache->{ $item->id() }{ $item->period() } = $item->percent();
            }
        }

        $outString = $gLiquidationScheduleCache->{$id}{$period};
    }

    return $outString;
}




1;

package RPS::Report::Dynamic::Contract::Artist::Parameters;
use strict;
use warnings;

use lib '/app/tools/common/lib';
use lib '/app/tools/report/lib';
use lib '/app/tools/rps/lib';
use base 'RPS::Report::Dynamic::Contract::Parameters';


sub _OptionalData
{
    {
        'ReportType' => {
            type => Report::Dynamic::Parameters::kTypeRadioGroup,
            order => 1,
            validValues => ['WithTerms', 'AttachedAssets', 'WithIncomeSources'],
            displayValues => ['Terms', 'Attached Albums & Tracks', 'License Income Sources & Expense Types'],
            display => 'Select Additional Artist/Producer Information',
            value => 'WithTerms',  # This sets the _default_ value.
            filename => ['Terms', 'wAlbumsTracks', 'wIncomeSources ExpenseTypes'],
        },
#        'ShowProcessedExpenses' => {
#            group => 'Expenses',
#            order => 3,
#            type => Report::Dynamic::Parameters::kTypeBoolean,
#            display => "Processed Expenses",
#            filename => "wExpenses",
#        },
#        'ShowUnprocessedExpenses' => {
#            group => 'Expenses',
#            order => 4,
#            type => Report::Dynamic::Parameters::kTypeBoolean,
#            display => "Unprocessed Expenses",
#            filename => "wExpenses",
#        },
    }
}

#sub _Groups
#{
#    return {
#        'Expenses' => {
#            'Heading' => 'Show Expenses for Attached Albums & Tracks',
#            order => 2,
#        },
#    }
#}

1;
