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

#------------------------------------------------------------
# A validation Item.  Use validation items when you want to
# run one or more tests on an element.  Elements could be
# anything from simple scalars to complex data structures.
#
# A validation item is composed of validation rules.  Each
# rule is considered a test which should be performed on the
# Validate::Item's data.  Rules are objects derived from
# Validate::Rule.
#
# Which rules are contained in Validate::Item object are
# defined in the kValidationRules constant.  This constant
# should be defined in the Validate::Item object as well as
# any of its children.  When a Validate::Item object is
# instantiated it will create all of the required
# Validate::Rule ojects.
#
# Before using the validate method you must call the
# config_rule method for any of the objects rules that you
# would like to be applied.  If a rule has not been
# configured then it is ignored when validating the object's
# data.
#
# Public methods:
#   + Validate::Item     new();
#   + Validate::Response validate( $data, %extra_parameters );
#   + void               config_rule( %rule_options );
#
# Public attributes:
#   none
#------------------------------------------------------------

package Validate::Item;
use strict;

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

use lib '/app/tools/metadata/lib';
use Validate;
use Validate::Response;

use Validate::Rule::Constraint::Code;
use Validate::Rule::Constraint::Territories;

###
# Define what validation rules can be applied to this object
###
use constant kValidationRules => qw( Validate::Rule::Constraint::Code
  Validate::Rule::Constraint::Territories );

#------------------------------------------------------------
# Public Methods
#------------------------------------------------------------

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

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

#------------------------------------------------------------
# Validate::Response validate($target_data, %extra_arguments)
#
# Method called to run all configured rules on $target_data.
# A Validate::Response object is returned which contains
# the ressult of all tests.
#
# Each rule is executed independantly of other rules and
# do not share results.  Order of execution is indeterminant.
#
# All parameters passed to this method are then directly
# passed to the Validate::Rule ok method.
#
# PRE: * _rules must contain a rules object list.
#      * one or more rules need to be configured.
#
# Parameters:
#   $target_data - The data element we will preform the test
#                  on.
#   %extra_arguments - Additional arguments to pass through
#                      to the Validate::Rule object.
#                      Generally this would be a hash.
#
# Return:
#   Validate::Response - A response object containing the
#                        individual results of each Rule
#                        execution.
#------------------------------------------------------------
sub validate {
    my ( $self, $target_data, %extra_arguments ) = @_;

    # Pre condition, we do have rules defined.
    assert( $self->{_rules} && ref( $self->{_rules} ) );

    my $response = new Validate::Response;
    my $result;
    my $configured_rule_executed;

    # Iterate through each rule and execute the OK method.  Results are
    # then stored in the response object.
    foreach ( keys %{ $self->{_rules} } ) {
        $result = $self->{_rules}->{$_}->ok( $target_data, %extra_arguments );
        $configured_rule_executed = 1
          if ( defined($result) || $configured_rule_executed );

        if ($result) {
            $response->add_error(
                code    => $result,
                message => $self->{_rules}->{$_}->message(),
                rule    => $_
            );
        }
    }

    # Pre condition.  Ensure at least one rule was configured.
    assert( $configured_rule_executed, "No rules configured for this item" );

    return $response;
}

#------------------------------------------------------------
# void config_rule( %args )
#
# Configure a rule object.  Establish all rule parameters
# and override default message and failure status if needed.
#
# PRE: The rule type passed in must have already been
#      instantiated in the Validate::Item object.  In other
#      words, the rule type needs to be defined in a
#      kValidationRules constant.
#
# Parameters:
#   rule        -  The object type of the rule.  This must be
#                  declaired in one of the kValidationRules
#                  constants.  This object will be instantiated
#                  by the Validate::Item ctor.
#
#   parameter   -  Data needed by the rule to act upon what is
#                  being tested.  This is passed directly to
#                  the rule object.  The rule object dictates
#                  what this data needs to be.
#
#   message     -  Overload the default error message for a
#                  rule.  ** optional **
#
#   fail_status -  Overload the default failure status code
#                  for a rule.  ** optional **
#
#------------------------------------------------------------
sub config_rule {
    my ( $self, %args ) = @_;
    assert($self);
    assert( $args{rule} && $self->_has_rule( $args{rule} ), "$args{rule} not defined in the Item object" );

    foreach ( keys %args ) {
        $self->{_rules}->{ $args{rule} }->$_( $args{$_} )
          unless ( $_ eq 'rule' );
    }
}

#------------------------------------------------------------
# Private  Methods
#------------------------------------------------------------

#------------------------------------------------------------
# Validate::Item _init()
#
# Initialize the Validate Item object.  All we really do here
# is instantiate all of the rule objects defined in
# kValidationRules
#
# Return:
#   Validate::Item - reference to ourself
#------------------------------------------------------------
sub _init {
    my ( $self, %args ) = @_;

    # Build a list of rules that apply to this item
    foreach ( (kValidationRules) ) {
        assert( !$self->_has_rule($_), "Multiple object for rule found: " . ref($_) );
        $self->_add_rule( rule => $_->new() );
    }

    # Set any attributes passed in
    foreach ( keys(%args) ) {
        $self->$_( $args{$_} ) if ( $self->{_attributes}->{$_} );
    }

    return $self;
}

#------------------------------------------------------------
# bool _has_rule( $rule )
#
# Determine if a rule type has been instantiated in this
# Validate::Item object.
#
# Parameters:
#   $rule - The rule type we are checking the existance of.
#
# Return:
#   bool - return true if a rule of $rule type has been
#          instantiated in this object, otherwise return
#          false.
#------------------------------------------------------------
sub _has_rule {
    my ( $self, $rule ) = @_;
    assert($self);
    assert( $rule, "rule required in _has_rule" );

    return undef unless ( $self->{_rules} );

    return defined( $self->{_rules}->{$rule} );
}

#------------------------------------------------------------
# void _add_rule( $rule )
#
# Add a new rule of type defined in $rule to this object.
# Rules are stored in a hash ref with the key being the rule
# type.  This allows us to do quite existance checks.
#
# Parameters:
#   $rule - The rule type we would like to add to this object
#------------------------------------------------------------
sub _add_rule {
    my ( $self, %args ) = @_;
    assert($self);
    assert( $args{rule} && ref( $args{rule} ) );

    $self->{_rules}->{ ref( $args{rule} ) } = $args{rule};
}

1;
