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

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

use strict;
use warnings;

use IO::File;

# tmpnam is deprecated. Should be removed or updated to File::Temp in RSD-6171
#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('client-album-id') );
    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') );
    push( @$cols, $self->_getEmptyColumnHash('album-custom-1') );
    push( @$cols, $self->_getEmptyColumnHash('album-custom-2') );
    push( @$cols, $self->_getEmptyColumnHash('album-custom-3') );
    push( @$cols, $self->_getEmptyColumnHash('track-custom-1') );
    push( @$cols, $self->_getEmptyColumnHash('track-custom-2') );
    push( @$cols, $self->_getEmptyColumnHash('track-custom-3') );

}

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 produce 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}{'rs-term-id'} ]          = '';
        $outLine->[ $self->{_arrayIndex}{'priority'} ]            = 'default';
        $outLine->[ $self->{_arrayIndex}{'rate-type'} ]           = '% Net Revenue';
        $outLine->[ $self->{_arrayIndex}{'channel'} ]             = 'All';
        $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'} ] = '100.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();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'client-album-id'} ] = $albumData->client_album_id();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'album-custom-1'} ]  = $albumData->custom_1();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'album-custom-2'} ]  = $albumData->custom_2();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'album-custom-3'} ]  = $albumData->custom_3();


    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();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'track-custom-1'} ] = $trackData->custom_1();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'track-custom-2'} ] = $trackData->custom_2();
    $self->{_currentLine}->[ $self->{_arrayIndex}{'track-custom-3'} ] = $trackData->custom_3();

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