#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package RPS::Product::Product;

use strict;
use warnings;
use Carp;
use Data::Dumper;

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

use lib '/app/tools/rps/lib';
use RPS::DB::Item::Product;
use lib '/app/tools/rps/lib';
use RPS::DB::Item::Product;
use RPS::DB::Item::ProductDigital;
use RPS::DB::Item::Album;
use RPS::Product::TrackList;
use RPS::Product::PriceList;
use RPS::Product::ProductDistribution;

use Common::FormObject;
use base 'Common::FormObject';


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

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

	if ($args{_dbItem})
	{
		return $self->_initFromDBItem($args{_dbItem});
	}
	elsif ($args{productID})
	{
		return $self->_initFromDB($args{productID});
	}
	else
	{
		return $self->_initWithDefaults($args{albumID}, $args{catalogNumber});
	}
}


sub _initFromDB
{
	my ($self, $productID) = @_;

	my $dbItem = RPS::DB::Item::Product->Lookup(product_id => $productID);
	return $self->_initFromDBItem($dbItem);
}


sub _initFromDBItem
{
	my ($self, $dbItem) = @_;

    my %properties =
    (
        product_id          => $dbItem->product_id,
        parent_product_id   => $dbItem->parent_product_id,
        asset_id            => $dbItem->asset_id,
	    title               => $dbItem->title,
	    title_clean         => $dbItem->title_clean,        
	    product_code        => $dbItem->product_code,
        upc_ean             => $dbItem->upc_ean,
        upc_alt             => $dbItem->upc_alt,
        release_date        => $dbItem->release_date,
        date_created        => $dbItem->date_created,
        date_modified       => $dbItem->date_modified,
        created_by          => $dbItem->created_by,
        modified_by         => $dbItem->modified_by,
        product_type_id     => $dbItem->product_type_id,
        product_price_id    => $dbItem->product_price_id,
        product_status_id   => $dbItem->product_status_id,
        default_price_level_id   => $dbItem->default_price_level_id,
        deleted             => $dbItem->deleted,
        deleted_date        => $dbItem->deleted_date,
    );


    #  Derive the catalog_number, assuming we have an albumID
    #  Might as well grab the album name too.
    if (RPS::DB::Item::Product::kProductTypeDigitalTrack != $dbItem->product_type_id)
    {
		my $albumDBItem = RPS::DB::Item::Album->Lookup(album_id => $dbItem->asset_id);
        $properties{catalog_number} = $albumDBItem->catalog_number;
        $properties{album_name} = $albumDBItem->title;
        $properties{artist_id} = $albumDBItem->artist_id;
        
	}


    return $self->_initProperties(\%properties);
}


sub _initWithDefaults
{
	my ($self, $albumID, $catalogNumber) = @_;

    # Need to look up the album title to use as a default for the product title.
    my $title;
    if ($albumID) {
    	my $albumDBItem = RPS::DB::Item::Album->Lookup(album_id => $albumID);
        $title = $albumDBItem->title;
    }

    my %defaultProperties =
    (
        title           => $title,
        asset_id        => $albumID,
        catalog_number  => $catalogNumber,
    );

    return $self->_initProperties(\%defaultProperties);
}


sub _initProperties
{
	my ($self, $prop) = @_;

	$self->{ProductID}          = Common::FormObject::Scalar->new(value => $prop->{product_id}, readOnly => 1);
	$self->{ParentProductID}    = Common::FormObject::Scalar->new(value => $prop->{parent_product_id});
	$self->{Title}              = Common::FormObject::Scalar::String->new(value => $prop->{title}, required => 1, maxlength => 255);
	$self->{TitleClean}         = Common::FormObject::Scalar::String->new(value => $prop->{title_clean}, readOnly => 1);	
	$self->{ProductCode}        = Common::FormObject::Scalar::String->new(value => $prop->{product_code});
	$self->{UPC}                = Common::FormObject::Scalar::String->new(value => $prop->{upc_ean}, required => 1, maxlength => 25);
	$self->{UPCAlt}             = Common::FormObject::Scalar::String->new(value => $prop->{upc_alt}, maxlength => 25);
	$self->{ReleaseDate}        = Common::FormObject::Scalar::Date->new(value => $prop->{release_date});

	$self->{CreatedDate}        = Common::FormObject::Scalar::DateTime->new(value => $prop->{date_created}, readOnly => 1);
	$self->{ModifiedDate}       = Common::FormObject::Scalar::DateTime->new(value => $prop->{date_modified}, readOnly => 1);
	$self->{CreatedBy}          = Common::FormObject::Scalar::UserName->new(value => $prop->{created_by}, readOnly => 1);
	$self->{ModifiedBy}         = Common::FormObject::Scalar::UserName->new(value => $prop->{modified_by}, readOnly => 1);

	$self->{ProductTypeID}      = Common::FormObject::Scalar->new(value => $prop->{product_type_id}, required => 1);
	$self->{ProductPriceID}     = Common::FormObject::Scalar->new(value => $prop->{product_price_id});
	$self->{ProductStatusID}    = Common::FormObject::Scalar->new(value => $prop->{product_status_id}, required => 1);
	$self->{DefaultPriceLevelID}= Common::FormObject::Scalar->new(value => $prop->{default_price_level_id});

	$self->{AssetID}            = Common::FormObject::Scalar->new(value => $prop->{asset_id}, required => 1);
	$self->{CatalogNumber}      = Common::FormObject::Scalar::String->new(value => $prop->{catalog_number});
	$self->{AlbumName}          = Common::FormObject::Scalar::String->new(value => $prop->{album_name}, readOnly => 1);
	$self->{Deleted}            = Common::FormObject::Scalar->new(value => $prop->{deleted});
	$self->{DeletedDate}        = Common::FormObject::Scalar::Date->new(value => $prop->{deleted_date});

    # We need to fetch the artist name, given an artist id.
    #
    my $artistName;
    if ($prop->{artist_id})
    {
        my $artistDBItem = RPS::DB::Item::Artist->Lookup(artist_id => $prop->{artist_id});
        $artistName = $artistDBItem->name;
    }
    $self->{ArtistName} = Common::FormObject::Scalar::String->new(value => $artistName);

	if($self->loadSubs())
	{
        if ( ! defined($prop->{product_type_id}) || RPS::DB::Item::Product::kProductTypeDigitalTrack != $prop->{product_type_id})
        {
		    $self->{TrackList} = RPS::Product::TrackList->new(productID => $prop->{product_id}, albumID => $prop->{asset_id});
        }
		$self->{PriceList} = RPS::Product::PriceList->new(productID => $prop->{product_id});
	}

    return $self;
}


sub validate
{
	my $self = shift;
	my %args = @_;
	my $valid = 1;
	#my $valid = $self->SUPER::validate( @_ );

    $valid = 0
        unless ($self->_validateMetadata( %args ));

    return $valid;
}

sub _validateMetadata() {
    my ($self, %args) = @_;
    my $validator = $args{metadataValidator};
    
    my %validation_args;

	my $currentData = RPS::DB::Item::Product->Lookup(product_id => $self->ProductID)
        if( $self->ProductID );

    my $validationKey;

	my %scalar_rule_map = (
    );

	my $valid =  $self->SUPER::_validateMetadata( fieldMap => \%scalar_rule_map,
                                                  currentData => $currentData,
                                                  metadataValidator => $validator );
   
    if ($self->AssetID()) 
    {
        # Adding some special DA only validation
        if ($self->ProductTypeID == RPS::DB::Item::Product::kProductTypeDigital)
        {
            # Enforcing a limit of 32 for DA products.
            my $daCount;
            if( $self->ProductID ) 
            {
                $daCount = RPS::DB::Item::Product->GetDACountByAlbumID($self->AssetID(), $self->ProductID);
            }
            else  
            {
                $daCount = RPS::DB::Item::Product->GetDACountByAlbumID($self->AssetID()); 
            }  
                
            if ( $daCount >= 32 ) {
                $self->{ProductTypeID}->setError( "product_type_id_invalid" );
                $self->{ProductTypeID}->setErrorString( "limit has been reached." );
                $valid = 0;
            }
            
            #if ( RPS::DB::Item::Product->UPCExistsDA( upc => $self->UPC, albumID => $self->AssetID(), productID => $self->ProductID ) ) {
            #    $self->{UPC}->setError( "upc_inuse" );
            #    $self->{UPC}->setErrorString( "in use by another digital album." );
            #    $valid = 0;
            #}                
        } 
        # And now some validation for physical products
        elsif ($self->ProductTypeID != RPS::DB::Item::Product::kProductTypeDigitalTrack)
        {
            # Enforcing a limit of 1 for physical products.
            my $productTypeExists = RPS::DB::Item::Product->ProductTypeExistsOnAlbum(productTypeID => $self->ProductTypeID, albumID => $self->AssetID(), productID => $self->ProductID);
            if ( $productTypeExists ) {
                $self->{ProductTypeID}->setError( "product_type_id_invalid" );
                $self->{ProductTypeID}->setErrorString( "limit has been reached." );
                $valid = 0;
            }            
        }
    }

	unless( $validator ) {
		Common::Log::Debug( "No validator passed to " . ref($self) . "::_validateMetadata, skipping validation!!!!!" );
		return $valid;
    }

    my $product_track_valid = $self->TrackList->validate( %args );

    # Don't test if data hasn't changed
    return $valid
        if( ($self->ProductID && $currentData->upc_ean eq $self->UPC) && ! $validator->{_ignoreCurrentData} );

	my $test = $validator->validate_constraint( 'digital_upc', $self->UPC, 'digital_upc' => $self->UPC );

	if( ! $test->is_valid() ) {
        $self->{UPC}->setError( $test->error_code() );
        $self->{UPC}->setErrorString( $test->error_as_string() );
		$valid = 0;
	}

    # If the UPC is valid then we want to see if any other products are using
    # the same UPC.  However, we do allow duplicate UPC is the are for the same
    # album, but a different product type.
    if ($self->AssetID()) {
        if( $self->ProductTypeID == RPS::DB::Item::Product::kProductTypeDigitalTrack ) {
            $validation_args{trackID} = $self->AssetID();
        } else {
            $validation_args{albumID} = $self->AssetID();
        }
    } else {
        $validation_args{albumID} = $args{assetID};
    }

    use Data::Dumper;
    Common::Log::Debug( "VALID: " . Dumper \%validation_args );

    #if( RPS::DB::Item::Product->UPCExists( upc => $self->UPC, %validation_args ) ) {
    #    $self->{UPC}->setError( "upc_inuse" );
    #    $self->{UPC}->setErrorString( "in use by another album." );
    #	$valid = 0;
    #}

    if( ! $self->UPC ) {
        $self->{UPC}->setError( "upc_required" );
        $self->{UPC}->setErrorString( "UPC Required" );
		$valid = 0;
    }

    if( ! Validate::Util::is_valid_upc_or_ean( $self->UPC ) ) {
        $self->{UPC}->setError( "upc_invlid" );
        $self->{UPC}->setErrorString( "is not valid" );
		$valid = 0;
    }

    if( $valid && $product_track_valid ) {
        Common::Log::Debug( "Product _validateMetadata Passed All Tests.");
    } else {
        Common::Log::Debug( "Product _validateMetadata Failed.");
    }


	#die "Validate: " . $self->{ReleaseDate}->validate() . " ReleaseDate: '" . "$self->{ReleaseDate}->{_value}" . "'";
	# Couldn't do this with standard metadata validation because we changed the
	# release date metadata validation for the importer.  Only a failure if we are
	# Checking for distribution
	if( $self->ProductTypeID == RPS::DB::Item::Product::kProductTypeDigital &&
	    ( ! $self->{ReleaseDate}->_getValue() || ! $self->{ReleaseDate}->validate )&&
		$validator->{_failState} == 0 ) {
    	$self->{ReleaseDate} = Common::FormObject::Scalar::Date->new();
		$self->{ReleaseDate}->setError( "release_date_required" );
        $self->{ReleaseDate}->setErrorString( "Required for distribution" );
		$valid = 0;
	}

    Common::Log::Debug( "Product Valid: $valid, Product Track Valid: $product_track_valid" );
    return $valid && $product_track_valid;
}


sub save
{
	my ($self) = @_;

	# If we don't have a ProductID, then this is a _new_ product...
	# ProductID is given to us by the database.
	#
	my $dbObj;
	my $dbObjProdDig;
    my $createProductDigital = 0;
	if ($self->ProductID())
	{
		$dbObj = RPS::DB::Item::Product->Lookup(product_id => $self->ProductID());
	}
	else
	{
		$dbObj = RPS::DB::Item::Product->Create();

        ## if we're creating a digital album product, need to also
        ## create an entry in the product_digital table!
        if ($self->ProductTypeID() == 3)
        {
            $createProductDigital = 1;
		    $dbObjProdDig = RPS::DB::Item::ProductDigital->Create();
		    $dbObjProdDig->product_id($self->ProductID());
		    $dbObjProdDig->save();
        }

	}

	#
	# !!! Some of these fields will require special encoding...
	#
	$dbObj->product_code($self->ProductCode());
	$dbObj->asset_id($self->AssetID());
	$dbObj->title($self->Title());
	$dbObj->upc_ean($self->UPC());
	$dbObj->upc_alt($self->UPCAlt());
	$dbObj->release_date($self->ReleaseDate());
	$dbObj->product_type_id($self->ProductTypeID());
	$dbObj->product_price_id($self->ProductPriceID());
	$dbObj->product_status_id($self->ProductStatusID());
	$dbObj->default_price_level_id($self->DefaultPriceLevelID());
	$dbObj->deleted($self->Deleted());
	$dbObj->deleted_date($self->DeletedDate());


    # Derrive the clean title from title
    #
    $dbObj->title_clean(Common::Util::clean_name_catalog($self->Title()));

	# Did anything change?  Then save it!
	#
	if ($dbObj->isDirty())
	{
		$dbObj->save();

        # reload object... but don't reload the subs. We'll be dealing with
        # that later.
        #
        $self->loadSubs(0);
        $self->_initProperties($dbObj);
	}

    if ($createProductDigital == 1)
    {
        $dbObjProdDig->product_id($self->ProductID());
        $dbObjProdDig->save();
    }

	# Now, save the prices...
    # But first, we need to set the ProductID
	#
    my $prices = $self->{PriceList}->getPrices();
    foreach my $price (@$prices)
    {
        $price->ProductID($self->ProductID());
    }
	$self->{PriceList}->save();

	# Now, save the track list..
    # But first, we need to set the ProductID
	#
    my $tracks = $self->{TrackList}->getTracks();
    foreach my $track (@$tracks)
    {
        $track->ProductID($self->ProductID());
    }
	$self->{TrackList}->save();

	# !!! Be careful here, this data is never surfaced in the UI
	# so if something's wrong you won't know unless you look in the db. -jff-
	#
	# ok, let's handle those digital track products (the unseen, unheard products)
	#
	if(RPS::DB::Item::Product::kProductTypeDigital == $dbObj->product_type_id)
	{
		$tracks = $self->{TrackList}->getTracks();
		foreach my $trk (@$tracks)
		{
			# if this track is included in the digital product then we
			# need to have a digital track product for it.
			#
			my $trkProd = RPS::DB::Item::Product->GetDigitalTrackProduct($trk->TrackID(), $dbObj->product_id);
			if($trk->{_inDB})
			{
				# does one already exist?
				#
				if(!defined $trkProd)
				{
					# if not, then create one here
					#
					$trkProd = RPS::DB::Item::Product->Create(asset_id => $trk->TrackID(),
															  product_type_id => RPS::DB::Item::Product::kProductTypeDigitalTrack(),
															  parent_product_id => $dbObj->product_id);
				}

				# let's sync up the track product with the main digital product
				#
				# same as the digital album product
				#
				$trkProd->product_code($dbObj->product_code);
				$trkProd->upc_ean($dbObj->upc_ean);
				$trkProd->release_date($dbObj->release_date);
				$trkProd->product_price_id($dbObj->product_price_id);
				$trkProd->product_status_id($dbObj->product_status_id);

				# save it
				#
				$trkProd->save();
			}
			else
			{
				# this track is not included in this digital product so we
				# need to make sure a track product doesn't exist either.
				#
				# does this track product exist?
				#
                # JPK - !!! NO WAY !!! We can't just nuke a product, it may have been matched to a sale!
#				if($trkProd)
#				{
#					# ok, let's delete it
#					#
#                    assert(0, "WHAT THE FUCK, DUDE?");
#					$trkProd->delete();
#				}
			}
		}
	}
}


sub TrackList
{
	my ($self) = @_;

	return $self->{TrackList};
}


sub delete
{
	my ($self) = @_;

	# check the track_licenses for licenses that
	# are attached to specific products,
	# these products cannot be deleted
	#
	# 1. get the album
	# 2. get all track on the album
	# 3. get all track_licenses for each track
	# 4. lookup the license for each track_license
	# 5. if license.product_id matches this product then DONT DELETE
	#
	# this is a hairy process to go through :-0

	# we need to delete the track list
	#
	foreach my $trkProduct (@{$self->{TrackList}->getTracks()})
	{
        # !!! This looks totally redundant to me
        # !!! If I call 'delete' on a track product, why do I need to bother with this GetDigitalTrackProduct stuff?
        # !!! Has this code ever actually _run_?
        # jpk - TrackList contains RPS::Product::Track objects.  These are different - may or may not map to a product.
        #

		# delete digital track products too
		#
		if(RPS::DB::Item::Product::kProductTypeDigital == $self->ProductTypeID())
		{
			my $trkProd = RPS::DB::Item::Product->GetDigitalTrackProduct($trkProduct->TrackID(), $self->ProductID());
			$trkProd->delete if($trkProd);
		}

		$trkProduct->delete();
	}

	# delete this product
	#
	my $product = RPS::DB::Item::Product->Lookup(product_id => $self->ProductID());
	$product->delete();

	return 1;
}

sub requeueDelivery
{
    my $self = shift;
    my @serviceIDs = @_;

    foreach my $serviceID ( @serviceIDs ) {
        my $productService = new RPS::Product::ProductDistribution( product_id => $self->ProductID,
                                                                    service_id => $serviceID );

        if( $productService ) {
		    $productService->invalidateDistribution();
            $productService->forceStageDistribution();
		}
    }
}

sub takedownDelivery
{
    my $self = shift;
    my @serviceIDs = @_;

    foreach my $serviceID ( @serviceIDs ) {
        my $productService = new RPS::Product::ProductDistribution( product_id => $self->ProductID,
                                                                    service_id => $serviceID );

        $productService->invalidateDistribution();
    }
}


1;
