#------------------------------------------------------------
# Copyright (C) 2007 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------

#------------------------------------------------------------
# Validation Harness.  Main controller class for import
# validation.
#
# Validation process has three stages:
#   1. Contraint validation (row validation)
#   2. Group validation (album validation)
#   3. Database validation (current data validation)
#
# Constraint Validation:
#   Constraint validation operates on a single row and one
#   column at a time.  For intance a constraint test could be
#   if a field is allowed to be null.  All of these test are
#   defined in the kConstraintDefinitions constant.
#
# Group Validation:
#   Group validation operates on the import dataset, but
#   covers multiple rows.  An example of a group validation
#   is ensuring the albumn name is constant for each entry
#   for a disc.  These validation rules could be used to
#   ensure normilization integrity.
#
# Database Validation:
#   Database validation operates on the import dataset and
#   current data that is already imported.  For instance,
#   we could use this validation to ensure that a CD hasn't
#   already been imported.
#
# Public methods:
#   + Validate::Rule::Code new();
#   + void                 validate();
#------------------------------------------------------------
package Metadata::Validate::Harness;
use strict;

use lib '/app/tools/common/lib';
use Common::Log;
use Common::RSApp;
use Common::Assert;
use Data::Dumper;

use lib '/app/tools/metadata/lib';
use Metadata::Parser;
use Metadata::Validate::Constraint;
use Metadata::Validate::Album;
use Metadata::Validate::CurrentData;
use Metadata::DB::Item::ContentImportData;
use Metadata::DB::Item::ContentImportDataStatus;

use Validate;
use Validate::Item;
use Validate::Util;

###
#   Validation Definitions
#
#   This hash defines the rule configuration for validations.  It is used by
#   _initializeConstraints, _initializeAlbums, and _initializeCurrentData.
#
#   Each element defines a default failure status
#   and confiugration information for each rule in a constraint.  Constraint
#   references can either be a hash reference or something else.  If the
#   parameter is a hash reference the we are we pass extended options to the
#   rule, otherwise the parameter is passed to the rule as a parameter.
#
#   Extended options are used if you want to overload the default values for
#   the fail_status or the error message.  The hash MUST have at least a
#   parameter key/value pair.  i.e.
#
#      {
#        parameter => 1,
#        message   => "This failed for some reason",
#        fail_status => Validate::kStatusWarn
#      }
#
#   Currently there are 4 rules setup for the metadata import validation:
#
#   Validate::Rule::Nullable  - Can the field be null?
#   Validate::Rule::Code      - Setup a custom validation function
#   Validate::Rule::Regex     - Does the field match the regular expression?
#   Validate::Rule::Exclusive - If this field is not null then all other
#                               exclusive fields must be null
#
#   ** For more information on these rules see the rules source code.  **
#
#   ** To disable all constraints for a field either remove the field from the
#      constant definitions or leave it there with an empty hash reference. **
#
#   Examples:
#
#     'album-name' => {
#         fail_status                 => Validate::kStatusError,
#         'Validate::Rule::Nullable'  => 0,
#         'Validate::Rule::Code'      => {
#                                            parameter   => sub { },
#                                            message     => "This failed",
#                                            fail_status => kStatusWarn
#                                        },
#         'Validate::Rule::Regex'     => '^.*$',
#         'Validate::Rule::Exclusive' => [ 'album1-artist', 'artist2' ],
#         'Validate::Rule::Required'  => 'digital-upc:album-upc',
#         'Validate::Rule::Length'    => [ min => 1, max => 5 ],
#         'Validate::Rule::Between'   => [ min => 1, max => 5 ],
#         'Validate::Rule::List'      => [ 'one', 'two', 'three' ]
#     },
#
#     'artist-favorite-color' => {}
#
#  In the above example album name defines constraints for the albumn name,
#  but turns off all constraints for the 'artist-favorite-color'.
#
#  Constraint Validation:
#
#  Album Validation:
#
#  Current Data Validation:
#
###

use constant kImportDefinitions => (
    'album-title' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 0,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "album title must be the same for the entire album"
            }
        },

        current_data => {
            fail_status                          => Validate::kStatusDuplicate,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusDuplicate,
                destination_column => 'title',
                destination_table  => 'album'
            }
        },
    },

    'album-artist' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 0,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "album artist must be the same for the entire album"
            }
        },

        current_data => {},
    },

    'additional-album-artists' => {
        constraint => {
            fail_status                                         => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable'              => 1,
            'Validate::Rule::Constraint::AdditionalArtistsList' => 1,
            'Validate::Rule::Constraint::UniqueKeyValuePairs'   => 1,
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "additional album artists must be the same for the entire album"
            }
        },

        current_data => {},
    },

    'price-tiers' => {
        constraint => {
            fail_status                                  => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable'       => 1,
            'Validate::Rule::Constraint::PriceTiersList' => 1,
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "price tiers must be the same for the entire album"
            }
        },

        current_data => {},
    },

    'label-name' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 0,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "label name must be the same for the entire album"
            }
        },

        current_data => {}
    },

    'catalog-no' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 0,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 32 ],
                message   => "max length 32 characters"
            }
        },

        album => {},

        current_data => {
            fail_status                          => Validate::kStatusDuplicateError,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusDuplicateError,
                destination_column => 'catalog_number',
                destination_table  => 'album'
            }
        },
    },

    'client-album-id' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {
            fail_status                             => Validate::kStatusError,
            'Metadata::Rule::Album::Common'         => undef,
            'Metadata::Rule::Album::UniqueClientID' => {
                fail_status => Validate::kStatusDuplicate
            }
        },

        current_data => {
            fail_status                          => Validate::kStatusDuplicate,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusWarn,
                destination_column => 'client_album_id',
                destination_table  => 'album'
            }
        },
    },

    'digital-product-id' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['digital-upc'],
                succeed_on_null => 1,
                message         => "Digital UPC Required for digital product id"
            },
        },

        current_data => {},

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "digital product id must be the same for the entire album"
            }
        },
    },

    'cd-product-id' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['cd-upc'],
                succeed_on_null => 1,
                message         => "CD UPC Required for CD product id"
            },
        },
        current_data => {},

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "cd product id must be the same for the entire album"
            }
        },
    },

    'vinyl-product-id' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['vinyl-upc'],
                succeed_on_null => 1,
                message         => "Digital UPC Required for digital product id"
            },
        },
        current_data => {},

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "vinyl product id must be the same for the entire album"
            }
        },
    },

    'dvd-product-id' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['dvd-upc'],
                succeed_on_null => 1,
                message         => "Digital UPC Required for digital product id"
            },
        },
        current_data => {},

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "dvd product id must be the same for the entire album"
            }
        },
    },

    'cd-upc' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Metadata::Rule::Constraint::Required' => {
                parameter => [ 'cd-upc', 'digital-upc', 'vinyl-upc', 'dvd-upc' ],
                message   => "Must specify at least one UPC"
            },
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 25 ],
                message   => "max length 25 characters"
            }
        },

        album => {

            #         	   fail_status => Validate::kStatusError,
            #               'Metadata::Rule::Album::UPCUnique' => undef,
        },

        current_data => {
            fail_status                          => Validate::kStatusDuplicate,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusDuplicate,
                destination_column => 'upc_ean',
                destination_table  => 'product'
            }
        },
    },

    'digital-upc' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Metadata::Rule::Constraint::Required' => {
                parameter => [ 'cd-upc', 'digital-upc', 'vinyl-upc', 'dvd-upc' ],
                message   => "Must specify at least one UPC"
            },
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 25 ],
                message   => "max length 25 characters"
            }
        },

        album => {

            #         	   fail_status => Validate::kStatusError,
            #               'Metadata::Rule::Album::UPCUnique' => undef,
        },

        current_data => {
            fail_status                          => Validate::kStatusDuplicate,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusDuplicate,
                destination_column => 'upc_ean',
                destination_table  => 'product'
            }
        },
    },

    'vinyl-upc' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Metadata::Rule::Constraint::Required' => {
                parameter => [ 'cd-upc', 'digital-upc', 'vinyl-upc', 'dvd-upc' ],
                message   => "Must specify at least one UPC"
            },
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 25 ],
                message   => "max length 25 characters"
            }
        },

        album => {

            #         	   fail_status => Validate::kStatusError,
            #               'Metadata::Rule::Album::UPCUnique' => undef,
        },

        current_data => {
            fail_status                          => Validate::kStatusDuplicate,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusDuplicate,
                destination_column => 'upc_ean',
                destination_table  => 'product'
            }
        },
    },

    'dvd-upc' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Metadata::Rule::Constraint::Required' => {
                parameter => [ 'cd-upc', 'digital-upc', 'vinyl-upc', 'dvd-upc' ],
                message   => "Must specify at least one UPC"
            },
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 25 ],
                message   => "max length 25 characters"
            }
        },

        album => {

            #         	   fail_status => Validate::kStatusError,
            #               'Metadata::Rule::Album::UPCUnique' => undef,
        },

        current_data => {
            fail_status                          => Validate::kStatusDuplicate,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusDuplicate,
                destination_column => 'upc_ean',
                destination_table  => 'product'
            }
        },
    },

    'digital-release-date' => {
        constraint => {
            fail_status                            => Validate::kStatusWarn,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Date'     => {
                parameter   => "YYYY-MM-DD",
                fail_status => Validate::kStatusError,
                message     => "Invalid date format. Must be YYYY-MM-DD"
            },
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['digital-upc'],
                succeed_on_null => 1,
                message         => "Digital UPC Required for digital release date"
            },
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                required_field => 'digital_upc'
            }
        },

        current_data => {},
    },

    'cd-release-date' => {
        constraint => {
            fail_status                            => Validate::kStatusWarn,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Date'     => {
                parameter   => "YYYY-MM-DD",
                fail_status => Validate::kStatusError,
                message     => "Invalid date format. Must be YYYY-MM-DD"
            },
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['cd-upc'],
                succeed_on_null => 1,
                message         => "CD UPC Required for cd release date"
            },
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                required_field => 'cd_upc'
            }
        },

        current_data => {},
    },

    'vinyl-release-date' => {
        constraint => {
            fail_status                            => Validate::kStatusWarn,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Date'     => {
                parameter   => "YYYY-MM-DD",
                fail_status => Validate::kStatusError,
                message     => "Invalid date format. Must be YYYY-MM-DD"
            },
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['vinyl-upc'],
                succeed_on_null => 1,
                message         => "Vinyl UPC Required for vinyl release date"
            },
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                required_field => 'vinyl_upc'
            }
        },

        current_data => {},
    },

    'dvd-release-date' => {
        constraint => {
            fail_status                            => Validate::kStatusWarn,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Date'     => {
                parameter   => "YYYY-MM-DD",
                fail_status => Validate::kStatusError,
                message     => "Invalid date format. Must be YYYY-MM-DD"
            },
            'Metadata::Rule::Constraint::Required' => {
                fail_status     => Validate::kStatusError,
                parameter       => ['dvd-upc'],
                succeed_on_null => 1,
                message         => "DVD UPC Required for dvd release date"
            },
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                required_field => 'dvd_upc'
            }
        },

        current_data => {},
    },

    'primary-genre' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 48 ],
                message   => "max length 48 characters"
            },
            'Validate::Rule::Constraint::Code' => {
                parameter   => \&Validate::Util::is_valid_genre,
                message     => "Invalid genre",
                fail_status => Validate::kStatusError,
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'secondary-genre' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 48 ],
                message   => "max length 48 characters"
            },
            'Validate::Rule::Constraint::Code' => {
                parameter => \&Validate::Util::is_valid_genre,
                message   => "Invalid genre"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'c-line' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            },
            'Validate::Rule::Constraint::Nullable' => {
                parameter   => 0,
                fail_status => Validate::kStatusWarn,
                message     => "needed for distribution"
            },
            'Validate::Rule::Constraint::Regex' => {
                parameter => '^\d{4}\s+.+',
                message   => "format incorrect, must be 'YYYY rightsholder name'"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'p-line' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => {
                parameter   => 0,
                fail_status => Validate::kStatusWarn,
                message     => "needed for distribution"
            },
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            },
            'Validate::Rule::Constraint::Regex' => {
                parameter => '^\d{4}\s+.+',
                message   => "format incorrect, must be 'YYYY rightsholder name'"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'territories-allowed' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            },
            'Validate::Rule::Constraint::Territories' => {
                parameter => \&Validate::Util::is_valid_territory_list,
                message   => "Invalid territory"
            },
            'Metadata::Rule::Constraint::Exclusive' => {
                parameter => [ 'territories-denied', 'territories-allowed' ],
                message   => "please use either territories allowed OR territories denied"
            },
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "territories allowed must be the same for all tracks in an album"
            }
        },

        current_data => {},
    },

    'territories-denied' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            },
            'Validate::Rule::Constraint::Territories' => {
                parameter => \&Validate::Util::is_valid_territory_list,
                message   => "Invalid territory"
            },
            'Metadata::Rule::Constraint::Exclusive' => {
                parameter => [ 'territories-denied', 'territories-allowed' ],
                message   => "please use either territories allowed OR territories denied"
            },
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => {
                message => "territories denied must be the same for all tracks in an album"
            }
        },

        current_data => {},
    },

    'album-custom-1' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'album-custom-2' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'album-custom-3' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'cleared-for-distribution' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::List'     => {
                parameter => [ 'Y', 'y', 'N', 'n' ],
                message   => "must be 'Y' or 'N'"
            }
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Common' => undef
        },

        current_data => {},
    },

    'album-purchase-only' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::List'     => {
                parameter => [ 'Y', 'y', 'N', 'n' ],
                message   => "must be 'Y' or 'N'"
            }
        },

        album => {},

        current_data => {},
    },

    'track-title' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 0,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {},

        current_data => {},
    },

    'digital-disc-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 999 ],
                message   => "must be between 1 and 999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['digital-upc'],
                succeed_on_null => 1,
                message         => "Digital UPC Required for digital release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                allow_duplicates => 1,
                minimum          => 1,
            }
        },

        current_data => {},
    },

    'cd-disc-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 999 ],
                message   => "must be between 1 and 999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['cd-upc'],
                succeed_on_null => 1,
                message         => "CD UPC Required for cd release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                allow_duplicates => 1,
                minimum          => 1,
            }
        },

        current_data => {},
    },

    'vinyl-disc-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 999 ],
                message   => "must be between 1 and 999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['vinyl-upc'],
                succeed_on_null => 1,
                message         => "Vinyl UPC Required for vinyl release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                allow_duplicates => 1,
                minimum          => 1
            }
        },

        current_data => {},
    },

    'dvd-disc-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 999 ],
                message   => "must be between 1 and 999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['dvd-upc'],
                succeed_on_null => 1,
                message         => "DVD UPC Required for dvd release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                allow_duplicates => 1,
                minimum          => 1
            }
        },

        current_data => {},
    },

    'digital-track-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 9999 ],
                message   => "must be between 1 and 9999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['digital-upc'],
                succeed_on_null => 1,
                message         => "Digital UPC Required for digital release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                minimum          => 1,
                additional_field => "digital_disc_number"
            }
        },

        current_data => {},
    },

    'cd-track-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 9999 ],
                message   => "must be between 1 and 9999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['cd-upc'],
                succeed_on_null => 1,
                message         => "CD UPC Required for cd release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                minimum          => 1,
                additional_field => "cd_disc_number"
            }
        },

        current_data => {},
    },

    'vinyl-track-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 9999 ],
                message   => "must be between 1 and 9999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['vinyl-upc'],
                succeed_on_null => 1,
                message         => "Vinyl UPC Required for vinyl release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                minimum          => 1,
                additional_field => "vinyl_disc_number"
            }
        },

        current_data => {},
    },

    'dvd-track-number' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::Integer'  => 1,
            'Validate::Rule::Constraint::Between'  => {
                parameter => [ min => 1, max => 9999 ],
                message   => "must be between 1 and 9999"
            },
            'Metadata::Rule::Constraint::Required' => {
                parameter       => ['dvd-upc'],
                succeed_on_null => 1,
                message         => "DVD UPC Required for dvd release date"
            },
        },

        album => {
            fail_status                         => Validate::kStatusError,
            'Metadata::Rule::Album::Sequential' => {
                minimum          => 1,
                additional_field => "dvd_disc_number"
            }
        },

        current_data => {},
    },

    'track-artist' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 0,
            'Validate::Rule::Constraint::Length'   => {
                parameter => [ min => 1, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {},

        current_data => {},
    },

    'additional-track-artists' => {
        constraint => {
            fail_status                                         => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable'              => 1,
            'Validate::Rule::Constraint::AdditionalArtistsList' => 1,
            'Validate::Rule::Constraint::UniqueKeyValuePairs'   => 1,
        },

        album => {},

        current_data => {},
    },

    'track-composer' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            },
            'Validate::Rule::Constraint::Code' => {
                fail_status => Validate::kStatusWarn,
                parameter   => \&Validate::Util::is_valid_composer,
                message     => "needed for distribution for this genre"
            }
        },

        album => {},

        current_data => {},
    },

    'track-minutes' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => {
                parameter => 1,
                message   => "minutes field is blank"
            },
            'Validate::Rule::Constraint::Integer' => {
                parameter => 1,
                message   => "minutes field is not a number"
            },
            'Validate::Rule::Constraint::Between' => {
                parameter => [ min => 0, max => 999 ],
                message   => "minutes field must be between 0 and 999"
            }
        },

        album => {},

        current_data => {},
    },

    'track-seconds' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => {
                parameter => 1,
                message   => "seconds field is blank"
            },
            'Validate::Rule::Constraint::Integer' => {
                parameter => 1,
                message   => "seconds field is not a number"
            },
            'Validate::Rule::Constraint::Between' => {
                parameter => [ min => 0, max => 59 ],
                message   => "seconds field must be between 0 and 59"
            }
        },

        album => {},

        current_data => {},
    },

    'explicit' => {
        constraint => {
            fail_status                        => Validate::kStatusError,
            'Validate::Rule::Constraint::List' => {
                parameter => [ 'E', 'C', 'N', 'e', 'c', 'n' ],
                message   => "invalid parental advisory, must be C, E, or N"
            }
        },

        album => {},

        current_data => {},
    },

    'client-track-id' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            },
        },

        album => {
            fail_status                     => Validate::kStatusError,
            'Metadata::Rule::Album::Unique' => {
                per_album => 1
            }
        },

        current_data => {
            fail_status                          => Validate::kStatusDuplicate,
            'Metadata::Rule::CurrentData::Album' => {
                fail_status        => Validate::kStatusDuplicateError,
                destination_column => 'client_track_id',
                destination_table  => 'track'
            }
        },
    },

    'isrc' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => {
                parameter   => 0,
                fail_status => Validate::kStatusWarn,
                message     => 'required for distribution'
            },
            'Validate::Rule::Constraint::ISRC' => 0,

            #'Validate::Rule::Constraint::ISRCRequired'  => 0,
        },

        album => {
            fail_status                     => Validate::kStatusDuplicateError,
            'Metadata::Rule::Album::Unique' => {
                message   => "All ISRCs must be unique within this album",
                per_album => 1
            }
        },

        current_data => {},

        #    	current_data => {
        #                            fail_status    => Validate::kStatusDuplicate,
        #                            'Metadata::Rule::CurrentData::Track' => {
        #                                fail_status        => Validate::kStatusDuplicate,
        #                                destination_column => 'isrc',
        #                                destination_table  => 'master'
        #                            }
        #        },
    },

    'media-type' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::List'     => {
                parameter => [ '1', '2', 'a', 'A', 'v', 'V' ],
                message   => "must be '1' for audio or '2' for video"
            }
        },

        album => {},

        current_data => {},
    },

    'exclude-digitally' => {
        constraint => {
            fail_status                            => Validate::kStatusError,
            'Validate::Rule::Constraint::Nullable' => 1,
            'Validate::Rule::Constraint::List'     => {
                parameter => [ 'Y', 'y', 'N', 'n' ],
                message   => "must be 'Y' or 'N'"
            }
        },

        album => {},

        current_data => {},
    },

    'track-custom-1' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {},

        current_data => {},
    },

    'track-custom-2' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {},

        current_data => {},
    },

    'track-custom-3' => {
        constraint => {
            fail_status                          => Validate::kStatusError,
            'Validate::Rule::Constraint::Length' => {
                parameter => [ min => 0, max => 255 ],
                message   => "max length 255 characters"
            }
        },

        album => {},

        current_data => {},
    },
);

sub new {
    my ( $class, %args ) = @_;
    my $self = bless {}, $class;

    return $self->_init(%args);
}

sub validate_item {
    my ( $self, $rule, $target, $ignore_level, @args ) = @_;
    my $result;
    $ignore_level = $self->{_failState};

    assert($rule);
    $rule =~ s/-/_/g;

    Common::Log::Debug(
        sprintf( "Validating item: Key: %s Value: %s Args: ", defined($rule) ? $rule : "<undef>", defined($target) ? $target : "<undef>" )
    );

    $result = $self->validate_constraint( $rule, $target, @args );

    unless ( $self->ignore_current_data ) {
        $result = $self->validate_current_data( $rule, $target, @args )
          if ( !$result || $result->is_valid() );
    }

    if ( $result && !$result->is_valid() && $result->error_code() > $ignore_level ) {
        return { code => $result->error_code(), message => $result->error_as_string() };
    }

    return undef;
}

sub validate_constraint {
    my ( $self, $rule, $target, %args ) = @_;
    my $result = $self->{_constraints}->{$rule}->validate( $target, %args )
      if ( $self->{_constraints}->{$rule} );

    return $result;
}

sub validate_current_data {
    my ( $self, $rule, $target ) = @_;
    my $result = $self->{_current_data}->{$rule}->validate($target)
      if ( $self->{_current_data}->{$rule} );

    return $result;
}

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

    # Clear out the DB first
    my $dbo = Common::RSApp::GetClientDB();

    $dbo->DoCmd("DELETE FROM content_import_data_status");

    $self->_validate_constraints();
    $self->_validate_album();
    $self->_validate_current_data();
}

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

    Common::Log::Debug("Initializing Metadata::Validator Object");

    $self->{_failState}         = defined( $args{failState} ) ? $args{failState} : Validate::kStatusWarn;
    $self->{_ignoreCurrentData} = $args{ignoreCurrentData};
    $self->{_automated}         = $args{automated};

    $self->_initializeConstraintValidation();
    $self->_initializeAlbumValidation();
    $self->_initializeCurrentDataValidation();

    return $self;
}

sub ignore_current_data() {
    my $self = shift;
    return $self->{_ignoreCurrentData};
}

sub _validate_constraints {
    my ( $self, %args ) = @_;
    my $constraint;
    my $column;
    my $contentImport;
    my $row;
    my $contentImportCollection = Metadata::DB::Item::ContentImportData->GetAll();
    my $result;
    my $contentImportDataDBObj;

    # Iterate through each row
    while ( $contentImportCollection->hasNext() ) {
        $contentImport = $contentImportCollection->next();
        $row           = $contentImport->GetAsHashref();

        # Iterate through each column
        foreach $column ( keys(%$row) ) {
            my @row = keys( %{ $self->{_constraints} } );

            if ( $self->{_constraints}->{$column} ) {
                $result = $self->{_constraints}->{$column}->validate( $row->{$column}, %$row );

                unless ( $result->is_valid() ) {
                    Common::Log::Debug(
                        sprintf(
                            "Row: %s, Field: %s, Value: %s, Code: %s String: %s",
                            $row->{line_number}      ? $row->{line_number}      : "<undef>",
                            $column                  ? $column                  : "<undef>",
                            $row->{$column}          ? $row->{$column}          : "<undef>",
                            $result->error_code      ? $result->error_code      : "<undef>",
                            $result->error_as_string ? $result->error_as_string : "<undef>"
                        )
                    );

                    # Store import record
                    $contentImportDataDBObj = Metadata::DB::Item::ContentImportDataStatus->Create();

                    $contentImportDataDBObj->status( $result->error_code );
                    $contentImportDataDBObj->message( $result->error_as_string );
                    $contentImportDataDBObj->ref_column($column);
                    $contentImportDataDBObj->type('track');
                    $contentImportDataDBObj->content_import_data_id( $row->{content_import_data_id} );

                    $contentImportDataDBObj->save();
                }

                $contentImport->validated(2);
                $contentImport->save();
            }
        }
    }
}

sub _validate_album {
    my ( $self, %args ) = @_;
    my $row;
    my $column;
    my $result;
    my $contentImportDataDBObj;

    my $dbo = Common::RSApp::GetClientDB();

    my $sql =
        "SELECT MIN(content_import_data_id) as id, "
      . "catalog_no, line_number "
      . "FROM content_import_data "
      . "GROUP BY "
      . Metadata::Validate::Album::kGroupField;

    my $sth = $dbo->DoCmd($sql);

    while ( $row = $sth->fetchrow_hashref() ) {

        # Iterate through each column
        foreach $column ( keys( %{ $self->{_album} } ) ) {
            if ( $self->{_album}->{$column} ) {
                $result = $self->{_album}->{$column}->validate( $row->{catalog_no}, %$row );

                unless ( $result->is_valid() ) {
                    Common::Log::Debug(
                        sprintf(
                            "Row: %s, Field: %s, Value: %s, Code: %s String: %s",
                            $row->{line_number}      ? $row->{line_number}      : "<undef>",
                            $column                  ? $column                  : "<undef>",
                            $row->{catalog_no}       ? $row->{catalog_no}       : "<undef>",
                            $result->error_code      ? $result->error_code      : "<undef>",
                            $result->error_as_string ? $result->error_as_string : "<undef>"
                        )
                    );

                    # Store import record
                    $contentImportDataDBObj = Metadata::DB::Item::ContentImportDataStatus->Create();

                    $contentImportDataDBObj->status( $result->error_code );
                    $contentImportDataDBObj->message( $result->error_as_string );
                    $contentImportDataDBObj->ref_column($column);
                    $contentImportDataDBObj->type('album');
                    $contentImportDataDBObj->content_import_data_id( $row->{id} );

                    $contentImportDataDBObj->save();
                }

            }
        }

        Metadata::DB::Item::ContentImportData->UpdateValidationStatus(
            status     => 3,
            catalog_no => $row->{catalog_no}
        ) if ( $row->{catalog_no} );
    }
}

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

    my $dbo = Common::RSApp::GetClientDB();

    my $sql;
    my $album_search;
    my $unifying_field = Metadata::Validate::CurrentData::kGroupField;
    my $column;
    my $validatorObj;
    my $sth;
    my $row;
    my $result;
    my $contentImportDataDBObj;

    # TODO: Change the looping structure to loop through each row before each
    # Column so the progress can be monitored a little more efficently.
    foreach $column ( keys( %{ $self->{_current_data} } ) ) {

        $validatorObj = $self->{_current_data}->{$column};
        $album_search = $validatorObj->is_album();

        $sql = sprintf(
            "SELECT %s(content_import_data_id) as id, "
              . "%s as unifying_field, %s as target, "
              . "catalog_no, "
              . "line_number "
              . "FROM content_import_data %s",
            $album_search ? "MIN" : "",
            $unifying_field,
            $validatorObj->source_column(),
            $album_search ? "GROUP BY $unifying_field" : ""
        );

        $sth = $dbo->DoCmd($sql);

        while ( $row = $sth->fetchrow_hashref() ) {
            $result = $validatorObj->validate( $row->{target} );

            unless ( $result->is_valid() ) {
                Common::Log::Debug(
                    sprintf(
                        "Row: %s, Field: %s, Value: %s, Code: %s String: %s",
                        $row->{line_number}      ? $row->{line_number}      : "<undef",
                        $column                  ? $column                  : "<undef>",
                        $row->{target}           ? $row->{target}           : "<undef>",
                        $result->error_code      ? $result->error_code      : "<undef>",
                        $result->error_as_string ? $result->error_as_string : "<undef>"
                    )
                );

                # Store import record
                $contentImportDataDBObj = Metadata::DB::Item::ContentImportDataStatus->Create();

                $contentImportDataDBObj->status( $result->error_code );
                $contentImportDataDBObj->message( $result->error_as_string );
                $contentImportDataDBObj->ref_column($column);
                $contentImportDataDBObj->type('legacy');
                $contentImportDataDBObj->content_import_data_id( $row->{id} );

                $contentImportDataDBObj->save();
            }

        }
    }

    Metadata::DB::Item::ContentImportData->UpdateValidationStatus( status => 4, all => 1 );
}

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

    my @contraintValidators;
    my %definitions = (kImportDefinitions);
    my $constraintObj;
    my $constraints = {};
    my $name;

    if ( $self->{_automated} ) {
        $definitions{'catalog-no'}->{constraint}{'Validate::Rule::Constraint::Nullable'} = 1;
        $definitions{'p-line'}->{constraint}{'Validate::Rule::Constraint::Nullable'}     = 1;
    }

    Common::Log::Debug("  ++ Initializing constraints ++");
    foreach my $key ( keys(%definitions) ) {
        $name = $key;
        $name =~ s/-/_/g;

        # If no rules are configured ignore this constraint
        next unless ( ref( $definitions{$key} ) && keys( %{ $definitions{$key} } ) );

        assert(
            ref( $definitions{$key} ) && ref( $definitions{$key}->{constraint} ),
            "$key does not refer to a hash ref ($definitions{$key})"
        );

        # If no rules are configured ignore this constraint
        next unless ( keys( %{ $definitions{$key}->{constraint} } ) );

        Common::Log::Debug("     initializing constraint for $name");
        $constraintObj = new Metadata::Validate::Constraint( name => $name );

        assert( $definitions{$key}->{constraint}->{fail_status}, "fail_status not defined for $key" );

        foreach my $rule ( keys( %{ $definitions{$key}->{constraint} } ) ) {
            next unless ( $rule =~ /::Rule::/ );

            $constraintObj->config_rule(
                rule => $rule,
                $self->_get_rule_arguments( $definitions{$key}->{constraint}->{$rule}, $definitions{$key}->{constraint}->{fail_status} )
            );
        }

        $constraints->{$name} = $constraintObj;
    }

    $self->{_constraints} = $constraints;
}

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

    my @albumValidators;
    my %definitions = (kImportDefinitions);
    my $albumObj;
    my $albumValidators = {};
    my $name;
    my $album = {};
    my %additional_args;

    Common::Log::Debug("  ++ Initializing album validation ++");
    foreach my $key ( keys(%definitions) ) {
        $name = $key;
        $name =~ s/-/_/g;

        # If no rules are configured ignore this constraint
        next unless ( ref( $definitions{$key} ) && keys( %{ $definitions{$key} } ) );

        assert( ref( $definitions{$key} ) && ref( $definitions{$key}->{album} ), "$key does not refer to a hash ref" );

        # If no rules are configured ignore this constraint
        next unless ( keys( %{ $definitions{$key}->{album} } ) );

        Common::Log::Debug("     initializing validation for $name");
        $albumObj = new Metadata::Validate::Album( name => $name );

        assert( $definitions{$key}->{album}->{fail_status}, "fail_status not defined for $key" );

        foreach my $rule ( keys( %{ $definitions{$key}->{album} } ) ) {
            next unless ( $rule =~ /::Rule::/ );

            Common::Log::Debug("RULE: $rule NAME: $name");
            %additional_args = $definitions{$key}->{album}->{$rule} ? %{ $definitions{$key}->{album}->{$rule} } : ();

            $albumObj->config_rule(
                rule           => $rule,
                unifying_field => Metadata::Validate::Album::kGroupField,
                target_field   => $name,
                %additional_args
            );
        }

        $album->{$name} = $albumObj;
    }

    $self->{_album} = $album;
}

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

    my @validators;
    my %definitions  = (kImportDefinitions);
    my $current_data = {};
    my $validationObj;
    my $name;
    my %additional_args;

    if ( $self->{_automated} ) {
        $definitions{'catalog-no'}->{current_data}  = {};
        $definitions{'album-title'}->{current_data} = {};
    }

    Common::Log::Debug("  ++ Initializing album validation ++");
    foreach my $key ( keys(%definitions) ) {
        $name = $key;
        $name =~ s/-/_/g;

        # If no rules are configured ignore this constraint
        next unless ( ref( $definitions{$key} ) && keys( %{ $definitions{$key} } ) );

        assert( ref( $definitions{$key} ) && ref( $definitions{$key}->{current_data} ), "$key does not refer to a hash ref" );

        # If no rules are configured ignore this constraint
        next unless ( keys( %{ $definitions{$key}->{current_data} } ) );

        Common::Log::Debug("     initializing validation for $name");
        $validationObj = new Metadata::Validate::CurrentData( name => $name );

        assert( $definitions{$key}->{current_data}->{fail_status}, "fail_status not defined for $key" );

        $validationObj->source_column($name);

        foreach my $rule ( keys( %{ $definitions{$key}->{current_data} } ) ) {
            next unless ( $rule =~ /::Rule::/ );

            %additional_args = $definitions{$key}->{current_data}->{$rule} ? %{ $definitions{$key}->{current_data}->{$rule} } : ();

            $validationObj->config_rule( rule => $rule, %additional_args );
        }

        $current_data->{$name} = $validationObj;
    }

    $self->{_current_data} = $current_data;
}

sub _get_rule_arguments {
    my $self                = shift;
    my $parameter           = shift;
    my $default_fail_status = shift;

    assert( $self && defined($parameter) && defined($default_fail_status), "Missing required parameters" );

    my %rule_args;

    if ( ref($parameter) && ref($parameter) eq 'HASH' ) {
        assert( defined( $parameter->{parameter} ), "parameter is required" );

        $rule_args{message} = $parameter->{message}
          if ( $parameter->{message} );
        $rule_args{parameter} = $parameter->{parameter};
        $rule_args{fail_status} =
          defined( $parameter->{fail_status} )
          ? $parameter->{fail_status}
          : $default_fail_status;
    } else {
        $rule_args{parameter}   = $parameter;
        $rule_args{fail_status} = $default_fail_status;
    }

    return %rule_args;
}

1;
