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


# 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
#   - ABCKO MUSIC INC (P11300)
#   - ABKCO MUSIC INC PETE TOWNSHEND CATALOG (P20061)
my @skipPublishers = qw(P11300 P20061);


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


# report unmatched licenses
#
# reportErrors(\%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;

    # Process the file line by line
    #
    while (my $record = <$file>) 
    {
	    my @fields;
	    my $licenseType;
	    my $artistName;
	    my $albumTitle;
	    my $catalogNumber;
	    my $upc;
	    my $isrc;
	    my $songCode;
	    my $duration;
	    my $songTitle;
	    my $dateSent;
	    my $dateReceived;
	    my $topPublisherNumber;
	    my $topPublisherName;
	    my $publisherNumber;
	    my $publisherName;
	    my $publisherShare;
	    my $licenseNumber;
	    my $rateType;
    
        $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);


		# Note - clientAlbumProductID is coming from the 'albumID' field,
		# and clientTrackProductID is coming from the 'trackID' field.
		# I'm not positive that this mapping is correct, or even
		# meaningful, but I do believe that the original values _are_ just
		# client-specified meta-data... so, it may make sense.
		# In any event, we need _something_ to put in the
		# 'product_code' column in the RPS product table, so this is
		# as good as anything...
		#
		(undef,$licenseType,undef,undef,$artistName,$albumTitle,$catalogNumber,$upc,$isrc,undef,undef,$duration,$songTitle,undef,$songCode,$dateSent,$dateReceived,undef,$topPublisherNumber,$topPublisherName,$publisherNumber,$publisherName,$publisherShare,undef,$licenseNumber,undef,$rateType,undef,undef) = undefspaces(@fields);

		# SKIP these publishers:
		#
		if(grep(/^$publisherNumber$/, @skipPublishers) || grep(/^$topPublisherNumber$/, @skipPublishers))
		{
			report("!Warning (#".$recordCount."): Skipping publisher=$publisherName ($publisherNumber)", kDebug);
			$skipCount++;
			next;
		}

		$isrc = normalize_isrc($isrc);

		# trim leading zeros
		$upc = _myNormalizeUPC($upc);
		my $altUPC = _myNormalizeUPC($catalogNumber);
		$publisherShare =~ s/%//;

		# we aren't saving licenses with share of 0.00%.
		#
		if(abs($publisherShare) && ($altUPC || $upc || $isrc) && $songCode && $publisherNumber)
		{
				my $newLicense = {};
				$newLicense->{trackLicenseID}		= undef;
				$newLicense->{lineNum}				= $recordCount;
				$newLicense->{songCode}				= $songCode;
				$newLicense->{licenseType}			= $licenseType;
				$newLicense->{artistName}			= $artistName;
				$newLicense->{albumTitle}			= $albumTitle;
				$newLicense->{catalogNumber}		= $catalogNumber;
				$newLicense->{altUPC}				= $altUPC;
				$newLicense->{upc}					= $upc;
				$newLicense->{isrc}					= $isrc;
				$newLicense->{duration}				= $duration;
				$newLicense->{songTitle}			= $songTitle;
				$newLicense->{dateSent}				= _convertDate($dateSent);
				$newLicense->{dateReceived}			= _convertDate($dateReceived);
				$newLicense->{topPublisherNumber}	= $topPublisherNumber;
                $newLicense->{publisherNumber}		= $publisherNumber;
                $newLicense->{publisherShare}		= $publisherShare;
                $newLicense->{licenseNumber}		= $licenseNumber;
                $newLicense->{rateType}				= $rateType;

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

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

		# regular publisher
		#
		if($publisherNumber && $publisherName)
		{
			if(!exists($updates->{publishers}->{$publisherNumber}))
			{
				# $updates->{publishers}->{$publisherNumber}->{publisherID} = "";
				# $updates->{publishers}->{$publisherNumber}->{isAdmin} = "";
				# $updates->{publishers}->{$publisherNumber}->{adminNumber} = "";
				$updates->{publishers}->{$publisherNumber}->{publisher_name} = $publisherName;
			}
		}
		else
		{
			report("!Warning (#".$recordCount."): Incomplete publisher", kDebug);
		}

		# top (admin) publisher
		#
		if($topPublisherNumber && $topPublisherName)
		{
			if(!exists($updates->{publishers}->{$topPublisherNumber}))
			{
				$updates->{publishers}->{$topPublisherNumber}->{publisher_name} = $topPublisherName;
			}

			if($publisherName && ($publisherName ne $topPublisherName))
			{
				# mark this publisher as an admin
				#
				$updates->{publishers}->{$topPublisherNumber}->{isAdmin} = 1;

				# set the regular publisher's admin as this publisher
				#
				$updates->{publishers}->{$publisherNumber}->{adminNumber} = $topPublisherNumber;
			}
		}
		else
		{
			report("!Warning (#".$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 = 1;

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

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

		# have we already created/matched this publisher?
		#
		if(! $publisher->{publisherID})
		{
			my $publisherID;
			$publisherID = lookupPublisher($publisher->{publisher_name});
			if($publisherID)
			{
				$publisher->{publisherID} = $publisherID;
				$pubMatchCount++;
				next;
			}

			my %pubParams =
			(
				publisher_name => $publisher->{publisher_name},
				is_agency => 0,
				is_admin => 0,
				agent_id => $hfaID,
			);

			if($publisher->{adminNumber})
			{
				# does this publisher have an admin?
				#
				$pubParams{admin_id} = $publishers->{$publisher->{adminNumber}}->{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++;
		}
	} # 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 $matchCount = 0;
    $count = 0;
    foreach my $license (@$licenses)
    {
        $count++;
        if (0 == $count % kProgressQuanta)
        {
            report("  license $count ...");
        }

		# did we already create this license?
		#

		if(!$license->{trackLicenseID})
		{
			#
			# We need to identify this track before we can assign
			# a license to it.
			#
			my $trackID;
			my $trackKey = join('-', $license->{albumTitle},
								$license->{upc},
								$license->{songCode},
								$license->{songTitle},
								$license->{isrc},
								$license->{duration},
								);

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

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

				my %licenseParams = 
				(
					track_id => $trackID,
					date_sent => $license->{dateSent},
					date_received => $license->{dateReceived},
					date_issued => $license->{dateReceived},
					issuer_license_id => $license->{licenseNumber},
					type => 1,
					rate_basis => 1,
					percentage_of_sales => 100,
				);

				my $productType;
				if($license->{licenseType} eq 'DIGITAL')
				{
					$licenseParams{product_type_id} = 3;
				}

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

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

				my $rateType;
				if($license->{rateType} =~ /min(imum)?/i)
				{
					$licenseParams{rate_type} = 2;
				}
				elsif($license->{rateType} =~ /stat(utory)?/i)
				{
					$licenseParams{rate_type} = 1;
				}

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

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

					my $dbItem = RPS::DB::Item::TrackLicense->Create(%licenseParams);
					$dbItem->save();
					$license->{trackLicenseID} = $dbItem->track_license_id;
					$matchCount++;
				}
				else
				{
					report("!Warning (#".$license->{lineNum}."): License already created: song = ".$license->{songTitle});
				}
			}
			else
			{
				# ok, we want to output the whole line here
				my @values;
				foreach my $key (sort keys %$license)
				{
					push @values, $license->{$key};
				}

				# report(join('\t', @values));
				report("!Warning (#".$license->{lineNum}."): Song not found: song = ".$license->{songTitle});
			}
		}
		else
		{
			report("!Warning (#".$license->{lineNum}."): I guess we already processed this license?");
		}

	} # END - licenses loop

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

sub _deriveTrackIDFromLicense
{
	my $license = shift;

	my $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})
	{
		$trackID = _getTrackIDFromCatalogNumber($license);
	}

	if(!$trackID && $license->{upc} || $license->{altUPC})
	{
		$trackID = _getTrackIDFromUPC($license);
	}

	# other lookups are not reliable, so we won't even bother
	#
	return $trackID;

	# let's try the isrc
	#
#	if($license->{isrc})
#	{
#		my $masterColl = RPS::DB::Item::Master->GetByISRC($license->{isrc});
#		if($masterColl->size == 1)
#		{
#			my $masterItem = $masterColl->next();
#			my $trackColl = RPS::DB::Item::Track::GetTracksByMasterID($masterItem->master_id);
#
#			if($trackColl->hasNext())
#			{
#				if($trackColl->size == 1)
#				{
#					my $trackItem = $trackColl->next();
#					$trackID = $trackItem->track_id;
#					report("TrackID found from isrc = ".$license->{isrc}, kDebug);
#				}
#				else
#				{
#					report("!Warning: more than one track for master_id = ".$masterItem->master_id, kDebug);
#				}
#			}
#			else
#			{
#				report("!Warning: no tracks found for master_id = ".$masterItem->master_id, kDebug);
#			}
#		}
#		else
#		{
#			report("!Warning: more than one master for isrc = ".$license->{isrc}, kDebug);
#		}
#	}
#
#	if(!$trackID && $license->{upc})
#	{
#		$trackID = _getTrackIDFromUPC($license->{upc}, $license);
#	}
#
#	if(!$trackID && $license->{altUPC} && $license->{altUPC} ne $license->{upc})
#	{
#		$trackID = _getTrackIDFromUPC($license->{altUPC}, $license);
#	}

}


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 reportErrors
{
    my ($updates) = @_;

    report("Reporting errors...");

    my $count = 0;
    my $licenses = $updates->{licenses};

	my $header;
	my @fields;
	foreach my $license (@$licenses)
	{
		next if($license->{trackLicenseID} > 0);

		if(!$header)
		{
			@fields = sort keys %$license;
			$header = join('\t', @fields);
			print $header . "\n";
		}

		my @values;
		foreach my $key (@fields)
		{
			push @values, $license->{$key};
		}
		print join('\t', @values) . "\n";
	}
}

sub _normalizeCatalogNumber
{
	my $catalogNumber = shift;

	$catalogNumber =~ s/\D//g;
	if(length($catalogNumber) >= 10)
	{
		$catalogNumber = substr($catalogNumber, 5, 10);
		if(length($catalogNumber) == 5)
		{
			$catalogNumber .= "-2";
		}
		else
		{
			$catalogNumber =~ s/2$/-2/;
		}
		return $catalogNumber;
	}
	else
	{
		return undef;
	}
}

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

	my $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});
	if($catalogNumber)
	{
		report("Looking up CatalogNumber = $catalogNumber", kDebug);
		my $coll = RPS::DB::Item::Album->GetByCatalogNumber($catalogNumber);

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

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

	return $trackID;
}


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

	my $trackID;
	my $upc = $license->{upc} || $license->{altUPC};

	if($upc)
	{
		report("Looking up UPC = $upc", kDebug);
		my $productColl = RPS::DB::Item::Product->GetByUPC($upc);
		if($productColl->hasNext())
		{
			my $prodItem = $productColl->next();
			my $matchedTracks = MatchTracksFromAlbumID($prodItem->album_id, $license);
			$trackID = FindTrackFromMatchedTracks($matchedTracks, $license);
		}
		else
		{
			report("!Warning: UPC not found", kDebug);
		}
	}

	return $trackID;
}

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

	my $trackID;

	if(@$matchedTracks == 1)
	{
		$trackID = $matchedTracks->[0]->track_id;
	}
	elsif(@$matchedTracks > 1)
	{
		report("Checking duration...", kDebug);
		foreach my $track (@$matchedTracks)
		{
			# let's compare duration
			#
			my $masterItem = RPS::DB::Item::Master->Lookup(master_id => $track->master_id);
			my $duration = RPS::XMLObject::_MMSSToSeconds($license->{duration});

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

		if(!$trackID)
		{
			report("!Warning (#".$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;

			# match a short title
			if (length($prodTrack) < 3)
			{
				if ($licenseTrack eq $prodTrack || $licenseTrackClean eq $prodTrackClean)
				{
					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
				   )
			{
				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);
}

