package LIMS::Validate::Roles::ConstraintMethods;

# contains constraint_method helper methods for LIMS::Validate

use Moose::Role;
use namespace::clean -except => 'meta';

use Data::Dumper;
use LIMS::Local::Utils;
use FormValidator::Simple qw(+LIMS::Validate::DatePlugin);
use Data::FormValidator::Constraints
	qw(:regexp_common FV_max_length FV_length_between valid_email);
use Data::FormValidator::Constraints::DateTime qw(:all); # using: ymd_before_today()
use Data::FormValidator::Constraints::MethodsFactory qw(:bool); # FV_and

#-------------------------------------------------------------------------------
sub check_nhsno_is_valid {
	my $self = shift; # warn 'here'; warn Dumper $self;
	
	return sub {
		my $dfv = shift; # warn 'here'; warn Dumper $dfv;
		my $nhs_number = shift || return 0; # warn 'NHSNo:'.$nhs_number;

		$dfv->name_this('is_valid');
		
		# check_nhsno() return true if valid:
		my $is_valid = LIMS::Local::Utils::check_nhsno( $nhs_number ); # warn $is_valid;
		return $is_valid;
	}
}

#-------------------------------------------------------------------------------
sub check_nhsno_is_unique {
	my $self = shift; 

	return sub {
		my $dfv = shift; # warn 'here'; warn Dumper $dfv;
		my $nhs_number = shift || return 0; # warn 'NHSNo:'.$nhs_number;

		$dfv->name_this('is_unique');

		my $validation_model = $self->models->{validate_nhs_number_unique};

		my $not_unique = $validation_model->($nhs_number);

		return $not_unique ? 0 : 1;
	}
}

#-------------------------------------------------------------------------------
sub check_email_is_valid {
	my $self = shift;
	
	return sub {
		my ($dfv, $email) = @_; # warn $email;
		return valid_email($email); # Data::FormValidator::Constraints method
	}	
}

#-------------------------------------------------------------------------------
sub check_post_code_is_valid {
	my $self = shift;
	
	return sub {
		my ($dfv, $zip) = @_; # warn $zip;
		return LIMS::Local::Utils::format_postcode($zip);
	}	
}

#-------------------------------------------------------------------------------
sub check_email_domain {
	my $self = shift;

	my $settings = $self->settings;
	my @valid_domains = split /\,\s?|\s+/, $settings->{valid_recipient_domains};

	return sub {
		my ($dfv, $email) = @_; # warn $email;
		return grep $email =~ /$_\Z/, @valid_domains;
	}	
}

#-------------------------------------------------------------------------------
sub check_no_spaces {
	my $self = shift; 

	return sub {
		my $dfv = shift; # warn 'here';		
		$dfv->name_this('no_spaces');
		
		my $val = $dfv->get_current_constraint_value(); # warn $val;
		
		my ($is_valid) = $val =~ /^[a-zA-Z0-9_]+$/; # warn $is_valid;

		return $is_valid;
	}	
}

#-------------------------------------------------------------------------------
# used by new_request() & Ajax::_validate_params()
sub get_request_number_constraints {
	my $self = shift;

	my $is_unique = sub {
		my $dfv = shift; # warn 'here';		
		$dfv->name_this('is_unique');
				
		my $val = $dfv->get_current_constraint_value(); # warn $val;
		my $uniqueness = $self->check_request_number($val);
				
		return $uniqueness;
	};
	
	return [ # TODO: join_these() doesn't work here
		$is_unique, # check it's unqiue
		FV_num_int, # must be integer; Regexp::Common, s/RE/FV/
		$self->check_param_length( [1,5] ), # don't expect >99_999/yr
#		FV_clamp(1, 1, 5), # MethodsFactory method - no configurable msgs output ?
	];
}

#-------------------------------------------------------------------------------
# used by new_request() & Ajax::_validate_params()
sub get_request_number_messages { 
	my $self = shift;

	return { # messages need to be set to constraint_names, not field_names:
		constraints => {
			is_unique      => $self->messages->{not_unique},
			FV_num_int     => $self->messages->{need_integer},
			length_between => $self->messages->{too_long},
		},
	};
}

#-------------------------------------------------------------------------------
# can have non-unique test_names, but not with same lab_section_id:
=begin ? not in use
sub check_lab_test_is_valid {
	my $self = shift;
	
	return sub {
		my $dfv = shift;		
		$dfv->name_this('is_unique');

		my $data = $dfv->get_filtered_data; # warn Dumper $data;

		my $validation_model = $self->models->{validate_lab_test};
		
		my $is_valid = $validation_model->($data);
		return $is_valid;
	}
}
=cut

#-------------------------------------------------------------------------------
# used by new_request() & Ajax::_validate_params()
sub get_specimen_constraints {
	my $self = shift; 

	return sub {
		my $dfv = shift; # warn 'here';		
		$dfv->name_this('check_specimen');
		
		my $val = $dfv->get_current_constraint_value(); # warn $val;
		
		my $is_valid = $self->check_specimen($val); # warn $is_valid;

		return $is_valid;
	}
}

#-------------------------------------------------------------------------------
# checks entry(s) in specimen field exists in specimens table:
# (not a DFV method so doesn't receive dfv object)
sub check_specimen { # warn 'HERE'; warn Dumper @_; warn 'HERE';
	my $self = shift;

	my $specimens = shift || return 0; # warn 'specimens:'.$specimens;

    # split input on comma and/or space(s):
    my @specimens = split /\,\s?|\s+/, $specimens; # $self->debug(\@specimens);

	my $validation_model = $self->models->{validate_specimen};

    foreach my $specimen (@specimens) {
        my $valid = $validation_model->($specimen);	# warn "$specimen:$valid";
        return 0 if ! $valid;
    }
	# OK, specimen(s) valid:
	return 1;
}

#-------------------------------------------------------------------------------
sub check_referrer_code {
	my $self = shift;

	return sub {
		my $dfv = shift; #  warn Dumper $dfv;		
		$dfv->name_this('referrer_code');
		
		my $val = $dfv->get_current_constraint_value(); # warn $val;

		# C, D, E or CS followed by 5-7 numbers:
		return ($val =~ /\A([CDE]|CS)\d{5,7}\Z/) ? 1 : 0;
	}
}

#-------------------------------------------------------------------------------
sub check_request_number {
	my $self = shift;

	my $request_number = shift || return 0; # warn 'request_number:'.$request_number;

	my $validation_model = $self->models->{validate_request_number};

	my $not_unique = $validation_model->($request_number);

    return $not_unique ? 0 : 1;
}

#-------------------------------------------------------------------------------
sub get_date_constraints {
	my $self = shift;
	
	my $constraint = FV_and(
		$self->check_four_digit_year(),
		$self->check_date_is_valid( [ qw( year month day ) ] ),
		$self->check_date_not_future( [ qw( year month day ) ] ),
	);
	
	return $constraint;
}

#-------------------------------------------------------------------------------
sub get_change_password_profile {
	my $self = shift;
	    
	return [ # FormValidator::Simple method
        new_password         => [ 'NOT_BLANK', ['LENGTH', 5, 255 ] ],
        old_password         => [ 'NOT_BLANK', ['LENGTH', 5, 255 ] ],
        new_password_confirm => [ 'NOT_BLANK', ['LENGTH', 5, 255 ] ],
        { pwds => ['new_password', 'new_password_confirm'] } => [ 'DUPLICATION' ],
    ];
}

#-------------------------------------------------------------------------------
# uses FV_and() to return constraint_method, which executes list in order &
# exits on 1st failure, instead of executing all and potentially generating errs
sub join_these {
	my $self = shift;

	return FV_and(@_);
}

#-------------------------------------------------------------------------------
sub check_four_digit_year {
	my $self = shift;
	
	return sub {
		my $dfv = shift; #  warn Dumper $dfv;		
		$dfv->name_this('yyyy_format');
		
		my $data = $dfv->get_filtered_data; # warn Dumper $data;		
		return ( length $data->{year} == 4 );
	}
}

#-------------------------------------------------------------------------------
sub check_numeric {
	my $self = shift;
	
	return sub {
		my $dfv = shift; #  warn Dumper $dfv;		
		$dfv->name_this('numeric_format');
		
		my $val = $dfv->get_current_constraint_value(); # warn $val;
		
		# digits only:
		my ($is_valid) = $val =~ /^[0-9]+$/; # warn $is_valid;
		
		return $is_valid; 
	}
}

#-------------------------------------------------------------------------------
# override MethodsFactory method until can find out how to target msgs at it
sub _FV_clamp {
	my $self = shift;
    my ($result, $low, $high) = @_;
    return sub {
        my $dfv = shift;
		$dfv->name_this('FV_clamp');
        my $val = $dfv->get_current_constraint_value();
        return (($val < $low) or ($val > $high)) ? !$result : $result;
    }
}

#-------------------------------------------------------------------------------
# or could use 'day => ymd_before_today( qw/year month day/ )'
sub check_date_not_future {
	my $self = shift;
	
	return sub {
		my $dfv = shift; #  warn Dumper $dfv;
		$dfv->name_this('future_date'); 

		my $data = $dfv->get_filtered_data; # warn Dumper $data;
		
		# returns error if date not less than DateTime->now:
		my @cmp = (	'DATE_LESS_THAN', DateTime->now );
		
		my $result = FormValidator::Simple->check(
            $data => [
                { grouped => [ qw(year month day) ] } => [ \@cmp ],
            ]
        ); # warn Dumper $result->has_error;
		
		return $result->has_error ? 0 : 1;
	}
}
	
#-------------------------------------------------------------------------------
# if hidden flag '_diagnosis_id' passed, also need either 'revision' or
# 'final_diagnosis' param:
sub check_report_params { 
	my $self = shift;
	
	return sub {
		my $dfv = shift; # warn Dumper $dfv;
	
		$dfv->name_this('diagnosis_resubmission');
		my $data = $dfv->get_filtered_data; # warn Dumper $data;
		
		my $original_diagnosis_id = $dfv->get_current_constraint_value();
		# warn 'HERE'; warn $original_diagnosis_id;
		
		# eg authorisation, without change of diagnosis_id:
		return 1 if $original_diagnosis_id == $data->{diagnosis_id};
		
		# require either 'final_diagnosis' or 'revision' params:
		return grep $data->{$_}, qw(final_diagnosis revision);
	}
}

#-------------------------------------------------------------------------------
# # check end_date NOT before start_date - returns true (1) UNLESS $second_date
# BEFORE $first_date:
sub check_date_order {
	my $self = shift; 

	return sub {
		my $dfv = shift; # warn Dumper $dfv;
		$dfv->name_this('date_order'); 

		my $data = $dfv->get_filtered_data; # warn Dumper $data;
	
		# LIMS::Local::Utils::check_chronological_order() requires DateTime objects:
		my $start_date
			= LIMS::Local::Utils::to_datetime_using_datecalc($data->{start_date});
		my $end_date
			= LIMS::Local::Utils::to_datetime_using_datecalc($data->{end_date});
		
		my $expr # returns '-1' if dates in chronological order (0 if equal, otherwise 1):
			= LIMS::Local::Utils::check_chronological_order($end_date, $start_date);
			# warn Dumper $expr;
		return $expr == -1 ? 0 : 1; # return false if $expr eq -1 ie in date order
	}
}

#-------------------------------------------------------------------------------
sub check_date_is_valid {
	my $self = shift; 

	return sub {
		my $dfv = shift; #  warn Dumper $dfv;
		$dfv->name_this('valid_date'); 

		my $data = $dfv->get_filtered_data; # warn Dumper $data;
		
        my $result = FormValidator::Simple->check(
            $data => [
                { date => [ qw(year month day) ] } => [ 'DATE' ]
            ]
        ); # warn Dumper $result->has_error;
		
		return $result->has_error ? 0 : 1;
	};
}

#-------------------------------------------------------------------------------
sub check_EU_date_is_valid {
	my $self = shift; 

	return sub {
		my $dfv = shift; # warn Dumper $dfv;
		$dfv->name_this('valid_date'); 

		my $val = $dfv->get_current_constraint_value(); # warn $val;

        my $result = LIMS::Local::Utils::check_date($val); 
		return $result;
	};
}

#-------------------------------------------------------------------------------
sub check_param_length {
	my $self = shift;
	my $arg  = shift; # warn Dumper $arg;
	
	# if $arg is arrayref, assume length_between, otherwise max_length:
	return
		ref $arg eq 'ARRAY' ?
			FV_length_between(@$arg) :
				FV_max_length($arg);
}

#-------------------------------------------------------------------------------
# checks param is either unique, or belongs to submitted record id:
sub check_param_ownership {
	my $self = shift;
	my $tbl_info = shift; # arryref of 'col_name' & 'class'

	return sub {
		my $dfv = shift; # warn Dumper $dfv;
		my $data = $dfv->get_filtered_data; # warn Dumper $data;

		$dfv->name_this('ownership');

		my %args = (
			col   => $tbl_info->[0], # can be arrayref of col_names
			class => $tbl_info->[1],
			data  => $data,
		);

		# returns 'true' if param unique, or already belongs to record id:
		my $unique_or_belongs_to_record
			= $self->models->{validate_param_ownership}->(\%args);

		# so just return value of expression:
		return $unique_or_belongs_to_record;	
	};		
}

1;