#!/usr/bin/perl

use strict;
use Getopt::Std;
use IO::File;
use Date::Calc;
use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::Util qw(decodeDateMysql trimspaces clean normalize_upc normalize_isrc);
use Common::RSDB;
use Common::RSApp;
use Common::Assert;
use Common::Consts qw(%COUNTRY_CODE);
use Common::RSMath;

use lib '/app/tools/rps/lib';
use RPS::XMLObject;
use RPS::DB::Item::Track;
use RPS::DB::Item::Album;
use RPS::DB::Item::Product;
use RPS::DB::Item::Master;
use RPS::DB::Item::Publisher;
use RPS::DB::Item::TrackLicense;
use RPS::DB::Item::StatRate;
use RPS::DB::Item::MechanicalCarryover;

# reporting flags and variable
#
use constant kQuiet  => 0;
use constant kNormal => 1;
use constant kDebug  => 3;
my $gReportLevel = kNormal;

use constant kProgressQuanta => 200;

# if two tracks on the same album have the same title,
# duration is the only distinguishing property. In this
# case the import data might be slightly different from the
# database data so we'll allow a +/- tolerance in matching.
use constant kDurationTolerance => 5;

# we're skipping some publishers (DIGITAL ONLY)
#   - ABKCO MUSIC INC (P11300)
#   - ABKCO MUSIC INC PETE TOWNSHEND CATALOG (P20061)
my @skipPublishers = qw();

# Parse the command-line.
#
my ( $clientID, $inputFile ) = parseCommandLine();

$| = 1;

# Instantiate the application singleton object.
#
my $appSingleton = Common::RSApp->new( clientID => $clientID );

# Scan the input file, and build the data structure of updates.
#
my %updates;
scanInputFileForUpdates( $inputFile, \%updates );

# Commit any necessary updates.
#
processUpdates( \%updates );

#
# --- Subroutines
#

sub parseCommandLine {
    my %opt;
    getopts( 'c:f:p', \%opt );

    my $inputFile = $opt{f};
    my $clientID  = $opt{c};

    unless ( $clientID =~ /^\d+$/ && $clientID > 0 ) {
        die usage("You must specify a valid client ID");
    }

    unless ($inputFile) {
        die usage("You must specify a catalog file to import");
    }

    unless ( -e $inputFile ) {
        die usage("The specified catalog file does not exist");
    }

    return ( $clientID, $inputFile );
}

sub usage {
    my $errstr = shift;
    my $text = ($errstr) ? "ERROR: $errstr\n" : '';
    $text .= "Usage: $0 -c <clientID> -f <inputFile>\n";
    return $text;
}

sub scanInputFileForUpdates {
    my ( $inputFilePath, $updates ) = @_;
    assert($inputFilePath);
    assert($updates);

    # Open the input file for reading.
    #
    my $inputFile = IO::File->new($inputFile) || die "Can't open input file '$inputFile': $!\n";

    readTabInputFile( $inputFile, $updates );

    # Done with the input file, close it.
    #
    $inputFile->close();
}

sub readTabInputFile {
    my ( $file, $updates ) = @_;
    assert($file);
    assert($updates);

    report("\nReading input file...");

    my $recordCount = 0;
    my $skipCount   = 0;

    # skip first line (headers)
    <$file>;

    # Process the file line by line
    #
    while ( my $record = <$file> ) {
        my @fields;
        my $accountNumber;
        my $licenseType;
        my $albumTitle;
        my $catalogNumber;
        my $songTitle;
        my $dateSent;
        my $dateSigned;
        my $topPublisherName;
        my $publisherName;
        my $publisherShare;
        my $rate;
        my $address1;
        my $address2;
        my $city;
        my $state;
        my $province;
        my $zip;
        my $country;
        my $taxID;
        my $actualShare;
        my $estimatedShare;
        my $negUnits;

        $recordCount++;

        if ( 0 == $recordCount % kProgressQuanta ) {
            report("  record $recordCount ... ");
        }

        # Split the line up into fields.
        #
        chomp $record;
        @fields = split( "\t", $record );
        @fields = trimquotes(@fields);
        @fields = escapequotes(@fields);

        (
            $accountNumber, undef,           $catalogNumber,    $songTitle, $publisherName, undef,
            $rate,          undef,           $topPublisherName, $address1,  $address2,      $city,
            $state,         $zip,            $province,         $country,   undef,          $taxID,
            $actualShare,   $estimatedShare, $licenseType,      undef,      $negUnits,      $albumTitle
        ) = undefspaces(@fields);

        $publisherShare = $actualShare || $estimatedShare;
        $publisherShare =~ s/%//;

        $country =~ s/^\s+//;
        $country =~ s/\s+$//;

        # we aren't saving licenses with share of 0.00%.
        #
        if ( $rate =~ m/\d+/ ) {
            my $newLicense = {};
            $newLicense->{trackLicenseID}   = undef;
            $newLicense->{lineNum}          = $recordCount + 1;    # the original excel file started on row 2, adjust here
            $newLicense->{catalogNumber}    = $catalogNumber;
            $newLicense->{songTitle}        = $songTitle;
            $newLicense->{licenseType}      = $licenseType;
            $newLicense->{albumTitle}       = $albumTitle;
            $newLicense->{topPublisherName} = $topPublisherName;
            $newLicense->{publisherName}    = $publisherName;
            $newLicense->{publisherShare}   = $publisherShare;
            $newLicense->{rate}             = $rate;
            $newLicense->{negativeUnits}    = $negUnits;

            push @{ $updates->{licenses} }, $newLicense;

            # -jff- I forget why this line is here
            #
            # report(join('\t', sort keys %$newLicense)) if($recordCount == 2);
        } else {
            report( "!Warning (line " . $recordCount . "): Incomplete data for song=$songTitle, publisher=$publisherName" );
            $skipCount++;
        }

        # regular publisher
        #
        my %pub = (
            address => join( ' ', $address1, $address2 ),
            city    => $city,
            state => $state || $province,
            zip   => $zip,
            taxID => $taxID,
            acctNum => $accountNumber,
            country => $COUNTRY_CODE{ lc($country) } || $country,
        );

        if ($publisherName) {
            if ( !exists( $updates->{publishers}->{$publisherName} ) ) {
                $updates->{publishers}->{$publisherName}{name} = $publisherName;
            }
        } else {
            report( "!Warning (line " . $recordCount . "): Incomplete publisher", kDebug );
        }

        # top (admin) publisher
        #
        if ($topPublisherName) {
            if ( !exists( $updates->{publishers}->{$topPublisherName} ) ) {
                map { $updates->{publishers}->{$topPublisherName}->{$_} ||= $pub{$_} } keys %pub;
                $updates->{publishers}->{$topPublisherName}{name} = $topPublisherName;
            }

            if ( $publisherName && ( $publisherName ne $topPublisherName ) ) {

                # mark this publisher as an admin
                #
                $updates->{publishers}->{$topPublisherName}->{isAdmin} = 1;

                # set the regular publisher's admin as this publisher
                #
                $updates->{publishers}->{$publisherName}->{adminName} = $topPublisherName;
            }
        } else {
            map { $updates->{publishers}->{$publisherName}->{$_} ||= $pub{$_} } keys %pub;

            #report("!Warning (line ".$recordCount."): Incomplete top publisher", kDebug);
        }

    }
    report("  Records processed : $recordCount");
    report("  Records skipped   : $skipCount");
}

sub processUpdates {
    my ($updates) = @_;

    my $publishers = $updates->{publishers};

    # Create publisher entries
    #
    report("\nCreating Publishers...");
    my $count = 0;

    # we need to create an entry for Harry Fox first
    #
    # my $hfa = RPS::DB::Item::Publisher->Create(publisher_name => "HARRY FOX AGENCY",
    # is_agency => 1,
    # is_admin => 0);
    # $hfa->save();
    # my $hfaID = $hfa->publisher_id;

    # HFA has publisher_id=1, we don't need to create it again. -jff-
    #
    my $hfaID = 0;

    my $pubCreateCount = 0;
    my $pubMatchCount  = 0;
    foreach my $publisherName ( sort { $publishers->{$b}->{isAdmin} <=> $publishers->{$a}->{isAdmin} } keys %$publishers ) {
        $count++;
        if ( 0 == $count % kProgressQuanta ) {
            report("  publisher $count ...");
        }

        my $publisher = $publishers->{$publisherName};

        # have we already created/matched this publisher?
        #
        if ( !$publisher->{publisherID} ) {

            # Does this publisher already exist?
            #
            #my $coll = RPS::DB::Item::Publisher->Match($publisher->{publisher_name});
            my $coll = '';

            # ignore any existing (for this import)
            if ( 0 && defined $coll && $coll->hasNext() ) {
                my $dbItem = $coll->next();
                $publisher->{publisherID} = $dbItem->publisher_id();
                $pubMatchCount++;

                # is this a new admin?
                #
                if ( $publisher->{isAdmin} == 1 && !$dbItem->is_admin() ) {
                    $dbItem->is_admin(1);
                    $dbItem->save();
                    report("!Update (Pub $publisherName): publisher set to Admin");
                }

                next;
            }

            my %pubParams = (
                publisher_name    => $publisher->{name},
                street_address    => $publisher->{address},
                city              => $publisher->{city},
                state_province    => $publisher->{state},
                postal_code       => $publisher->{zip},
                tax_id            => $publisher->{taxID},
                country_code      => $publisher->{country},
                is_agency         => 0,
                is_admin          => 0,
                agent_id          => $hfaID,
                client_account_id => $publisher->{acctNum},
            );

            if ( $publisher->{adminName} ) {

                # does this publisher have an admin?
                #
                $pubParams{admin_id} = $publishers->{ $publisher->{adminName} }->{publisherID};
            } elsif ( $publisher->{isAdmin} == 1 ) {

                # is this an admin?
                #
                $pubParams{is_admin} = 1;
            }

            report( "Creating publisher: " . Dumper( \%pubParams ), kDebug );

            my $dbItem = RPS::DB::Item::Publisher->Create(%pubParams);
            $dbItem->save();
            $publisher->{publisherID} = $dbItem->publisher_id;
            $pubCreateCount++;
        } else {
            print "exists: $publisherName : $publisher->{publisherID}\n";
        }

    }    # END - publishers loop

    report("  Publishers created : $pubCreateCount");
    report("  Publishers matched : $pubMatchCount");

    # Create license entries
    #
    my $tracks      = {};
    my $licenses    = $updates->{licenses};
    my %seenLicense = ();

    report("\nCreating Licenses...");

    my %product;
    my $matchCount = 0;
    $count = 0;
    foreach my $license (@$licenses) {
        $count++;
        if ( 0 == $count % kProgressQuanta ) {
            report("  license $count ...");
        }

        # SKIP these publishers:
        #
        my $publisherNumber    = $license->{publisherNumber};
        my $topPublisherNumber = $license->{topPublisherNumber};
        if ( grep( /^$publisherNumber$/, @skipPublishers ) || grep( /^$topPublisherNumber$/, @skipPublishers ) ) {
            report( "!Warning (line " . $license->{lineNum} . "): Skipping publisher=$publisherNumber (admin: $topPublisherNumber)" );
            next;
        }

        # did we already create this license?
        #
        if ( !$license->{trackLicenseID} ) {
            #
            # We need to identify this track before we can assign
            # a license to it.
            #
            my ( $albumID, $trackID );
            my $trackKey = join( '|', $license->{albumTitle}, $license->{catalogNumber}, $license->{songTitle}, );

            if ( !exists( $tracks->{$trackKey} ) ) {
                ( $albumID, $trackID ) = _deriveAlbumTrackFromLicense($license);
            } else {
                $albumID = $tracks->{$trackKey}{albumID};
                $trackID = $tracks->{$trackKey}{trackID};
                report( "TrackID already found for trackKey = $trackKey", kDebug );
            }

            if ($trackID) {
                report( "Song matched!", kDebug );

                # store this for future reference
                #
                $tracks->{$trackKey}{albumID} = $albumID;
                $tracks->{$trackKey}{trackID} = $trackID;

                my %licenseParams = (
                    track_id      => $trackID,
                    date_sent     => $license->{dateSent},
                    date_received => $license->{licenseDate},
                    date_issued   => $license->{dateSigned},
                    type          => 1,
                    rate_basis    => 1,
                    free_goods    => 15,                        # Sanctuary physical license specific
                );

                # all the balances are negative
                #
                if ( $license->{advance_amount} < 0 ) {
                    $licenseParams{advance_amount} = $license->{advance_amount} * -1;
                }

                my $rateType = _deriveRateType( $license->{licenseType} );
                if ($rateType) {
                    $licenseParams{rate_type}       = $rateType;
                    $licenseParams{rate_percentage} = _deriveRatePercentage( $license->{licenseType} );
                } else {
                    report( "!Warning (line "
                          . $license->{lineNum}
                          . "): unknown rate basis ("
                          . $license->{rate}
                          . ") for song = "
                          . $license->{songTitle} );
                    next;
                }

                if ( $rateType == RPS::DB::Item::TrackLicense::kRateTypePenny() ) {

                    # just assign the penny rate
                    #
                    # $licenseParams{penny_rate} = $license->{rate};
                    $licenseParams{penny_rate} = Common::RSMath::round( $license->{rate}, 4 );
                    $licenseParams{rate_basis} = undef;
                }

                my ($productTypeID) = $license->{catalogNumber} =~ m/-(\d)$/;
                if ($productTypeID) {
                    $licenseParams{product_type_id} = $productTypeID;
                } else {
                    report( "!Warning (line "
                          . $license->{lineNum}
                          . "): unknown license type ("
                          . $license->{catalogNumber}
                          . ") for song = "
                          . $license->{songTitle} );
                    next;
                }

                my $share;
                if ( $license->{publisherShare} ) {
                    $licenseParams{share} = $license->{publisherShare};
                } else {
                    report( "!Warning (line " . $license->{lineNum} . "): missing share for song = " . $license->{songTitle} );
                    next;
                }

                my $publisherID;
                my $pubNum = $license->{publisherName};
                if ( $pubNum && $publishers->{$pubNum} && $publishers->{$pubNum}->{publisherID} ) {
                    $licenseParams{publisher_id} = $publishers->{$pubNum}->{publisherID};
                } else {
                    report( "!Warning (line " . $license->{lineNum} . "): missing publisher for song = " . $license->{songTitle} );
                    next;
                }

             # let's make sure we didn't already import this license
             #
             # my $licenseKey = join('-', $trackID, $licenseParams{publisher_id}, $licenseParams{share}, $licenseParams{issuer_license_id});
                my $licenseKey = join( '-', $trackID, $licenseParams{publisher_id}, $licenseParams{product_type_id} );
                if ( !$seenLicense{$licenseKey} ) {
                    $seenLicense{$licenseKey} = 1;

                    # check for duplicate in the system
                    #
                    my $coll = RPS::DB::Item::TrackLicense->GetByTrackPublisherProductType(
                        $trackID,
                        $licenseParams{publisher_id},
                        $licenseParams{product_type_id}
                    );
                    if ( defined $coll && $coll->size > 0 ) {
                        report( "!Warning (line "
                              . $license->{lineNum}
                              . "): License already exists: track_id=$trackID, song = "
                              . $license->{songTitle} );
                        next;
                    }

                    #report("Creating track_license with: ".Dumper(\%licenseParams), kDebug);

                    my $newLicense = RPS::DB::Item::TrackLicense->Create(%licenseParams);
                    $newLicense->save();
                    $license->{trackLicenseID} = $newLicense->track_license_id;
                    $matchCount++;

                    # store any associated negative units
                    #
                    if ( $license->{negativeUnits} < 0 ) {
                        unless ( $product{$trackID} ) {
                            unless ( $product{$trackID} = _derivePhysicalProduct($albumID) ) {
                                report("!Warning (line $license->{lineNum}: can't locate physical product for track id: $trackID");
                                next;
                            }
                        }
                        my $saleDate    = '2006-01-01';                         # (for this import)
                        my $releaseDate = $product{$trackID}->release_date();
                        $releaseDate = $saleDate unless $releaseDate;
                        my $issueStatRateID = _getStatRateID($releaseDate);
                        my $saleStatRateID  = _getStatRateID($saleDate);
                        my $co              = RPS::DB::Item::MechanicalCarryover->Create(
                            track_license_id   => $license->{trackLicenseID},
                            sale_stat_rate_id  => $saleStatRateID,
                            issue_stat_rate_id => $issueStatRateID,
                            product_id         => $product{$trackID}->product_id(),
                            units              => $license->{negativeUnits},
                        );
                        $co->save();

                    }
                } else {
                    report( "!Warning (line " . $license->{lineNum} . "): License already created: song = " . $license->{songTitle} );
                }
            } else {
                report( "!Warning (line " . $license->{lineNum} . "): Song not found: song = " . $license->{songTitle} );
            }
        } else {
            report( "!Warning (line " . $license->{lineNum} . "): I guess we already processed this license?" );
        }

    }    # END - licenses loop

    report("  Total licenses processed : $count");
    report("  Licenses created         : $matchCount");
}

sub _deriveRateType {
    my $type = shift;

    # full  = 1
    # min   = 2
    # penny = 3
    #
    my $rateType;
    if ( $type =~ /\d\d%|STATUTORY/i ) {
        $rateType = RPS::DB::Item::TrackLicense::kRateTypeFull;
    } else {
        $rateType = RPS::DB::Item::TrackLicense::kRateTypePenny;
    }

    return $rateType;
}

sub _deriveRatePercentage {
    my $type = shift;

    return $1 if ( $type =~ m/^(\d+)%/ and $1 < 100 );
    return 100;
}

sub _deriveRate {
    my $rateType = shift;

    my $rate;

    if ( $rateType =~ /(\d\d)%/ ) {
        $rate = $1;
    } else {
        $rate = 100;
    }

    return $rate;
}

sub _deriveProductType {
    my $config = shift;

    my $id;

    if ( $config =~ /(DIGITAL|DOWNLOAD)/i ) {

        # digital
        #
        $id = 3;
    } elsif ( $config =~ /COMPACT DISC/i ) {

        # physical - CD
        #
        $id = 2;
    }

    return $id;
}

sub _deriveAlbumTrackFromLicense {
    my $license = shift;

    my ( $albumID, $trackID );

    # The Sanctuary matching rule is this:
    # 1. We must be able to match the catalog number or UPC to a release in our catalog.
    #
    if ( $license->{catalogNumber} || $license->{upc} ) {
        ( $albumID, $trackID ) = _getAlbumTrackIDFromCatalogNumber($license);
    }

    return ( $albumID, $trackID );
}

sub _derivePhysicalProduct {
    my $albumID = shift;

    my $coll = RPS::DB::Item::Product->GetProductsByAlbumID( $albumID, RPS::DB::Item::Product::kProductTypeCD );

    return $coll->next() if ( $coll->hasNext() );
    return undef;
}

sub lookupPublisher {
    my ($name) = @_;

    my $publisherID;

    # Does this publisher already exist?
    #
    my $coll = RPS::DB::Item::Publisher->Match($name);
    if ( defined $coll && $coll->size == 1 ) {
        my $dbItem = $coll->next();
        $publisherID = $dbItem->publisher_id;
    }

    return $publisherID;
}

sub _normalizeCatalogNumber {
    my $catalogNumber = shift;

    #$catalogNumber =~ s/\D//g;
    if ( length($catalogNumber) >= 10 ) {
        if ( length($catalogNumber) == 10 ) {
            if ( $catalogNumber !~ /2$/ ) {
                $catalogNumber = $catalogNumber . "2";
            } elsif ( $catalogNumber !~ /^0/ ) {
                $catalogNumber = "0" . $catalogNumber;
            }
        }

        $catalogNumber = substr( $catalogNumber, 5, 10 );
        if ( length($catalogNumber) == 5 ) {
            $catalogNumber .= "-2";
        } else {
            $catalogNumber =~ s/2$/-2/;
        }
    } elsif ( length($catalogNumber) == 5 ) {
        $catalogNumber .= "-2";
    }

    return $catalogNumber;
}

sub _getAlbumTrackIDFromCatalogNumber {
    my ($license) = @_;

    my ( $albumID, $trackID );
    my $matchedTracks;

    # the order here is important, we always want to use the catalogNumber if we can.
    #
    my $catalogNumber = _normalizeCatalogNumber( $license->{catalogNumber} ) || _normalizeCatalogNumber( $license->{upc} );
    report( "Looking up CatalogNumber = $catalogNumber", kDebug );
    if ($catalogNumber) {
        my $coll = RPS::DB::Item::Album->GetByCatalogNumber($catalogNumber);

        if ( $coll->hasNext() ) {
            my $albumItem = $coll->next();

            $albumID       = $albumItem->album_id;
            $matchedTracks = MatchTracksFromAlbumID( $albumID, $license );
            $trackID       = FindTrackFromMatchedTracks( $matchedTracks, $license );
        } else {
            report( "!Warning: CatalogNumber not found", kDebug );
        }
    }

    return ( $albumID, $trackID );
}

sub FindTrackFromMatchedTracks {
    my ( $matchedTracks, $license ) = @_;

    my $trackID;

    if ( @$matchedTracks == 1 ) {
        $trackID = $matchedTracks->[0]->track_id;
        report( "!Notice (line " . $license->{lineNum} . "): MATCH found! trackID = " . $trackID, kDebug );
    } elsif ( @$matchedTracks > 1 && $license->{duration} =~ /\d?\d?:\d\d/ ) {
        report( "Checking duration...", kDebug );
        foreach my $track (@$matchedTracks) {

            # let's compare duration
            #
            my $licDuration = RPS::XMLObject::_MMSSToSeconds( $license->{duration} );
            my $masterItem = RPS::DB::Item::Master->Lookup( master_id => $track->master_id );

            if ( $licDuration == $masterItem->duration || abs( $licDuration - $masterItem->duration ) <= kDurationTolerance ) {
                $trackID = $track->track_id;
            }
        }

        if ( !$trackID ) {
            report( "!Warning (line " . $license->{lineNum} . "): duplicate song, no match on duration = " . $license->{songTitle},
                kDebug );
        }
    } else {
        report( "!Warning (line " . $license->{lineNum} . "): duplicate song, no match on duration = " . $license->{songTitle}, kDebug );
    }

    return $trackID;
}

sub MatchTracksFromAlbumID {
    my ( $albumID, $license ) = @_;

    my @matchedTracks = ();

    # normalize the song title we are trying to match
    #
    my $licenseTrack = lc( $license->{songTitle} );
    $licenseTrack =~ s/\s+//g;
    my $licenseTrackClean = clean($licenseTrack);

    my $trackColl = RPS::DB::Item::Track->GetTracksByAlbumID($albumID);
    while ( $trackColl->hasNext() ) {
        my $trackItem = $trackColl->next();

        if ( $trackItem->title ) {

            # normalize the song title from the database
            #
            my $prodTrack = lc( $trackItem->title );
            $prodTrack =~ s/\s+//g;
            my $prodTrackClean = clean($prodTrack);
            $prodTrackClean =~ s/_//g;

            report( "!Notice (line " . $license->{lineNum} . "): comparing ('" . $prodTrack . "' cmp '" . $licenseTrack . "')", kDebug );

            ##########################
            ########################## We are restricting matches to ONLY EXACT matches
            ##########################

            # match a short title
            #			if (length($prodTrack) < 3)
            #			{
            if ( $licenseTrack eq $prodTrack || $licenseTrackClean eq $prodTrackClean ) {
                report( "!Notice (line " . $license->{lineNum} . "): MATCH ('" . $prodTrack . "' cmp '" . $licenseTrack . "')", kDebug );
                push @matchedTracks, $trackItem;

                # $trackID = $trackItem->track_id;
            }

            #			}
            #			# does the dbTrack contain the title? or the other way around?
            #			# This works because we've already narrowed it down to the
            #			# correct album.
            #			elsif ($licenseTrack eq $prodTrack ||
            #				   index($licenseTrack, $prodTrack) > -1 ||
            #				   index($prodTrack, $licenseTrack) > -1 ||
            #				   $licenseTrackClean eq $prodTrackClean ||
            #				   index($licenseTrackClean, $prodTrackClean) > -1 ||
            #				   index($prodTrackClean, $licenseTrackClean) > -1
            #				   )
            #			{
            #				report("!Notice (line ".$license->{lineNum}."): MATCH ('".$prodTrack."' cmp '".$licenseTrack."')", kDebug);
            #				push @matchedTracks, $trackItem;
            #				# $trackID = $trackItem->track_id;
            #			}
        }
    }

    return \@matchedTracks;
}

sub escapequotes {
    my @string = @_;

    for (@string) {
        s/\"\"/"/g;
    }

    return wantarray ? @string : $string[0];
}

sub trimquotes {
    my @string = trimspaces(@_);

    for (@string) {
        if (m/^\".*\"$/) {
            s/^\"//;
            s/\"$//;
        }
    }

    return wantarray ? @string : $string[0];
}

sub undefspaces {
    my @string = trimspaces(@_);

    for (@string) {
        if ( $_ eq '' ) {
            $_ = undef;
        }
    }

    return wantarray ? @string : $string[0];
}

sub report {
    my ( $text, $level ) = @_;

    $level = kNormal unless $level;

    if ( $level <= $gReportLevel ) {
        print $text . "\n";
    }
}

sub _convertDate {
    my $date = shift;
    my $newValue;

    my ( $year, $month, $day );
    if (   ( ( $year, $month, $day ) = Date::Calc::Decode_Date_US($date) )
        || ( ( $year, $month, $day ) = Date::Calc::Decode_Date_EU($date) )
        || ( ( $year, $month, $day ) = decodeDateMysql($date) ) ) {
        $newValue = sprintf( "%4d-%02d-%02d", $year, $month, $day );
    }

    return $newValue;
}

sub _myNormalizeUPC {
    my $upc = shift;

    if ( length($upc) > 10 ) {
        $upc =~ s/^0+//;
    }

    if ( length($upc) == 10 ) {

        # assume it needs the "format" digit on the end (2)
        $upc = $upc . "2";
    }

    return normalize_upc($upc);
}

my $gStatRateTable;
my $gStatRateByDate;

sub _getStatRateTable {
    if ( !$gStatRateTable ) {
        $gStatRateTable = {};

        my $statRates = RPS::DB::Item::StatRate->GetAll();
        while ( my $statRate = $statRates->next() ) {
            $gStatRateTable->{ $statRate->stat_rate_id } = $statRate;
        }
    }

    return $gStatRateTable;
}

# Generally we are going to be asking for the same stat rate for the same # date over and over and over and over... so it makes sense to cache it.
sub _getStatRateID {
    my $endDate = shift;
    if ( !$gStatRateByDate->{$endDate} ) {
        my $table = _getStatRateTable();

        # get the stat rates, sorted by date
        my @sortedRateIDs = sort { $table->{$b}->date_effective <=> $table->{$a}->date_effective } keys %$table;
        foreach my $testRateID (@sortedRateIDs) {
            if ( $table->{$testRateID}->date_effective <= $endDate ) {
                $gStatRateByDate->{$endDate} = $table->{$testRateID}->stat_rate_id;
                last;
            }
        }
    }
    return $gStatRateByDate->{$endDate};
}

