sub _answerHints_init {}

=head1 AnswerHints

#  This is an answer-checker post-filter that allows you to produce
#  additional error messages for incorrect answers.  You can trigger
#  a message for a single answer, a collection of answers, or via a
#  subroutine that determines the condition for the message.
#
#  Note that this filter only works for MathObjects answer checkers.
#
#  The answer hints are given as a pair using => with the right-hand
#  side being the answer message and the left-hand side being one of
#  three possibilities:  1) the value that triggers the message,
#  2) a reference to an array of values that trigger the message, or
#  3) a code reference to a subtroutine that accepts tthe correct
#  answer, the student's answer, and the answer hash, and returns
#  1 or 0 depending on whether the message should or should not be
#  displayed.  (See the examples below.)
#
#  The right-hand side can be either the message string itself, or
#  a referrence to an array where the first element is the message
#  string, and the remaining elements are name-value pairs that
#  set options for the message.  These can include:
#
#      checkCorrect => 0 or 1      1 means check for messages even
#                                  if the answer is correct.
#                                      Default: 0
#
#      replaceMessage => 0 or 1    1 means it's OK to repalce any
#                                  message that is already in place
#                                  in the answer hash.
#                                      Default: 0
#
#      checkTypes => 0 or 1        1 means only perform the test
#                                  if the student answer is the
#                                  same type as the correct one.
#                                      Default: 1
#
#      score => number             Specifies the score to use if
#                                  the message is triggered (so that
#                                  partial credit can be given).
#                                      Default: keep original score
#
#      cmp_options => [...]        provides options for the cmp routine
#                                  used to check if the student answer
#                                  matches these answers.
#                                     Default: []
#
#  If more than one message matches the student's answer, the first
#  one in the list is used.
#
#  Example:
#
#    ANS(Vector(1,2,3)->cmp(showCoordinateHints=>0)->withPostFilter(AnswerHints(
#      Vector(0,0,0) => "The zero vector is not a valid solution",
#      "-<1,2,3>" => "Try the opposite direction",
#      "<1,2,3>" => "Well done!",
#      ["<1,1,1>","<2,2,2>","<3,3,3>"] => "Don't just guess!",
#      sub {
#        my ($correct,$student,$ans) = @_;
#        return $correct . $student == 0;
#      } => "Your answer is perpendicular to the correct one",
#      Vector(1,2,3) => [
#        "You have the right direction, but not length",
#        cmp_options => [parallel=>1],
#      ],
#      0 => ["Careful, your answer should be a vector!", checkTypes => 0, replaceMessage => 1],
#      sub {
#        my ($correct,$student,$ans) = @_;
#        return norm($correct-$student) < .1;
#      } => ["Close!  Keep trying.", score => .25],
#    )));
#

=cut

sub AnswerHints {
  return (sub {
    my $ans = shift; $ans->{_filter_name} = "Answer Hints Post Filter";
    my $correct = $ans->{correct_value};
    my $student = $ans->{student_value};
    Value::Error("AnswerHints can only be used with MathObjects answer checkers") unless ref($correct);
    return $ans unless ref($student);

    while (@_) {
      my $wrongList = shift; my $message = shift; my @options;
      ($message,@options) = @{$message} if ref($message) eq 'ARRAY';
      my %options = (
        checkCorrect => 0,
        replaceMessage => 0,
	checkTypes => 1,
        score => undef,
        cmp_options => [],
        @options,
      );
      next if $options{checkType} && $correct->type ne $student->type;
      $wrongList = [$wrongList] unless ref($wrongList) eq 'ARRAY';
      foreach my $wrong (@{$wrongList}) {
	if (ref($wrong) eq 'CODE') {
	  if (($ans->{score} < 1 || $options{checkCorrect}) &&
	      ($ans->{ans_message} eq "" || $options{replaceMessage}) &&
	      &$wrong($correct,$student,$ans)) {
	    $ans->{ans_message} = $ans->{error_message} = $message;
	    $ans->{score} = $options{score} if defined $options{score};
	    last;
	  }
	} else {
	  $wrong = Value::makeValue($wrong);
	  if (($ans->{score} < 1 || $options{checkCorrect} || AnswerHints::Compare($correct,$wrong,$ans)) &&
	      ($ans->{ans_message} eq "" || $options{replaceMessage}) &&
	      AnswerHints::Compare($wrong,$student,$ans,@{$options{cmp_options}})) {
	    $ans->{ans_message} = $ans->{error_message} = $message;
	    $ans->{score} = $options{score} if defined $options{score};
	    last;
	  }
	}
      }
    }
    return $ans;
  },@_);
}

package AnswerHints;

my $noValueRef = ! defined &{\&{Value::Ref}};
#
#  Calls the answer checker on two values with a copy of the answer hash
#  and returns true if the two values match and false otherwise.
#
sub Compare {
  my $self = shift; my $other = shift; my $ans = shift;
  $ans = bless {%{$ans},@_}, ref($ans);  # make a copy
  $ans->{typeError} = 0; $ans->{ans_message} = $ans->{error_message} = ""; $ans->{score} = 0;
  if ($noValueRef || Value::Ref($self) != Value::Ref($ans->{correct_value})) {
    $ans->{correct_ans} = $self->string;
    $ans->{correct_value} = $self;
    $ans->{correct_formula} = Value->Package("Formula")->new($self);
  }
  if ($noValueRef || Value::Ref($other) != Value::Ref($ans->{student_value})) {
    $ans->{student_ans} = $other->string;
    $ans->{student_value} = $other;
    $ans->{student_formula} = Value->Package("Formula")->new($other);
  }
  $self->cmp_preprocess($ans);
  $self->cmp_equal($ans);
  $self->cmp_postprocess($ans) if !$ans->{error_message} && !$ans->{typeError};
  return $ans->{score} >= 1;
}

1;
