#---------------------------------------------------------------
# ____                   _ _         ____  _
#|  _ \ ___  _   _  __ _| | |_ _   _/ ___|| |__   __ _ _ __ ___
#| |_) / _ \| | | |/ _` | | __| | | \___ \| '_ \ / _` | '__/ _ \
#|  _ < (_) | |_| | (_| | | |_| |_| |___) | | | | (_| | | |  __/
#|_| \_\___/ \__, |\__,_|_|\__|\__, |____/|_| |_|\__,_|_|  \___|
#            |___/             |___/
#
# 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.
        #
    }
}

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;
