#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package Distribution::Catalog::Album;

use strict;
use warnings;

use Data::Dumper;
use List::Compare;

use lib '/app/tools/common/lib';
use Common::XMLObject;
use Common::Assert;
use Common::UTF8;

use lib '/app/tools/distribution/lib';
use Distribution::Catalog::Genre;
use Distribution::Catalog::Volume;
use Distribution::Catalog::CountryList;
use Distribution::Catalog::AdditionalArtist;
use Distribution::DB::Item::GenreServiceMap;
use Distribution::DB::Item::ProductDistribution;

use base 'Common::XMLObject';

# This hash defines what tags/keys we expect.
#
use constant kAttribute             => 'a';
use constant kTag                   => 't';
use constant kVolumeObjList         => 'v';
use constant kGenreObject           => 'g';
use constant kAdditionalArtistsList => 'z';

my %gConfig = (
    'sell-bundled'            => kTag,
    'is-explicit'             => kTag,
    'upc'                     => kTag,
    'title'                   => kTag,
    'catalog-number'          => kTag,
    'label'                   => kTag,
    'release-date'            => kTag,
    'cline'                   => kTag,
    'pline'                   => kTag,
    'genre'                   => kTag,
    'genre-secondary'         => kTag,
    'genre-id'                => kTag,
    'genre-secondary-id'      => kTag,
    'display-artist'          => kTag,
    'territories'             => kTag,
    'is-classical'            => kTag,
    'image-path-linux'        => kTag,
    'image-path-windows'      => kTag,
    'image-file'              => kTag,
    'apple-album-id'          => kTag,
    'product-distribution-id' => kTag,
    'wholesale-price-tier'    => kTag,
    'default-price-tier'      => kTag,
    'genre-object'            => kGenreObject,
    'additional-artists'      => kAdditionalArtistsList,
    'volume'                  => kVolumeObjList,
);

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

    $self->SUPER::_init(%args);

    # So, we'll keep this _very_ simple for now.
    # I don't think we'll even need an XML based constructor here.
    # We'll just take in a hash table reference, and that'll be it.
    #
    my $hashRef = $args{hash};
    assert( $hashRef, "ERROR - Missing 'hash' argument" );

    $self->{_serviceID}     = $args{service_id};
    $self->{_invertDenied}  = $args{invert_denied};
    $self->{_removeRegions} = $args{remove_regions};

    # It would be quicker, I suppose, to just hang onto the hash reference
    # and output that directly...  But, I'd like to give us the chance to
    # do some sort of validation.  And some of these keys need to become 'attributes'.
    # Plus, hanging onto references that get passed around is a good way to leak memory.
    #
    foreach my $expectedKey ( keys %gConfig ) {
        if (   ( $expectedKey eq "genre-secondary" && !( exists $hashRef->{$expectedKey} ) )
            || ( $expectedKey eq "is-classical" && !( exists $hashRef->{$expectedKey} ) )
            || ( $expectedKey eq "genre-secondary-id" && !( exists $hashRef->{$expectedKey} ) ) ) {
            ## TODO: create a stub?
            next;
        }

        #assert(exists $hashRef->{$expectedKey}, "ERROR - hash missing key $expectedKey");
        if ( kAttribute eq $gConfig{$expectedKey} ) {
            $self->setXMLParam( $expectedKey, Common::UTF8::Encode( $hashRef->{$expectedKey} ) );
        } elsif ( kTag eq $gConfig{$expectedKey} ) {
            $self->{$expectedKey} = Common::UTF8::Encode( $hashRef->{$expectedKey} );
        } elsif ( kVolumeObjList eq $gConfig{$expectedKey} ) {

            # Instantiate a 'volume' object, save it here.
            #
            $self->{volume} = [];
            my $volumeList = $hashRef->{$expectedKey};
            foreach my $volumeHash (@$volumeList) {

                # Instantiate a Volume, save it here.
                #
                my $volume = Distribution::Catalog::Volume->new( hash => $volumeHash, serviceID => $self->{_serviceID} );
                push @{ $self->{volume} }, $volume;
            }
        } elsif ( kAdditionalArtistsList eq $gConfig{$expectedKey} && $hashRef->{$expectedKey} ) {
            $self->{'additional-artists'} = [];

            my $artistList;
            if ( ref( $hashRef->{$expectedKey} ) eq 'ARRAY' ) {
                $artistList = $hashRef->{$expectedKey} || [];
            } else {
                $artistList = [ $hashRef->{$expectedKey} ];
            }

            foreach my $artistHash (@$artistList) {

                # Instantiate a Volume, save it here.
                #
                my $artist = Distribution::Catalog::AdditionalArtist->new( hash => $artistHash );
                push @{ $self->{'additional-artists'} }, $artist;
            }
        } elsif ( kGenreObject eq $gConfig{$expectedKey} ) {
        }

    }

    my $genre     = new Distribution::Catalog::Genre( genreName => $self->{genre},             serviceID => $self->{_serviceID} );
    my $sec_genre = new Distribution::Catalog::Genre( genreName => $self->{'genre-secondary'}, serviceID => $self->{_serviceID} );

    $self->{'genre-object'}           = $genre;
    $self->{'genre-secondary-object'} = $sec_genre;

    $self->_buildCountryList();
    $self->setSequence();

    $self->{'wholesale-price-tier'} = $self->{'default-price-tier'}
      unless ( $self->{'wholesale-price-tier'} );

    return $self;
}

# Set sequence numbers for vendors that don't have volume numbers
sub setSequence {
    my $self = shift;
    my $volume;
    my $track;

    my @volumes;
    my @tracks;
    my %maxTrack;

    @volumes = @{ $self->{volume} } if ( $self->{volume} );

    # Find the last track for each volume
    foreach $volume (@volumes) {
        @tracks = @{ $volume->{track} } if ( $volume->{track} );
        foreach my $track (@tracks) {
            $maxTrack{ $volume->{sequence} } = _max( $track->{sequence}, $maxTrack{ $volume->{sequence} } );
        }
    }

    # Find the last track for each volume
    foreach $volume (@volumes) {
        $volume->setSequence( \%maxTrack );
    }
}

sub _buildCountryList {
    my $self = shift;
    my @serviceCountries = @{ $self->{_serviceCountries} } if ( $self->{_serviceCountries} );

    my $countryList = new Distribution::Catalog::CountryList(
        allowed => $self->{territories}->{allowed},
        denied  => $self->{territories}->{denied}
    );

    $countryList->RequireAllowed( $self->{_invertDenied} );
    $countryList->CountriesOnly( $self->{_removeRegions} );
    $countryList->Countries(@serviceCountries) if (@serviceCountries);

    $self->{territories}->{allowed} = $countryList->Allowed();
    $self->{territories}->{denied}  = $countryList->Denied();
}

sub asHashRef {
    my $self = shift;
    my $result;
    my %data;
    my $dest_key;

    foreach my $key ( keys( %{$self} ) ) {
        next if ( $key =~ /^_/ );
        next if ( $key =~ /^volume$/ );

        $dest_key = $key;
        $dest_key =~ s/-/_/g;

        if ( ref( $self->{$key} ) && ref( $self->{$key} ) eq 'HASH' ) {
            %data = %{ $self->{$key} };
            $result->{$dest_key} = \%data;
        } elsif ( ref( $self->{$key} ) && ref( $self->{$key} ) ne 'ARRAY' ) {
            $result->{$dest_key} = $self->{$key}->asHashRef();
        } else {
            $result->{$dest_key} = $self->{$key};
        }
    }

    $result->{volumes} = [];
    foreach my $volume ( @{ $self->{volume} } ) {
        push( @{ $result->{volumes} }, $volume->asHashRef() );
    }

    if ( $self->{'additional-artists'} ) {
        $result->{'additional_artists'} = [];
        foreach my $artist ( @{ $self->{'additional-artists'} } ) {
            push( @{ $result->{'additional_artists'} }, $artist->asHashRef() );
        }
    }

    return $result;
}

sub setWorldWideTerritory {
    my $self = shift;
    my $code = shift || "WW";

    Common::Log::Debug("Checking to see WW territory needs to be set");

    my @deniedCountries =
      ref( $self->{territories}->{denied} )
      ? @{ $self->{territories}->{denied} }
      : ( $self->{territories}->{denied} )
      if ( $self->{territories}->{denied} );

    my @allowedCountries =
      ref( $self->{territories}->{allowed} )
      ? @{ $self->{territories}->{allowed} }
      : ( $self->{territories}->{allowed} )
      if ( $self->{territories}->{allowed} );

    unless ( @deniedCountries || @allowedCountries ) {
        Common::Log::Debug("  - setting allowed territory to $code");
        $self->{territories}->{allowed} = $code;
    }
}

sub setServiceCountries {
    my $self = shift;

    if (@_) {
        Common::Log::Debug("Service restricted to @_");

        $self->{_serviceCountries} = \@_;

        $self->_buildCountryList();
    }
}

sub invertDeniedCountries {
    my $self   = shift;
    my $invert = shift;

    $self->{_invertDenied} = $invert ? 1 : undef;

    $self->_buildCountryList();
}

sub removeRegions {
    my $self   = shift;
    my $remove = shift;

    $self->{_removeRegions} = $remove ? 1 : undef;

    $self->_buildCountryList();
}

sub canBeSoldInCountry {
    my $self            = shift;
    my $targetCountries = shift;
    my $worldWideCode   = shift;

    my @albumCountries =
      ref( $self->{territories}->{allowed} )
      ? @{ $self->{territories}->{allowed} }
      : ( $self->{territories}->{allowed} )
      if ( $self->{territories}->{allowed} );

    my @deniedCountries =
      ref( $self->{territories}->{denied} )
      ? @{ $self->{territories}->{denied} }
      : ( $self->{territories}->{denied} )
      if ( $self->{territories}->{denied} );

    if (@albumCountries) {
        return $self->_isAllowedCountry( $targetCountries, $worldWideCode );
    } elsif (@deniedCountries) {
        return !$self->_isDeniedCountry($targetCountries);
    } else {
        Common::Log::Debug("No countries found, default allow");
        return 1;
    }
}

sub canDeliverGenre {
    my $self             = shift;
    my $serviceID        = shift;
    my $primaryGenreID   = $self->{'genre-id'};
    my $secondaryGenreID = $self->{'genre-secondary-id'};

    # If there isn't a secondary genre just set the secondary to the primary

    assert($serviceID);
    assert($primaryGenreID);

    Common::Log::Debug("Checking if genre is allowed for service");
    Common::Log::Debug("  Genre ID primary: $primaryGenreID ");
    Common::Log::Debug("  Genre ID secondary: $secondaryGenreID ")
      if ($secondaryGenreID);

    # If we found a primary genre match we're done.
    return 1 if (
        Distribution::DB::Item::GenreServiceMap->GetNewGenreName(
            service_id => $serviceID,
            genre_id   => $primaryGenreID
        )
    );

    # If a secondary genre is defined, check for a mapping
    return 1
      if (
        $secondaryGenreID
        && Distribution::DB::Item::GenreServiceMap->GetNewGenreName(
            service_id => $serviceID,
            genre_id   => $secondaryGenreID
        )
      );

    # No genre mapping found
    return undef;
}

sub _isAllowedCountry {
    my $self          = shift;
    my $countries     = shift;
    my $worldWideCode = shift;

    my @targetCountries = @$countries;

    # Service has no country restrictions
    return 1 unless (@targetCountries);

    return unless ( $self->{territories}->{allowed} );

    my @albumCountries =
      ref( $self->{territories}->{allowed} )
      ? @{ $self->{territories}->{allowed} }
      : ( $self->{territories}->{allowed} )
      if ( $self->{territories}->{allowed} );

    push( @targetCountries, $worldWideCode ) if ($worldWideCode);

    Common::Log::Debug("Checking allowed country for album");
    Common::Log::Debug("  album allowed countries: @albumCountries");
    Common::Log::Debug("  target countries: @targetCountries");

    my $lc = List::Compare->new( {
            lists    => [ \@albumCountries, \@targetCountries ],
            unsorted => 1,
        }
    );

    my @intersection = $lc->get_intersection();
    Common::Log::Debug("    INTERSECTION: @intersection");

    return scalar @intersection;
}

sub _isDeniedCountry {
    my $self            = shift;
    my $countries       = shift;
    my @targetCountries = @$countries;

    my @albumCountries =
      ref( $self->{territories}->{denied} )
      ? @{ $self->{territories}->{denied} }
      : ( $self->{territories}->{denied} )
      if ( $self->{territories}->{denied} );

    Common::Log::Debug("Checking denied country for album");
    Common::Log::Debug( "  album denied countries: @albumCountries (count: " . @albumCountries . ")" );
    Common::Log::Debug("  target countries: @targetCountries");

    # Nothing denied
    return unless ( $self->{territories}->{denied} );

    # Everything is denied and the service isn't country restricted
    my $allCountries = new Distribution::Catalog::CountryList;
    my @allCountries = @{ $allCountries->Countries() };
    if ( @allCountries == @albumCountries && !@targetCountries ) {
        Common::Log::Debug("  -- All countries are denied -- ");
        return 1;
    }

    # The service doesn't have any country restrictions
    return unless (@targetCountries);

    Common::Log::Debug( "  all countries: @allCountries (count: " . @allCountries . ")" );

    my $lc = List::Compare->new( {
            lists    => [ \@targetCountries, \@albumCountries ],
            unsorted => 1,
        }
    );

    my @intersection = $lc->get_intersection();
    Common::Log::Debug("  intersecting countries: @intersection");

    # If the intersection is the same size as our target country list
    # Then it is denied for ALL countries for this service.
    return @intersection == @targetCountries;
}

sub setDenied {
    my $self    = shift;
    my $message = shift;

    assert($message);

    Distribution::DB::Item::ProductDistribution->setDenied( $self->{'product-distribution-id'}, $message );

}

sub _max {
    my ( $a, $b ) = @_;

    $a = 0 unless ($a);

    return $a unless ($b);

    return $a > $b ? $a : $b;
}

1;
