#---------------------------------------------------------------
# ____                   _ _         ____  _
#|  _ \ ___  _   _  __ _| | |_ _   _/ ___|| |__   __ _ _ __ ___
#| |_) / _ \| | | |/ _` | | __| | | \___ \| '_ \ / _` | '__/ _ \
#|  _ < (_) | |_| | (_| | | |_| |_| |___) | | | | (_| | | |  __/
#|_| \_\___/ \__, |\__,_|_|\__|\__, |____/|_| |_|\__,_|_|  \___|
#            |___/             |___/
#
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
# $Id$
#---------------------------------------------------------------
package BookPub::Sale::Report::Reconciliation;
use strict;
use warnings;

use Data::Dumper;
use File::Path;

use lib '/app/tools/common/lib';
use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::Service;
use BookPub::DB::Item::Sale;
use BookPub::DB::Item::SalePriceException;
use BookPub::Config;
use BookPub::Tracker::Period;
use Common::Util;
use Common::Client;
use Common::Assert;

use base 'BookPub::Sale::Report';

# Notes:
# - seems to be more of a files based report.

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

    return 'BookPub::Sale::Report::Reconciliation::Row';
}

sub _seperator {
    my ($self) = @_;
    return "\t";
}

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

    if ( !$self->{_fileName} ) {
        my $client = Common::Client::Current();
        my $period = BookPub::Tracker::Period->new( periodID => $self->periodID() );

        my $periodRef = $period->Name();
        $periodRef =~ s/\s+/_/g;
        $periodRef =~ s/\W+//g;
        $periodRef ||= $period->PeriodID();
        $self->{_fileName} = $client->ClientNameClean() . "_Recon_Report_$periodRef\.txt";
    }
    return $self->{_fileName};
}

sub excelFileName {
    my ($self) = @_;
    return $self->periodID() . '_RoyaltyReport.xls';
}

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

    if ( !$self->{_excelFilePath} ) {
        my $baseDir = $self->_baseDir();
        $self->{_excelFilePath} = $baseDir . '/' . $self->excelFileName();
    }
    return $self->{_excelFilePath};
}

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

    # Make sure we have a directory to write the final file into.
    #
    my $baseDir = $self->_baseDir();
    if ( !-d $baseDir ) {
        File::Path::mkpath($baseDir) || die "ERROR - make_path failed for $baseDir: $!";
    }

    # Open a file handle to the destination file.
    # Note that this WILL overwrite the file if it's currently there...
    #
    my $dest_file_path = $self->filePath();

    open( WRITE, ">" . $dest_file_path );
    binmode( WRITE, ':utf8' );

    # If this report is the current (open) period, we want to look at both open and closed
    # files.  Otherwise, just the closed files.
    my @fileStatus = ( BookPub::DB::Item::File::STATUS_CLOSED() );
    if ( 0 == $self->periodID() ) {
        push @fileStatus, BookPub::DB::Item::File::STATUS_OPEN();
    }

    my $fileList = BookPub::DB::Item::File->GetByPeriodID( $self->periodID(), file_status => \@fileStatus, sort_by => 'service_name' );

    my $rowClass = $self->getRowClass();

    # Write out the header line.
    #
    my $fields = $rowClass->Fields();
    my $header = join( $self->_seperator, @$fields );
    print WRITE "$header\n";

    while ( my $file = $fileList->next() ) {

        # Instantiate the correct 'row' class.
        # !!! I think in this instance we can shove most of the intelligence into this Row class.
        #

        my $row = $rowClass->new( file => $file );

        my @values = ();
        foreach my $fieldName (@$fields) {
            my $value = $row->{$fieldName};
            $value = '' unless defined $value;
            $value =~ s/\s/ /g;
            push @values, $value;
        }

        # Actually output the row.
        # Note that we determine which character to use as a delimeter via a private method.
        #
        print WRITE join( $self->_seperator(), @values ) . "\n";
    }

    close WRITE;

    return 1;

}

sub createExcel {
}

###
1;    #
###

package BookPub::Sale::Report::Reconciliation::Row;
use strict;
use warnings;

use lib '/app/tools/common/lib';
use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::Service;
use BookPub::DB::Item::File;
use BookPub::DB::Item::Period;
use BookPub::Config;
use BookPub::Catalog::Imprint;
use BookPub::Matcher::Product;
use Common::Util;
use Common::Assert;
use Common::RSApp;

# Going to define these strings as constants, and use the constant internally.
# Since these are used as hash keys, and some are rather long, this will help
# prevent silly bugs related to misspellings...
#
use constant kFileID                                => 'File ID';
use constant kOriginalPeriod                        => 'Original Period';
use constant kServiceName                           => 'Service Name';
use constant kFileName                              => 'File Name';
use constant kBeginDate                             => 'Begin Date';
use constant kEndDate                               => 'End Date';
use constant kTotalRevenueReceived                  => 'Total Revenue Received';
use constant kPreviouslyProcessedRevenue            => 'Previously Processed Revenue';
use constant kProcessedRevenueThisPeriod            => 'Processed Revenue This Period';
use constant kProcessedExceptionRevenueThisPeriod   => 'Processed Permanent Exception Revenue This Period';
use constant kUnprocessedRevenueThisPeriod          => 'Unprocessed Revenue This Period';
use constant kTotalPaidUnitsReceived                => 'Total Paid Units Received';
use constant kPreviouslyProcessedPaidUnits          => 'Previously Processed Paid Units';
use constant kProcessedPaidUnitsThisPeriod          => 'Processed Paid Units This Period';
use constant kProcessedPaidExceptionUnitsThisPeriod => 'Processed Paid Permanent Exception Units This Period';
use constant kUnprocessedPaidUnitsThisPeriod        => 'Unprocessed Paid Units This Period';
use constant kTotalLinesReceived                    => 'Total Lines Received';
use constant kPreviouslyProcessedLines              => 'Previously Processed Lines';
use constant kProcessedLinesThisPeriod              => 'Processed Lines This Period';
use constant kProcessedExceptionLinesThisPeriod     => 'Processed Permanent Exception Lines This Period';
use constant kUnprocessedLinesThisPeriod            => 'Unprocessed Lines This Period';

sub new {
    my ( $class, %args ) = @_;

    my $self = bless {}, $class;
    return $self->_init(%args);
}

# This is a static class method! Invoke like so:
# BookPub::Sale::Report::Royalty::Row->Fields()
#
sub Fields {
    my ($class) = @_;

    my $appRamCache = Common::RSApp::GetRAMCache();

    if ( !$appRamCache->{$class}{Fields} ) {
        my @fields;

        # By using a second private method, we'll be able
        # to use inheritance - This provides a method that
        # subclasses can override or extend.
        #
        $class->_addFields( \@fields );

        $appRamCache->{$class}{Fields} = \@fields;
    }

    return $appRamCache->{$class}{Fields};
}

sub ServiceName {
    my ($serviceID) = @_;
    assert($serviceID);

    my $appRamCache = Common::RSApp::GetRAMCache();

    if ( !$appRamCache->{ServiceName} ) {

        # Fetch all the service names.
        #
        my $allServices = BookPub::DB::Item::Service->GetAll();
        while ( my $serviceItem = $allServices->next() ) {

            # I'll just stash the DB::Item::Service object.
            #
            $appRamCache->{ServiceName}{ $serviceItem->service_id } = $serviceItem->service_name;
        }
    }

    return $appRamCache->{ServiceName}{$serviceID};
}

sub _addFields {
    my ( $self, $fieldArray ) = @_;

    # What we're doing here is determining:
    # - The field order,
    # - The column names (i.e. the header labels)
    # - The accessor method name to provide that data.
    #
    push @$fieldArray, kFileID;
    push @$fieldArray, kOriginalPeriod;
    push @$fieldArray, kServiceName;
    push @$fieldArray, kFileName;
    push @$fieldArray, kBeginDate;
    push @$fieldArray, kEndDate;
    push @$fieldArray, kTotalRevenueReceived;
    push @$fieldArray, kPreviouslyProcessedRevenue;
    push @$fieldArray, kProcessedRevenueThisPeriod;
    push @$fieldArray, kProcessedExceptionRevenueThisPeriod;
    push @$fieldArray, kUnprocessedRevenueThisPeriod;
    push @$fieldArray, kTotalPaidUnitsReceived;
    push @$fieldArray, kPreviouslyProcessedPaidUnits;
    push @$fieldArray, kProcessedPaidUnitsThisPeriod;
    push @$fieldArray, kProcessedPaidExceptionUnitsThisPeriod;
    push @$fieldArray, kUnprocessedPaidUnitsThisPeriod;
    push @$fieldArray, kTotalLinesReceived;
    push @$fieldArray, kPreviouslyProcessedLines;
    push @$fieldArray, kProcessedLinesThisPeriod;
    push @$fieldArray, kProcessedExceptionLinesThisPeriod;
    push @$fieldArray, kUnprocessedLinesThisPeriod;
}

sub _init {
    my ( $self, %args ) = @_;
    my $file = $args{file};
    assert($file);

    my $priceExceptions = $args{priceExceptions};

    my $periodID = $file->period_id;

    $self->{ kServiceName() } = ServiceName( $file->service_id );
    $self->{ kFileID() }      = $file->file_id;

    # 'zero out' data fields to start.
    #
    $self->{ kProcessedRevenueThisPeriod() }            = 0;
    $self->{ kProcessedExceptionRevenueThisPeriod() }   = 0;
    $self->{ kUnprocessedRevenueThisPeriod() }          = 0;
    $self->{ kProcessedPaidUnitsThisPeriod() }          = 0;
    $self->{ kProcessedPaidExceptionUnitsThisPeriod() } = 0;
    $self->{ kUnprocessedPaidUnitsThisPeriod() }        = 0;
    $self->{ kProcessedLinesThisPeriod() }              = 0;
    $self->{ kProcessedExceptionLinesThisPeriod() }     = 0;
    $self->{ kUnprocessedLinesThisPeriod() }            = 0;
    $self->{ kPreviouslyProcessedRevenue() }            = 0;
    $self->{ kPreviouslyProcessedPaidUnits() }          = 0;
    $self->{ kPreviouslyProcessedLines() }              = 0;
    $self->{ kTotalRevenueReceived() }                  = 0;
    $self->{ kTotalPaidUnitsReceived() }                = 0;
    $self->{ kTotalLinesReceived() }                    = 0;

    # Let's work backwards from the current file towards the original file.
    #
    # Actually, we need to find the newest child in the file's blood line.
    # Then we can start working back from there.
    #
    # Let's stash this ID away before we lose it.
    my $originalFileID = $file->file_id;
    ($file) = $self->_findNewestChild($file);

    my $client                 = Common::Client::Current();
    my $includePriceExceptions = 0;
    if ( $client->isClientType(Common::Client::kSalePriceValidation) ) {
        $includePriceExceptions = 1;
    }

    while ($file) {

        # Fetch the revenue summary info.
        # This involves querying the sale table.
        #
        # !!! Ignoring 'skip_free' for now.
        #
        my $fileSummaryInfo = BookPub::DB::Item::Sale->GetRevenueSummaryByFileID(
            file_id                  => $file->file_id,
            include_price_exceptions => $includePriceExceptions
        );

        if ( $file->period_id == $periodID ) {

            # We want to make sure that we are only adding in stuff from the original file here.
            # Otherwise, we will double up on everything when a parent/child is in the same period.
            if ( $file->file_id == $originalFileID ) {
                $self->{ kFileName() } = $file->orig_file_name;
                my $hasChild = BookPub::DB::Item::File->ChildCount( $file->file_id );
                if ( $file->generation != 0 || $hasChild != 0 ) {
                    my $displayGeneration = $file->generation + 1;
                    $self->{ kFileName() } .= " (Sp" . $displayGeneration . ")";
                }

                # Current period stuff
                #
                $self->{ kProcessedRevenueThisPeriod() }            += $fileSummaryInfo->{matched}{revenue}   || 0;
                $self->{ kProcessedExceptionRevenueThisPeriod() }   += $fileSummaryInfo->{dontmatch}{revenue} || 0;
                $self->{ kUnprocessedRevenueThisPeriod() }          += $fileSummaryInfo->{unmatched}{revenue} || 0;
                $self->{ kProcessedPaidUnitsThisPeriod() }          += $fileSummaryInfo->{matched}{units}     || 0;
                $self->{ kProcessedPaidExceptionUnitsThisPeriod() } += $fileSummaryInfo->{dontmatch}{units}   || 0;
                $self->{ kUnprocessedPaidUnitsThisPeriod() }        += $fileSummaryInfo->{unmatched}{units}   || 0;
                $self->{ kProcessedLinesThisPeriod() }              += $fileSummaryInfo->{matched}{lines}     || 0;
                $self->{ kProcessedExceptionLinesThisPeriod() }     += $fileSummaryInfo->{dontmatch}{lines}   || 0;
                $self->{ kUnprocessedLinesThisPeriod() }            += $fileSummaryInfo->{unmatched}{lines}   || 0;
            }

            # Note that the current period is the only one where we add
            # the unproccessed revenue to the total received.
            #
            $self->{ kTotalRevenueReceived() } += $fileSummaryInfo->{unmatched}{revenue} || 0;
            $self->{ kTotalRevenueReceived() } += $fileSummaryInfo->{matched}{revenue}   || 0;
        } elsif ( $periodID == 0 || ( $file->period_id < $periodID && $file->period_id != 0 ) ) {

            # Previous period stuff
            #
            # Important to note that since these are 'previous' files (i.e. have been split), their
            # revenue and unit totals contain MATCHED revenue and units.  Whereas an open file
            # in the current period contains matched and unmatched (i.e. potential revenue and units).
            #
            $self->{ kPreviouslyProcessedRevenue() }   += $fileSummaryInfo->{matched}{revenue} || 0;
            $self->{ kPreviouslyProcessedPaidUnits() } += $fileSummaryInfo->{matched}{units}   || 0;
            $self->{ kPreviouslyProcessedLines() }     += $fileSummaryInfo->{matched}{lines}   || 0;

            $self->{ kTotalRevenueReceived() } += $fileSummaryInfo->{matched}{revenue} || 0;
        } else {

            # future period stuff (relative to closed periods)
            #
            $self->{ kUnprocessedRevenueThisPeriod() }   += $fileSummaryInfo->{unmatched}{revenue} || 0;
            $self->{ kUnprocessedPaidUnitsThisPeriod() } += $fileSummaryInfo->{unmatched}{units}   || 0;
            $self->{ kUnprocessedLinesThisPeriod() }     += $fileSummaryInfo->{unmatched}{lines}   || 0;
            $self->{ kUnprocessedRevenueThisPeriod() }   += $fileSummaryInfo->{matched}{revenue}   || 0;
            $self->{ kUnprocessedPaidUnitsThisPeriod() } += $fileSummaryInfo->{matched}{units}     || 0;
            $self->{ kUnprocessedLinesThisPeriod() }     += $fileSummaryInfo->{matched}{lines}     || 0;

            $self->{ kTotalRevenueReceived() } += $fileSummaryInfo->{unmatched}{revenue} || 0;
            $self->{ kTotalRevenueReceived() } += $fileSummaryInfo->{matched}{revenue}   || 0;
        }

        # All period stuff
        #
        $self->{ kTotalPaidUnitsReceived() } += $file->units;
        $self->{ kTotalLinesReceived() }     += $file->records;

        # We want to present the ultimate date range across all related files...
        #
        my ( $dateBegin, $dateEnd ) = BookPub::DB::Item::Sale->GetDateRangeByFileID( $file->file_id );
        $self->{ kBeginDate() } = $dateBegin if ( !$self->{kBeginDate} || $self->{kBeginDate} gt $dateBegin );
        $self->{ kEndDate() }   = $dateEnd   if ( !$self->{kEndDate}   || $self->{kEndDate} lt $dateEnd );

        # Original Period stuff.
        #
        if ( !$file->parent_file_id ) {
            my $period = BookPub::DB::Item::Period->Lookup( period_id => $file->period_id );
            if ($period) {
                $self->{ kOriginalPeriod() } = $period->name;
            }

            # Stop the loop!
            #
            $file = undef;
        } else {

            # Fetch the next file
            #
            $file = BookPub::DB::Item::File->Lookup( file_id => $file->parent_file_id );
        }
    }

    return $self;
}

sub _findNewestChild {
    my ( $self, $file ) = @_;
    assert($file);

    my $childFile = BookPub::DB::Item::File->Lookup( parent_file_id => $file->file_id );

    if ($childFile) {
        $self->_findNewestChild($childFile);
    } else {

        # No (more) children, return this one.
        return $file;
    }
}

1;
