#---------------------------------------------------------------
# ____                   _ _         ____  _
#|  _ \ ___  _   _  __ _| | |_ _   _/ ___|| |__   __ _ _ __ ___
#| |_) / _ \| | | |/ _` | | __| | | \___ \| '_ \ / _` | '__/ _ \
#|  _ < (_) | |_| | (_| | | |_| |_| |___) | | | | (_| | | |  __/
#|_| \_\___/ \__, |\__,_|_|\__|\__, |____/|_| |_|\__,_|_|  \___|
#            |___/             |___/
#
# Copyright (C) 2010 RoyaltyShare, Inc.   All Rights Reserved
#---------------------------------------------------------------
package RPS::MapFace::Mapper;
use strict;
use warnings;
use Data::Dumper;

use lib '/app/tools/raptor/lib';
use Raptor::DB::Item::File;
use Raptor::DB::Item::Sale;

use lib '/app/tools/rps/lib';
use RPS::DB::Item::MapFaceTempMapping;
use RPS::DB::Item::MapFaceTempMappingHistory;
use RPS::DB::Item::SaleImportError;
use RPS::DB::Item::SaleImportFieldMapping;
use RPS::File::File;
use RPS::File::Sale;
use RPS::Import::SaleRec;
use RPS::Import::Service;

use lib '/app/tools/common/lib';
use Common::Log;
use Common::RSApp;
use Common::Util;
use Common::Email;
use Common::Assert;
use Common::DB::Item::MapFaceMapping;
use Common::DB::Item::MapFaceMappingCriteria;

use lib '/app/tools/data_classes/lib';
use Client::Service;

use lib '/app/tools/appuser/lib';
use AppUser::User::User;

use constant MASS_UPDATE_SALE_ATTR_DEFAULTS => {
    import_status => 0,
    service_id    => 0,
    product_id    => 0,
    map_id        => 0,
    product_type  => '',
    format_type   => '',
    media_type    => '',
    country_code  => '',
};

use constant MASS_UPDATE_SALE_ATTRS => [
    keys %{+MASS_UPDATE_SALE_ATTR_DEFAULTS}
];

use constant MAPFACE_FIELDS_METHODS => {
    serviceID   => '_mapFaceServiceID',
    productType => '_mapFaceProductType',
    formatType  => '_mapFaceFormatType',
    mediaType   => '_mapFaceMediaType',
    countryCode => '_mapFaceCountryCode',
};

use constant MAPFACE_FIELDS_ERRORS => {
    serviceID   => RPS::DB::Item::SaleImportError->kErrorService,
    countryCode => RPS::DB::Item::SaleImportError->kErrorCountry,
    productType => RPS::DB::Item::SaleImportError->kErrorProductType,
    formatType  => RPS::DB::Item::SaleImportError->kErrorFormatType,
    mediaType   => RPS::DB::Item::SaleImportError->kErrorMediaType,
};

sub new {
    my $class = shift;
    my $self = bless {}, $class;

    $self->_init(@_);

    return $self;
}

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

    $self->{_fileID}   = $args{fileID};
    $self->{_clientID} = $args{clientID};

    return $self;
}

sub run {
    my $self = shift;
    my $clientID;

    if ( $self->{_clientID} ) {
        $clientID = $self->{_clientID};
    } else {
        $clientID = Common::RSApp::GetClientID();
    }

    if ( $self->{_fileID} ) {
        Common::Log::Print("Start processing the on-hold file for client_id: $clientID, file_id: $self->{_fileID}");
        $self->_processFile( $self->{_fileID}, $clientID );
    } else {
        # Let's do all of the on-hold files for this client.
        Common::Log::Print("Start processing all on-hold files for client_id: $clientID");
        my $onHoldFiles = Raptor::DB::Item::File->GetAllOnHold();
        while ( $onHoldFiles->hasNext() ) {
            my $onHoldFile = $onHoldFiles->next();
            $self->_processFile( $onHoldFile->file_id, $clientID );
        }
    }
}

sub _processFile {
    my ( $self, $fileID, $clientID ) = @_;

    # Reset process-wide matcher cache.
    $self->resetMatcherCache();

    Common::Log::Print("Processing file for client_id: $clientID, file_id: $fileID");

    Common::Log::Print("Start preparing stash and cache.");
    $self->_prepareStash( $fileID, $clientID );
    $self->_prepareCache( $fileID, $clientID );
    Common::Log::Print("Stash and cache have been prepared.");

    Common::Log::Print("Get the grouped records to be mapped.");
    # get grouped records that have to be mapped (sale_import_error)
    my $aUnmappedGroupList = $self->_getRecordsToMap( $self->_file->FileID, 'ARRAY' );
    $self->_checkForMapping($_) for @$aUnmappedGroupList;

    Common::Log::Print("Check the file status for client_id: $clientID, file_id: $fileID");
    # We need to check the file's status now.
    my $fileStatusNew = Common::DB::Item::MapFaceFile->GetStatusByClientAndFileID( $clientID, $fileID );
    if ( !$fileStatusNew || $fileStatusNew != Common::DB::Item::MapFaceFile::STATUS_ERROR ) {

        # If we've mapped all of the errors for a group, we need to set the status to approved.
        # !!! But only if it has not already been manually approved.
        Common::Log::Print("Get unapproved and unmapped records for the given file_id: $fileID");
        my $mappings = RPS::DB::Item::MapFaceTempMapping->GetUnapprovedByFileID($fileID);
        while ( $mappings->hasNext() ) {
            my $mapping            = $mappings->next();
            my $unmappedErrorCount = RPS::DB::Item::SaleImportError->GetUnmappedCountByMappingID( $mapping->mapface_temp_mapping_id );
            if ( $unmappedErrorCount == 0 ) {
                $mapping->status(RPS::DB::Item::MapFaceTempMapping::STATUS_AUTO_APPROVED);
                $mapping->save();

                # And add the event to this history.
                my $newHistoryAction = RPS::DB::Item::MapFaceTempMappingHistory->Create(
                    mapface_temp_mapping_id => $mapping->mapface_temp_mapping_id,
                    action                  => RPS::DB::Item::MapFaceTempMappingHistory::kActionAutoApproved,
                );
                $newHistoryAction->save();
            }
        }

        Common::Log::Print("Get the remaining errors from the mapface_file table.");
        # Now we need to update the remaining_errors field in the mapface_file table in RSCOMMON.
        my $remainingCount = RPS::DB::Item::MapFaceTempMapping->GetRemainingErrorCountByFileID($fileID);

        Common::Log::Print("Get initial errors (non-auto-approved and approved).");
        # We also want to update the initial error count, in case we auto-approved anything.
        my $initialErrors = RPS::DB::Item::MapFaceTempMapping->GetNonAutoApprovedCountByFileID($fileID);
        my $approvedCount = RPS::DB::Item::MapFaceTempMapping->GetApprovedCountByFileID($fileID);

        my $mapFaceFiles = Common::DB::Item::MapFaceFile->GetByClientAndFileID( $clientID, $fileID );
        while ( $mapFaceFiles->hasNext() ) {
            my $mapFaceFile = $mapFaceFiles->next();
            $mapFaceFile->initial_errors($initialErrors);
            $mapFaceFile->remaining_errors($remainingCount);
            $mapFaceFile->approved_mappings($approvedCount);

            # Note that we may end up changing this status below.
            # But maybe not.  So let's just go with what we know so far.
            if ( $remainingCount == 0 ) {
                $mapFaceFile->status(Common::DB::Item::MapFaceFile::STATUS_REVIEW);
            }

            $mapFaceFile->save();
        }
        Common::Log::Print("The remaining errors in the mapface_file table have been updated.");

        # After we've checked all of the rows, we need to see if we've resolved everything for this file.
        # If so, we'll need to update its status so that it moves to the open files section.
        my $unmappedErrorCount = RPS::DB::Item::SaleImportError->GetUnmappedCountByFileID($fileID);
        Common::Log::Print("Unmapped error count is $unmappedErrorCount");

        if ( $unmappedErrorCount == 0 ) {

            # If this file was previously on hold, let's send out an email to the user who uploaded it
            # and let them know it is in the open files section now.
            if ( $self->_file->FileStatus == File::File::STATUS_ON_HOLD ) {
                _fileProcessedNotification( $self->_file );
            }

            $self->_file->UpdateSummary( no_save => 1 );
            $self->_file->FileStatus(File::File::STATUS_OPEN);
            $self->_file->MD5Sum( Common::Util::md5sum( $self->_file->FileDir . '/' . $self->_file->FileName ) )
              unless $self->_file->MD5Sum;
            $self->_file->Save();

            # Also update the mapface_file entry in RSCOMMON
            my $mapFaceFiles = Common::DB::Item::MapFaceFile->GetByClientAndFileID( $clientID, $fileID );
            while ( $mapFaceFiles->hasNext() ) {
                my $mapFaceFile = $mapFaceFiles->next();
                $mapFaceFile->status(Common::DB::Item::MapFaceFile::STATUS_COMPLETED);
                $mapFaceFile->date_completed( Common::Util::today_and_now() );
                $mapFaceFile->save();
            }
        }
        Common::Log::Print("The file with file_id $fileID has been processed.");
    }
}

sub resetMatcherCache {
    my $self = shift;

    # Reset the global matcher cache. See RPS::File::Sale::_getMatcher().
    my $ramCache = Common::RSApp::GetRAMCache();
    if ( ref($ramCache) eq 'HASH' ) {
        delete $ramCache->{_matcher};
    }

    return 1;
}

sub createJob {
    my $self = shift;
    my %args = @_;

    Common::Log::Debug("Creating new mapper job");
    my $job = $self->_mapperJobObject(%args);

    $job->enqueue();
}

sub _mapperJobObject {
    my $self = shift;
    my %args = @_;

    return RPS::MapFace::Job::Mapper->new( clientID => $args{clientID}, );
}

sub _getRecordsToMap {
    my ( $self, $fileID, $dataType ) = @_;

    return RPS::DB::Item::SaleImportError->GetUnmappedByFileID( $fileID, $dataType );

}

sub _checkForMapping {
    my ( $self, $hUnmappedItem ) = @_;

    Common::Log::Print("Start checking for mappings.");

    my $errorTypes         = $hUnmappedItem->{error_types};
    my $compatibleVersions = $self->_cache->{importer_compatible_versions};

    my $aGroupMappingCriteria = $self->_getMappingCriteriaByErrors($errorTypes);

    # it's enought to get data only for the first sale_id of the group
    my ($sampleSaleID) = split ',', $hUnmappedItem->{sale_ids};
    my $hGroupSaleData = $self->_getItemSaleData( $aGroupMappingCriteria, $sampleSaleID );

    my $hNewMappings = $self->_cache->{prepared_service_mappings};
    unless ( keys %$hNewMappings ) {
        Common::Log::Print("There are no mappings for the compatible versions: $compatibleVersions");
        return;
    }

    # Ensure stable selection order when multiple mappings match a group.
    my @sortedMappings = sort {
        ( $a->{mapface_mapping_id} || 0 ) <=> ( $b->{mapface_mapping_id} || 0 )
    } values %$hNewMappings;

    foreach my $hNewMapping (@sortedMappings) {
        next unless $self->_areFieldsMatched( $aGroupMappingCriteria, $hGroupSaleData, $hNewMapping->{mapface_mapping_id} );

        # Found a match!
        $self->_setMappingValueForSales( $hUnmappedItem, $hNewMapping );

        # run the Matcher
        my $matched = $self->_runMatcher( $hUnmappedItem->{sale_ids} );

        # The first successful mapping is enough.
        last if $matched;
    }

    Common::Log::Print("Finished checking for mappings.");
}

sub _runMatcher {
    my ( $self, $saleIdGroupList ) = @_;
    return unless $saleIdGroupList;

    return if RPS::DB::Item::SaleImportError->GetUnmappedCountBySaleID($saleIdGroupList);

    Common::Log::Print("The matching process has started.");

    my $hMatchedGroups = {};
    foreach my $saleID ( split ',', $saleIdGroupList ) {
        my $oSale = RPS::File::Sale->new( sale_id => $saleID );
        my $oSaleRec = $self->_createSaleRec($oSale);

        $self->_importer->_sanitizeFormatType($oSaleRec);
        $oSale->FormatType( $oSaleRec->formatType );
        $self->_importer->{_rawValuesStored} = 1;
        $self->_importer->_validateSaleRec($oSaleRec);
        $oSale->_findMatch( file => $self->_file );
        $self->_groupMatchedSales( $hMatchedGroups, $oSale );
    }

    Common::Log::Print("Create a sale object for a mass update.");
    # create a fake sale object for mass update
    RPS::File::Sale->new->MassUpdate( $hMatchedGroups, MASS_UPDATE_SALE_ATTRS );

    # something is wrong
    if ( RPS::DB::Item::SaleImportError->GetUnmappedCountBySaleID($saleIdGroupList) ) {
        my $oMapFaceFiles = Common::DB::Item::MapFaceFile->GetByClientAndFileID( $self->_file->ClientID, $self->_file->FileID() );
        while ( $oMapFaceFiles->hasNext() ) {
            my $oMapFaceFile = $oMapFaceFiles->next();
            $oMapFaceFile->status(Common::DB::Item::MapFaceFile::STATUS_ERROR);
            $oMapFaceFile->save();
        }
        Common::Log::Print("[Error] Something went wrong! Unmapped records has been detected.");
        return 0;
    }

    Common::Log::Print("The matching process has completed.");
    return 1;
}

sub _groupMatchedSales {
    my ( $self, $hMatchedGroups, $oSale ) = @_;

    # this method creates groups of sales for mass update
    my $key = join '~~', map {
        ( $oSale->{$_} // MASS_UPDATE_SALE_ATTR_DEFAULTS->{$_} )
    } @{ +MASS_UPDATE_SALE_ATTRS };
    push @{ $hMatchedGroups->{$key} }, $oSale->SaleID();

    return 1;
}

sub _createSaleRec {
    my ( $self, $sale ) = @_;
    my $saleRec = RPS::Import::SaleRec->new();

    $saleRec->price( $sale->Price );
    $saleRec->dateBegin( $sale->DateBegin );
    $saleRec->dateEnd( $sale->DateEnd );
    $saleRec->mediaType( $sale->MediaType );
    $saleRec->productType( $sale->ProductType );
    $saleRec->formatType( $sale->FormatType );
    $saleRec->albumName( $sale->AlbumName );
    $saleRec->artistName( $sale->ArtistName );
    $saleRec->averagePrice( $sale->AveragePrice );
    $saleRec->channel( $sale->Channel );
    $saleRec->clientProductID( $sale->ClientProductID );
    $saleRec->configuration( $sale->Configuration );
    $saleRec->conversionRate( $sale->ConversionRate );
    $saleRec->countryCode( $sale->CountryCode );
    $saleRec->currencyCode( $sale->CurrencyCode );
    $saleRec->free( $sale->Free );
    $saleRec->importStatus( $sale->ImportStatus );
    $saleRec->isrc( $sale->ISRC );
    $saleRec->labelName( $sale->LabelName );
    $saleRec->outlet( $sale->Outlet );
    $saleRec->payoutType( $sale->PayoutType );
    $saleRec->wholesalePrice( $sale->WholesalePrice );
    $saleRec->retailPrice( $sale->RetailPrice );
    $saleRec->priceLevel( $sale->PriceLevel );
    $saleRec->priceType( $sale->PriceType );
    $saleRec->retail( $sale->Retail );
    $saleRec->returns( $sale->Returns );
    $saleRec->returnsRevenue( $sale->ReturnsRevenue );
    $saleRec->sales( $sale->Sales );
    $saleRec->salesRevenue( $sale->SalesRevenue );
    $saleRec->serviceID( $sale->ServiceID );
    $saleRec->serviceProductID( $sale->ServiceProductID );
    $saleRec->totalRevenue( $sale->TotalRevenue );
    $saleRec->trackName( $sale->TrackName );
    $saleRec->trackNum( $sale->TrackNum );
    $saleRec->units( $sale->Units );
    $saleRec->upc( $sale->UPC );
    $saleRec->wholesaleRate( $sale->WholesaleRate );
    $saleRec->mcps_adjustment( $sale->McpsAdjustment );
    $saleRec->comments( $sale->Comments );
    $saleRec->lineNum( $sale->LineNum );

    return $saleRec;
}

sub _fileProcessedNotification {
    my $file = shift;

    my $user = AppUser::User::User->new( userID => $file->UserID );

    if ( $user && $user->Email ) {
        my $to      = $user->Email;
        my $from    = 'do-not-reply@royaltyshare.com';
        my $subject = "File Successfully Processed";
        my $url =
            "https://"
          . Common::RSApp::GetClientVHost()
          . ".royaltyshare.com/app/tracker?Highlight="
          . $file->FileID . "#file"
          . $file->FileID;
        my $fileName = $file->OrigFileName;
        my $body     = "The file $fileName that previously encountered an error has now been successfully processed.

Click here to go to the Revenue tab on your RoyaltyShare website to view the file:  $url

Thank you,

RoyaltyShare Support";

        Common::Email->SendAWS(
            to      => $to,
            from    => $from,
            subject => $subject,
            body    => $body
        );
    }
}

sub _getMappingCriteriaByErrors {
    my ( $self, $errorTypes ) = @_;

    my $hCriteria = $self->_cache->{importer_mapping_criteria};

    my %groupedCriteria;
    foreach my $errorType ( split ",", $errorTypes ) {
        unless ( exists $hCriteria->{$errorType} ) {
            die "Unknown error type '$errorType'";
        }

        my $aFields = $hCriteria->{$errorType};
        foreach my $field (@$aFields) {
            ($field) = %$field if ref $field eq 'HASH';
            $groupedCriteria{$field} = undef;
        }
    }

    return [ keys %groupedCriteria ];
}

sub _getItemSaleData {
    my ( $self, $aItemErrorCriteria, $saleID ) = @_;

    my $hSaleImportErrors = $self->_cache->{sale_import_errors_raw}{$saleID};

    my %data;
    @data{@$aItemErrorCriteria} = @$hSaleImportErrors{@$aItemErrorCriteria};
    return \%data;
}

sub _areFieldsMatched {
    my ( $self, $aGroupMappingCriteria, $hGroupSaleData, $mapFaceMappingID ) = @_;

    my $matched                = 0;
    my $hCommonMappingCriteria = $self->_cache->{common_mapping_criteria};
    return $matched unless exists $hCommonMappingCriteria->{$mapFaceMappingID};

    my $hCriteriaMappingID = $hCommonMappingCriteria->{$mapFaceMappingID};

    while ( my ( $field, $value ) = each %$hGroupSaleData ) {
        next unless exists $hCriteriaMappingID->{$field} && defined $value;
        $matched++ if $hCriteriaMappingID->{$field} eq $value;
    }

    return @$aGroupMappingCriteria == $matched ? 1 : 0;
}

sub _setMappingValueForSales {
    my ( $self, $hUnmappedItem, $hMapping ) = @_;

    Common::Log::Print("Match has been found.");

    my @saleFields = qw/service_id country_code product_type format_type media_type/;

    my $hFieldsToUpdate = {};
    foreach my $field (@saleFields) {
        my $mappedField = 'mapped_' . $field;
        next unless exists $hMapping->{$mappedField} && $hMapping->{$mappedField};
        $hFieldsToUpdate->{$field} = $hMapping->{$mappedField};
    }

    Common::Log::Print("Update the mapped sale records in the related tables.");
    if ( exists $hFieldsToUpdate->{service_id} ) {

        # We also need to make sure that this service is in the client's service table.
        # We'll just try to add it, and it's smart enough to skip it if it's already there.
        Client::Service::AddService(
            client_id  => $self->_file->ClientID,
            service_id => $hMapping->{mapped_service_id}
        );
    }

    # update the 'sale' table
    Raptor::DB::Item::Sale->UpdateMappedSalesIDs( $hFieldsToUpdate, $hUnmappedItem->{sale_ids} );

    # clean the 'mapface_temp_mapping' table
    %$hFieldsToUpdate = map { $_ => undef } keys %$hFieldsToUpdate;
    RPS::DB::Item::MapFaceTempMapping->UpdateByMappingIDs( $hFieldsToUpdate, $hUnmappedItem->{mapface_temp_mapping_id} );

    # update the item in the 'sale_import_error' table as mapped = 'Y'
    RPS::DB::Item::SaleImportError->UpdateUnmappedByMappingIDs( { mapped => 'Y' }, $hUnmappedItem->{mapface_temp_mapping_id} );

    Common::Log::Print("Match has been confirmed and saved.");

    return 1;
}

## prepare stash and cache
sub _prepareStash {
    my ( $self, $fileID, $clientID ) = @_;

    Common::Log::Print("[Stash] Start preparing stash for client_id: $clientID, file_id: $fileID");

    my $oFile = RPS::File::File->new(
        file_id   => $fileID,
        client_id => $clientID
    );

    my $oImporter = RPS::Import::Service->new(
        service_id  => $oFile->ServiceID,
        version_num => $oFile->VersionNum
    );
    $oImporter->{_file} = $oFile;

    # fill the stash
    $self->{_stash}{oImporter} = $oImporter;

    return 1;
}

sub _stash    { return shift->{_stash} }
sub _importer { return shift->{_stash}{oImporter} }
sub _file     { return shift->{_stash}{oImporter}{_file} }
sub _cache    { return shift->{_cache} }

sub _prepareCache {
    my ( $self, $fileID, $clientID ) = @_;

    Common::Log::Print("[Cache] Start preparing cache for client_id: $clientID, file_id: $fileID");

    # all available mapping criteria for the importer (C_RSTEST.sale_import_field_mapping)
    my $hMappingCriteria = $self->_getImporterMappingCriteriaList();

    # a string with all compatible versions for mapFace (inner data)
    my $compatibleVersions = join ',', $self->_importer->_mapFaceCompatibleVersions, $self->_file->VersionNum;
    Common::Log::Print("[Cache] Extract compatible versions: $compatibleVersions");

    # all stored values with error (sale_import_error_raw_value)
    my $hSaleImportErrorRowList = $self->_getSaleImportErrorRawList();

    # list of prepared mappings for the service (RSCOMMON.mapface_mapping)
    my $hPreparedMappings = $self->_getMappingsList($compatibleVersions);

    # list of common mapface mapping criteria (RSCOMMON.mapface_mapping_criteria)
    my $hCommonMappingCriteria = $self->_getCommonMappingCriteriaList($hPreparedMappings);

    Common::Log::Print("[Cache] Building the cache object.");
    $self->{_cache} = {
        importer_mapping_criteria    => $hMappingCriteria,
        importer_compatible_versions => $compatibleVersions,
        sale_import_errors_raw       => $hSaleImportErrorRowList,
        prepared_service_mappings    => $hPreparedMappings,
        common_mapping_criteria      => $hCommonMappingCriteria,
    };

    return 1;
}

# for cache
sub _getImporterMappingCriteriaList {
    my $self = shift;
    my $hMap = {};

    if ( my $fileID = $self->_file->FileID ) {
        Common::Log::Print("[Cache] Get existing sale import file mappings for file_id: $fileID");
        my $aData = RPS::DB::Item::SaleImportFieldMapping->GetAllByFileID( $fileID, 'ARRAY' );

        # Filling the hash with the default mappings if unable to fetch from the database (sale_import_field_mapping)
        unless (@$aData) {
            foreach my $field (keys %{+MAPFACE_FIELDS_ERRORS}) {
                my $method = MAPFACE_FIELDS_METHODS->{$field};
                my $mapping = $self->_importer->$method();
                if ($mapping) {  # Only if the method returns a value
                    my $errorType = MAPFACE_FIELDS_ERRORS->{$field};
                    $hMap->{$errorType} = [$mapping];
                }
            }
            return $hMap;
        }

        Common::Log::Print("[Cache] Populate the mapping errors hash by fields for the given file_id: $fileID");
        foreach my $hData ( @$aData ) {
            my ( $field, $mapping ) = @$hData{qw/field mapping/};
            next unless exists MAPFACE_FIELDS_ERRORS->{$field};

            my $errorType = MAPFACE_FIELDS_ERRORS->{$field};
            push @{ $hMap->{$errorType} }, $mapping;
        }
    }

    return $hMap;
}

sub _getSaleImportErrorRawList {
    my $self = shift;

    Common::Log::Print("[Cache] Get sales import raw error values.");

    my %data;
    my $aData = RPS::DB::Item::SaleImportErrorRawValue->GetAllByFileID( $self->_file->FileID, 'ARRAY' );
    $data{ $_->{sale_id} }{ $_->{field} } = $_->{value} for @$aData;

    return \%data;
}

sub _getMappingsList {
    my ( $self, $compatibleVersions ) = @_;

    my $serviceID = $self->_file->ServiceID;
    Common::Log::Print("[Cache] Get prepared mapping list by service_id: $serviceID and compatible versions.");

    my $aMappings = Common::DB::Item::MapFaceMapping->GetByServiceAndVersion( $serviceID, $compatibleVersions, 'ARRAY' );
    my %mappings = map { $_->{mapface_mapping_id} => $_ } @$aMappings;

    return \%mappings;
}

sub _getCommonMappingCriteriaList {
    my ( $self, $hPreparedMappings ) = @_;

    my %data;
    return \%data unless keys %$hPreparedMappings;

    Common::Log::Print("[Cache] Get common mapping criteria list.");

    my $mapFaceMappingIDs = join ',', keys %$hPreparedMappings;
    my $aData = Common::DB::Item::MapFaceMappingCriteria->GetByMappings( \$mapFaceMappingIDs );

    $data{ $_->{mapface_mapping_id} }{ $_->{field} } = $_->{value} for @$aData;

    return \%data;
}

1;
