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

# This script will parse everything.
#
use strict;


ParseAll->start();



package ParseAll;


use lib '/app/tools/common/lib';
use lib '/app/tools/rps/lib';

use Common::Util;
use Common::Log;
use Common::Parser;
use Common::WriteXML;

use RPS::DB::Item::Label;
use RPS::DB::Item::Artist;
use RPS::DB::Item::Album;
use RPS::DB::Item::Product;
use RPS::DB::Item::Song;
use RPS::DB::Item::Master;
use RPS::DB::Item::Track;
use RPS::DB::Item::ProductTrack;
use RPS::DB::Item::Price;
use RPS::DB::Item::ProductPrice;
use RPS::DB::Item::PriceLevel;

use base 'Common::Script';

sub _options {{
  client_id => 
  {
      short        => 'c',
      required     => 1,
      description  => 'Limit to this client id',
      parameter    => 'i'
  },
  file => 
  {
      short        => 'f',
      required     => 1,
      description  => 'path to files',
      parameter    => 's'
  },
}}


sub _process 
{
    my $self = shift;

    binmode(STDOUT, ':utf8');

    my $path = $self->param('file');
    Log->info("parsing path: $path");


    # So, this script will iterate over all the EMI files and build their catalog.
    # It assumes a blank slate.
    #
    my @filesAndMethods = (
        ['RepertoireOwner.txt','_parseLabel'],
        ['Price_Codes.txt','_parsePrice'],
        ['ArtistCodes_v3.txt','_parseArtist'],
        ['SAP_Product.txt','_parseProduct'],
        ['SAP_Component.txt','_parseComponent'],
        ['SAP_Track_1.txt','_parseTrack'],
        ['SAP_Track_2.txt','_parseTrack'],
    );

    foreach my $fileAndMethod (@filesAndMethods)
    {
        my $file = $fileAndMethod->[0];
        my $method = $fileAndMethod->[1];
        Log->warn("parsing $file with $method");

        my $fullPath = $path . '/' . $file;

        my $iterator = Common::Parser->Parse( filename => $fullPath);
        die "ERROR - No iterator available" unless $iterator;
        $iterator->open($fullPath);

        my $headerRow;
        while (my $row = $iterator->next())
        {
            my $rArray = $row->getAllColumns();
            if (! $headerRow)
            {
                $headerRow = $rArray;

                Log->info("HEADER ROW:", $headerRow);

                next;
            }
        
            my $hash = $self->_rowToHash(row => $rArray, header => $headerRow);
        
            # Might be blank rows and stuff.
            #
            next unless $hash;
            eval {
            $self->$method($hash);
            };
            if ($@)
            {
                Log->error("Parse error: $@", $hash);
                die $@;
            }
        }

        # Make a snapshot, so we can recover after each step.
        #
#        `/usr/bin/mysqldump -udbuser -predStripe -hindy01 C_EMI > C_EMI.$file`;
    }
}

sub _rowToHash
{
    my ($self, %args) = @_;
    my $header = $args{header};
    my $row = $args{row};

    my %hash;
    for (my $i = 0; $i < scalar @$header; $i++)
    {
        # 'clean' the header tags.
        # emi likes to change case from file to file
        #
        my $hTag = Common::Util::clean($header->[$i]);

        if (defined $row->[$i])
        {
            $hash{$hTag} = $row->[$i];
        }
        else
        {
            $hash{$hTag} = 'NULL';
        }
    }

    return \%hash;
}


sub _parseLabel
{
    my ($self, $hash) = @_;

    my $clientID = $hash->{'company_code'};
    my $labelName = $hash->{'company_name'};


    # Clean up the crap a little bit.
    #
    $labelName = Common::Util::trimquotes($labelName);
    $labelName = Common::Util::trimspaces($labelName);


    my $labelNameClean = Common::Util::clean($labelName);


    # Lookup first.
    # Don't create if we don't need to create.
    #
    # !!! client_label_id is currently NOT a key...
    #
    my $old = RPS::DB::Item::Label->Lookup(client_label_id => $clientID);
    if (! $old)
    {
        my $new = RPS::DB::Item::Label->Create(
         label_name => $labelName,
         label_name_clean => $labelNameClean,
         client_label_id => $clientID,
        );
        $new->save();
        Log->info("Created new label: EMI_ID:$clientID  RPS_ID:".$new->label_id);
    }
}


sub _parseArtist
{
    my ($self, $hash) = @_;

    my $clientID = $hash->{globalartistcode};
    my $artistName = $hash->{artistname};

    # Clean up the crap a little bit.
    #
    $artistName = Common::Util::trimquotes($artistName);
    $artistName = Common::Util::trimspaces($artistName);


    my $artistNameClean = Common::Util::clean($artistName);


    # Sanity check - Do we have this artist already?
    #
    my $old = RPS::DB::Item::Artist->Lookup(client_artist_id => $clientID);
    if ($old)
    {
        Log->info("DUPLICATE ARTIST : GlobalArtistCode $clientID");
        return;
    }

    my $new = RPS::DB::Item::Artist->Create(
        name => $artistName,
        name_clean => $artistNameClean,
        client_artist_id => $clientID,
    );
    $new->save();
    Log->info("Created new artist: EMI_ID:$clientID  RPS_ID:".$new->artist_id);
}


sub _parseProduct
{
    my ($self, $hash) = @_;

    # There are a couple of conditions which will cause us to skip over a record.
    #
    if ('98' eq $hash->{release_type_code})
    {
        # NON AUDIO/VIDEO PRODUCTS
        #
        Log->warn("Product has non-av release type, skipping. id: ".$hash->{product_id});
        next;
    }

    if ('NULL' eq $hash->{title})
    {
        # There is at least one 'NULL' product in the data...
        #
        Log->warn("NULL Product, skipping. id: ".$hash->{product_id});
        next;
    }

    if ('Y' ne $hash->{data_complete_flag})
    {
        Log->warn("Data_Complete_Flag not 'Y', skipping. id: ".$hash->{product_id});
        next;
    }


    # First, fetch the associated album (creating it if necessary)
    #
    my $album = $self->_parseAlbum($hash);


    # Now create some products.
    # !!! Since this is supposed to be just a digital exercise, just create a digital album product.
    # !!! We'll create the track products later.
    # !!! But we will record some extra stuff for later analysis.
    # !!! BUT - Only if these are 12 or 13 digit numerical values.
    #
    my $physicalICPN = $hash->{physical_icpn};
    $physicalICPN = undef unless $physicalICPN =~ /^\d+$/;

    my $physicalUPC = $hash->{physical_upc};
    $physicalUPC = undef unless $physicalUPC =~ /^\d+$/;

    my $relatedICPN = $hash->{related_icpn};
    $relatedICPN = undef unless $relatedICPN =~ /^\d+$/;

    my $digitalICPN = $hash->{digital_icpn};
    die "ERROR - invalid digital_icpn for product id: " . $hash->{product_id} unless $digitalICPN =~ /^\d+$/;

    my $releaseDate = $hash->{release_date};
    my $statusID = RPS::DB::Item::Product::kProductStatusActive;
    $statusID = RPS::DB::Item::Product::kProductStatusInactive if $hash->{hold_flag} eq 'Y';


    # Fetch the price (assuming we have it...)
    # !!! Why do we have a circular reference here?  product_price has a product_id, and product has a product_price_id. dumb.
    #
    my $priceIDHash = $self->{_price}{$hash->{price_category_code}};
    if (! $priceIDHash)
    {
        Log->warn("Unable to find price for product id: " . $hash->{product_id});
    }

    
    # !!! If we already have this product, going to assume we also have the product_price stuff as well.
    #
    my $product = RPS::DB::Item::Product->Lookup(product_code => $hash->{product_id});
    if ($product)
    {
        Log->info("DUPLICATE PRODUCT: ProductCode " . $hash->{product_id});
        return;
        
    }

    $product = RPS::DB::Item::Product->Create(
        product_code => $hash->{product_id},
        upc_ean => $digitalICPN,
        product_type_id => RPS::DB::Item::Product::kProductTypeDigital,
        asset_id => $album->album_id(),
        emi_physical_upc => $physicalUPC,
        emi_physical_icpn => $physicalICPN,
        emi_release_type_code => $hash->{release_type_code},
        emi_related_icpn => $relatedICPN,
        release_date => $releaseDate,
        product_status_id => $statusID,
    );
    $product->save();
    Log->debug("created new product", $product);


    # Now build the product price stuff.
    #
    if ($priceIDHash)
    {
        foreach my $currencyCode (keys %$priceIDHash)
        {
            my $priceID = $self->{_price}{$hash->{price_category_code}}{$currencyCode};
            my $productPrice = RPS::DB::Item::ProductPrice->Create(
                product_id => $product->product_id(),
                price_level_id => RPS::DB::Item::PriceLevel::kPriceLevelAlbumDownload,
                price_id => $priceID,
            );
            $productPrice->save();
        }
#        $product->product_price_id($productPrice->product_price_id);
        $product->default_price_level_id(RPS::DB::Item::PriceLevel::kPriceLevelAlbumDownload);
        $product->save();
    }
}


sub _parseAlbum
{
    my ($self, $hash) = @_;

    my $title = $hash->{title};

    # Clean up the crap a little bit.
    #
    $title = Common::Util::trimquotes($title);
    $title = Common::Util::trimspaces($title);

    my $titleClean = Common::Util::clean($title);


    # JPK - I don't fully grok their schema yet.
    # For now, I will treat each entry as a seperate album.
    # In reality, we probably have redundancy here.
    # Ultimately, we'll probably want to create unique albums
    # based on artist and title, and attach multiple products.
    # (Once I understand what <Format_Code> means...)
    #
    # For this initial test, just going to create Albums.
    #
    # Let's go ahead and strive to only create 'unique' albums.
    # We can then extend this to create Products.
    #

    
    # Get the artist id.
    #
    my $artistID;
    my $artistCode = $hash->{artist_code};
    
    # NULL is valid...
    #
    if ('NULL' ne $artistCode)
    {
        my $artist = RPS::DB::Item::Artist->Lookup(client_artist_id => $artistCode);
        if (! $artist)
        {
            $self->{_missingArtistCodes}{$artistCode}++;
            Log->warn("Cannot find artist with client_artist_id " . $hash->{artist_code} . " : Going to create a placeholder artist: ");
            my $artistName = "EMI ARTIST CODE $artistCode";
            my $artistNameClean = Common::Util::clean($artistName);

            $artist = RPS::DB::Item::Artist->Create(
                name => $artistName,
                name_clean => $artistNameClean,
                client_artist_id => $artistCode,
            );
            $artist->save();
        }

        $artistID = $artist->artist_id();
    }


    # Get the label id
    # (which we've mapped to Repertoire_Owner_id)
    #
    # !!! We're going to want to know what label to use if it's 'unknown'.
    if (! $self->{_unknownLabelID})
    {
        # The 'unknown label' has a client_label_id of 999.
        #
        my $unknownLabel = RPS::DB::Item::Label->Lookup(client_label_id => '999');
        $self->{_unknownLabelID} = $unknownLabel->label_id();
    }

    my $clientLabelID = $hash->{repertoire_owner_id};
    my $labelID;
    if ($clientLabelID && '000' ne $clientLabelID)
    {
        my $label = RPS::DB::Item::Label->Lookup(client_label_id => "$clientLabelID");
        if (! $label)
        {
            Log->warn("Cannot find label with client_label_id " . $hash->{repertoire_owner_id} . " : going to use '799'");
            $labelID = $self->{_unknownLabelID};
        }
        else
        {
            $labelID = $label->label_id();
        }
    }
    else
    {
        # Just use the default 'non-emi' label.
        #
        $labelID = $self->{_unknownLabelID};
    }


    # Do we already have this album?
    # !!! We are NOT going to consolidate.  We'll just create multiple albums, at least initially.
    #
    # artist ID might be null...  this could be a problem.
    #
#    my %lookupArgs = (
#        title => $title,
#        title_clean => $titleClean,
#        label_id => $labelID,
#    );
#    $lookupArgs{artist_id} = $artistID if $artistID;
#
#    my $album = RPS::DB::Item::Album->Lookup(%lookupArgs);
#
#    if (! $album)
#    {

    # !!! We're not 'consolidating', per se, but I do want to check to see whether we've got an album for this product_id.
    #
    # JPK - This lookup may not be working...
    # We appear to have created a bunch of duplicate albums.
    #
    my $album = RPS::DB::Item::Album->Lookup(client_album_id => $hash->{product_id});
    if ($album)
    {
        Log->info("DUPLICATE ALBUM: ProductCode " . $hash->{product_id});
        return $album;
    }

        my %createArgs = (
            title => $title,
            title_clean => $titleClean,
            label_id => $labelID,
            catalog_number => $hash->{projectcode},
            client_album_id => $hash->{product_id},
            c_line => $hash->{c_notice},
            p_line => $hash->{p_notice},
            status => $hash->{hold_flag} eq 'Y' ? 0 : 1,
        );
        $createArgs{artist_id} = $artistID if $artistID;
        $album = RPS::DB::Item::Album->Create( %createArgs );
        $album->save();
        Log->info("Created new album: " . $album->album_id());
        Log->debug($hash, $album);
#    }
#    else
#    {
#        Log->info("Duplicate album: " . $album->album_id());
#        Log->debug($hash, $album);
#    }


    # ... And here we would create some products.
    #

    return $album;
}

sub _parseComponent
{
    my ($self, $hash) = @_;

    # Component will just be used to map tracks to discs.
    # I'm just going to stage this data in memory to use later.
    #
    $self->{_component}{$hash->{component_id}}{component_number} = $hash->{component_number};
    $self->{_component}{$hash->{component_id}}{product_id} = $hash->{product_id}; # !!! May not need this at all.
}
    

sub _parseTrack
{
    my ($self, $hash) = @_;

    # We are going to create entries in:
    # product (for the track product)
    # product_track (for the digital album product)
    # track
    # master
    # song
    # ... in the reverse order I just listed. :)

    if ($hash->{data_complete_flag} ne 'Y')
    {
        Log->warn("Data_Complete_Flag not 'Y', skipping track id: ".$hash->{track_id});
        return;
    }


    # find the artist... should already have it in the system.
    #
    my $artistID;
    if ('NULL' ne $hash->{artist_code})
    {
        my $artist = RPS::DB::Item::Artist->Lookup(client_artist_id => $hash->{artist_code});
        if (! $artist)
        {
            Log->warn("Unable to find artist for track: " . $hash->{track_id} . ", skipping record");
            return;
        }
        $artistID = $artist->artist_id();
    }

    # Find the matching product, which will lead us to the correct album.
    # I've stuffed emi's product_id into the product_code field for these album products.
    # 
    # Note that the product may not exist, if the <data_complete_flag> was not 'Y'.
    #
    my $product = RPS::DB::Item::Product->Lookup(product_code => $hash->{product_id}, product_type_id => RPS::DB::Item::Product::kProductTypeDigital);
    if (! $product)
    {
        Log->warn("Unable to find album product for track: " . $hash->{track_id} . ", skipping record");
        return;
    }
    my $albumID = $product->asset_id();


    # Find the component data
    #
    my $componentID = $hash->{component_id};
    my $componentRecord = $self->{_component}{$componentID};
    if (! $componentRecord)
    {
        Log->warn("Unable to find component id $componentID for track " . $hash->{track_id} . ", skipping record");
        return;
    }

    # !!! Check to see whether the track already exists.  If it doesn't, then we'll go ahead and create song, master, and track.
    #
    my $track = RPS::DB::Item::Track->Lookup(client_track_id => $hash->{track_id});
    if ($track)
    {
        Log->info("DUPLICATE TRACK: track_id " . $hash->{track_id});
        return;
    }


    # song
    #
    my $title = $hash->{title};
    $title = Common::Util::trimquotes($title);
    $title = Common::Util::trimspaces($title);


    my $song = RPS::DB::Item::Song->Create(
        title => $title,
    );
    $song->save();


    # master
    #
    # JPK - Using the physical isrc for the track product isrc.
    # We'll still record the digital isrc in a custom field in track.
    #
    my $isrc = $hash->{physical_isrc};
    my %masterArgs = (
        title => $title,
        song_id => $song->song_id(),
        isrc => $isrc,
        duration => $hash->{duration},
    );
    $masterArgs{artist_id} = $artistID if $artistID;
    my $master = RPS::DB::Item::Master->Create( %masterArgs );
    $master->save();


    # track
    #
    # !!! leaving track_order undefined for now.
    #
    my $titleClean = Common::Util::clean($title);
    my %trackArgs = (
        title => $title,
        title_clean => $titleClean,
        master_id => $master->master_id(),
        album_id => $albumID,
        client_track_id => $hash->{track_id},
        custom_1 => $hash->{digital_isrc},
        parental_advisory => ('Y' eq $hash->{parental_advisory_flag} ? 'E' : 'C'),
    );
    $trackArgs{artist_id} = $artistID if $artistID;

    $track = RPS::DB::Item::Track->Create(%trackArgs);
    $track->save();

    
    # Link this track to the digital album product.
    #
    my $productTrack = RPS::DB::Item::ProductTrack->Create(
        product_id => $product->product_id(),
        track_id => $track->track_id(),
        disc_number => $componentRecord->{component_number},
        disc_track => $hash->{track_number},
    );
    $productTrack->save();

    # Now, finally, create the digital track product
    #
    my $digitalTrackProduct = RPS::DB::Item::Product->Create(
        upc_ean => $product->upc_ean(),
        product_status_id => $product->product_status_id(),  # using the album product's status
        asset_id => $track->track_id(),
        product_type_id => RPS::DB::Item::Product::kProductTypeDigitalTrack,
        emi_release_type_code => $hash->{release_type},    # the track data calls this 'release_type' instead of 'release_type_code'
    );
    $digitalTrackProduct->save();


    # Associate a price with this track product?
    # Do we need to do that?  maybe...
    #
    my $priceIDHash = $self->{_price}{$hash->{price_category_code}};
    if (! $priceIDHash)
    {
        Log->warn("Unable to find price for track id: " . $hash->{track_id});
    }
    else
    {
        foreach my $currencyCode (keys %$priceIDHash)
        {
            my $priceID = $self->{_price}{$hash->{price_category_code}}{$currencyCode};
            my $productPrice = RPS::DB::Item::ProductPrice->Create(
                product_id => $digitalTrackProduct->product_id(),
                price_level_id => RPS::DB::Item::PriceLevel::kPriceLevelTrackDownload,
                price_id => $priceID,
            );
            $productPrice->save();
        }
#        $digitalTrackProduct->product_price_id($productPrice->product_price_id);
        $digitalTrackProduct->default_price_level_id(RPS::DB::Item::PriceLevel::kPriceLevelTrackDownload);
        $digitalTrackProduct->save();
    }

}


sub _parsePrice
{
    my ($self, $hash) = @_;

    # Ignore non-USD prices (for now).
    #
#    return unless 'USD' eq $hash->{currency_code};

    my $price = RPS::DB::Item::Price->Lookup(name => $hash->{price_code}, currency_code => $hash->{currency_code});
    if ($price)
    {
        Log->info("DUPLICATE PRICE: name:" . $hash->{price_code} . " currency:" . $hash->{currency_code});
    }
    else
    {
        $price = RPS::DB::Item::Price->Create(
        name => $hash->{price_code},
        emi_price_category_code => $hash->{price_code},
        wholesale => $hash->{price},
        currency_code => $hash->{currency_code},
        );
        $price->save();
    }

    # Going to keep a map internally of emi code to rps id, to speed up the subsequent product import.
    # It's a largish-list, but not too bad.
    #

    $self->{_price}{$hash->{price_code}}{$hash->{currency_code}} = $price->price_id();
}

1;
