# UVmenu: menu for interaction with the votetaker
# Used by uvvote.pl, uvcfv.pl, uvcount.pl
 
package UVmenu;
 
use strict;
use UVconfig;
use UVmessage;
use UVrules;
use vars qw($VERSION);

use Text::Wrap qw(wrap $columns);
 
# Module version
$VERSION = "0.4";

##############################################################################
# Menu for interaction with the votetaker                                    #
# Parameters: votes list and header (references to arrays)                   #
#             Body, Mailadress, Name, Ballot ID (references to strings)      #
#             List of newly set fields (reference to array)                  #
#             List of errors to correct (Array-Ref)                          #
# Return Values: 'w': proceed                                                #
#                'i': ignore (don't save vote)                               #
##############################################################################

sub menu {
  my ($votes, $header, $body, $addr, $name, $ballot_id, $set, $errors) = @_;
  my $input = "";
  my $voter_addr = $$addr || '';
  my $voter_name = $$name || '';
  my @newvotes = @$votes;
  my $mailonly = 0;
  my %errors;
  $$ballot_id ||= '';

  foreach my $error (@$errors) {

    # unrecognized vote: extract group number und display warning
    if ($error =~ /^UnrecognizedVote #(\d+)#(.+)$/) {
      $errors{UnrecognizedVote} ||= UVmessage::get("MENU_UNRECOGNIZEDVOTE");
      $errors{UnrecognizedVote} .= "\n  " . UVmessage::get("MENU_UNRECOGNIZED_LIST")
                                          . " #$1: $2";

    # violated rule: extract rule number and display warning
    } elsif ($error =~ /^ViolatedRule #(\d+)$/) {
      $errors{ViolatedRule} ||= UVmessage::get("MENU_VIOLATEDRULE", (RULE => "#$1"));

    } else {
      # special handling if called from uvballot.pl
      $mailonly = 1 if ($error =~ s/Ballot$//);

      # get error message for this error from messages.cfg
      $errors{$error} = UVmessage::get("MENU_" . uc($error));
    }
  }

  # This loop is only left by 'return'
  while (1) {

    system($config{clearcmd});
    print UVmessage::get("MENU_PROBLEMS") . "\n";

    foreach my $error (keys %errors) {
      print "* $errors{$error}\n";
    }

    my $menucaption = UVmessage::get("MENU_CAPTION");
    print "\n\n$menucaption\n";
    print "=" x length($menucaption), "\n\n";
    print "(1) ", UVmessage::get("MENU_SHOW_MAIL"), "\n\n",
          UVmessage::get("MENU_CHANGE_PROPERTIES"), "\n",
          "(2) ", UVmessage::get("MENU_ADDRESS"), " [$voter_addr]\n";

    # don't print these options if called from uvcfv.pl
    unless ($mailonly) {
      print "(3) ", UVmessage::get("MENU_NAME"), " [$voter_name]\n";
      print "(4) ", UVmessage::get("MENU_VOTES"), " [", @$votes, "]\n";
      print "(5) ", UVmessage::get("MENU_BALLOT_ID"), " [$$ballot_id]\n"
        if ($config{personal});
      print "(6) ", UVmessage::get("MENU_BDSG"), "\n" if ($config{bdsg});
    }

    print "\n",
          "(i) ", UVmessage::get("MENU_IGNORE"), "\n",
          "(w) ", UVmessage::get("MENU_PROCEED"), "\n\n",
          UVmessage::get("MENU_PROMPT");

    do { $input = <STDIN>; } until ($input);
    chomp $input;
    print "\n";

    # only accept 1, 2, i and w if called from uvcfv.pl
    next if ($mailonly && $input !~ /^[12iw]$/i);

    if ($input eq '1') {
      system($config{clearcmd});
      # ignore SIGPIPE (Bug in more and less)
      $SIG{PIPE} = 'IGNORE';
      open (MORE, "|$config{pager}");
      print MORE join("\n", @$header), "\n\n", $$body, "\n";
      close (MORE);
      
      print "\n", UVmessage::get("MENU_GETKEY");
      $input = <STDIN>;

    } elsif ($input eq '2') {
      my $sel;
      do {
        print "[a] ", UVmessage::get("MENU_ADDRESS_OK"), "\n",
              "[b] ", UVmessage::get("MENU_ADDRESS_CHANGE"), "\n",
              "[c] ", UVmessage::get("MENU_ADDRESS_INVALID"), "\n\n",
              UVmessage::get("MENU_PROMPT");
        $sel = <STDIN>;
      } until ($sel =~ /^[abc]$/i);
      if ($sel =~ /^a$/i) {
        delete $errors{SuspiciousAccount};
        delete $errors{InvalidAddress};
        next;
      } elsif ($sel =~ /^c$/i) {
        delete $errors{SuspiciousAccount};
        $errors{InvalidAddress} = UVmessage::get("MENU_INVALIDADDRESS") . " " .
                                  UVmessage::get("MENU_INVALIDADDRESS2");
        next;
      }
        
      do {
        print "\n", UVmessage::get("MENU_ADDRESS_PROMPT"), " ";
        $voter_addr = <STDIN>;
        chomp ($voter_addr);
      } until ($voter_addr);
      $$addr = $voter_addr;
      push (@$set, 'Adresse');
      delete $errors{SuspiciousAccount};
      delete $errors{InvalidAddress};
      check_ballotid(\%errors, \$voter_addr, $ballot_id, \%ids);

    } elsif ($input eq '3') {
      my $sel;
      do {
        print "[a] ", UVmessage::get("MENU_NAME_OK"), "\n",
              "[b] ", UVmessage::get("MENU_NAME_CHANGE"), "\n",
              "[c] ", UVmessage::get("MENU_NAME_INVALID"), "\n\n",
              UVmessage::get("MENU_PROMPT");
        $sel = <STDIN>;
      } until ($sel =~ /^[abc]$/i);
      if ($sel =~ /^a$/i) {
        delete $errors{InvalidName};
        next;
      } elsif ($sel =~ /^c$/i) {
        $errors{InvalidName} = UVmessage::get("MENU_INVALIDNAME");
        next;
      }
      print UVmessage::get("MENU_NAME"), ": ";
      $voter_name = <STDIN>;
      chomp ($voter_name);
      $$name = $voter_name;
      push (@$set, 'Name');
      delete $errors{NoName};
      delete $errors{InvalidName};

      $errors{InvalidName} = UVmessage::get("MENU_INVALIDNAME")
        unless ($voter_name =~ /$config{name_re}/);
 
    } elsif ($input eq '4') {
      # set votes

      my $sel;
      do {
        print "[a] ", UVmessage::get("MENU_VOTES_OK"), "\n",
              "[b] ", UVmessage::get("MENU_VOTES_RESET"), "\n",
              "[c] ", UVmessage::get("MENU_VOTES_INVALID"), "\n",
              "[d] ", UVmessage::get("MENU_VOTES_CANCELLED"), "\n\n",
              UVmessage::get("MENU_PROMPT");
        $sel = <STDIN>;
      } until ($sel =~ /^[abcd]$/i);
      if ($sel =~ /^[ad]$/i) {
        delete $errors{NoVote};
        delete $errors{UnrecognizedVote};
        delete $errors{ViolatedRule};
        delete $errors{DuplicateVote};
        if ($sel =~ /^d$/i) {
          # cancelled vote: replace all votes with an A
          @$votes = split(//, 'A' x scalar @groups);
          push @$set, 'Stimmen';
          # some errors are irrelevant when cancelling a vote:
          delete $errors{InvalidName};
          delete $errors{NoName};
          delete $errors{InvalidBDSG};
          delete $errors{InvalidAddress};
          delete $errors{SuspiciousAccount};
        }
        next;
      } elsif ($sel =~ /^c$/i) {
        $errors{NoVote} = UVmessage::get("MENU_INVALIDVOTE");
        next;
      }

      # Set columns for Text::Wrap
      $columns = $config{rightmargin};
      print "\n", wrap('', '', UVmessage::get("MENU_VOTES_REENTER_ASK")), "\n\n";
      print UVmessage::get("MENU_VOTES_REENTER_LEGEND"), "\n";

      for (my $n=0; $n<@groups; $n++) {
        my $voteinput = "";
        $votes->[$n] ||= 'E';

        # repeat while invalid character entered
        while (!($voteinput =~ /^[JNE]$/)) {
          my $invalid = $#groups ? 0 : 1;
          print UVmessage::get("MENU_VOTES_REENTER", (GROUP => $groups[$n]));
          $voteinput = <STDIN>;
          chomp $voteinput;
          $voteinput ||= $votes->[$n];
          $voteinput =~ tr/jne/JNE/;
        }
        
        # valid input, save new votes
        $newvotes[$n] = $voteinput;
      } 

      print "\n\n";
      my $oldvotes = UVmessage::get("MENU_VOTES_REENTER_OLD");
      my $newvotes = UVmessage::get("MENU_VOTES_REENTER_NEW");
      my $oldlen = length($oldvotes);
      my $newlen = length($newvotes);
      my $maxlen = 1 + (($newlen>$oldlen) ? $newlen : $oldlen);
      print $oldvotes, ' ' x ($maxlen - length($oldvotes)), @$votes, "\n",
            $newvotes, ' ' x ($maxlen - length($newvotes)), @newvotes, "\n\n";

      do {
        print "[a] ", UVmessage::get("MENU_VOTES_REENTER_ACK"), "    ",
              "[b] ", UVmessage::get("MENU_VOTES_REENTER_NACK"), "\n\n", 
               UVmessage::get("MENU_PROMPT");
        $sel = <STDIN>;
      } until ($sel =~ /^[ab]$/i);

      next if ($sel =~ /^b$/i);
      @$votes = @newvotes;
      push @$set, 'Stimmen';
      delete $errors{UnrecognizedVote};
      delete $errors{DuplicateVote};
      delete $errors{NoVote};
      delete $errors{ViolatedRule};

      if (my $rule = UVrules::rule_check($votes)) {
        $errors{ViolatedRule} = UVmessage::get("MENU_VIOLATEDRULE", (RULE => "#$rule"));
      }

    } elsif ($input eq '5' && $config{personal}) {
      print "\n", UVmessage::get("MENU_BALLOT_ID"), ": ";
      $$ballot_id = <STDIN>;
      chomp ($$ballot_id);
      push (@$set, 'Kennung');
      check_ballotid(\%errors, \$voter_addr, $ballot_id, \%ids);

    } elsif ($input eq '6' && $config{bdsg}) {
      my $sel;
      do {
        print "[a] ", UVmessage::get("MENU_BDSG_ACCEPTED"), "\n",
              "[b] ", UVmessage::get("MENU_BDSG_DECLINED"), "\n\n",
              UVmessage::get("MENU_PROMPT");
        $sel = <STDIN>;
      } until ($sel =~ /^[ab]$/i);

      if ($sel =~ /^a$/i) {
        delete $errors{InvalidBDSG};
      } else {
        $errors{InvalidBDSG} = UVmessage::get("MENU_INVALIDBDSG");
      }

    } elsif ($input =~ /^i$/i) {
      my $ignore = UVmessage::get("MENU_IGNORE_STRING");
      # Set columns for Text::Wrap
      $columns = $config{rightmargin};
      print wrap('', '', UVmessage::get("MENU_IGNORE_WARNING",
                                        (MENU_IGNORE_STRING => $ignore)
                                       ));
      if (<STDIN> eq "$ignore\n") {
        print "\n";
        return "i";
      }

    } elsif ($input =~ /^w$/i) {

      if (keys %errors) {
        if ((keys %errors)==1 && $errors{UnrecognizedVote}) {
          # unrecognized vote lines aren't errors if votetaker
          # did not change them
          @$errors = ();
        } else {
          # Set columns for Text::Wrap
          $columns = $config{rightmargin};
          @$errors = keys %errors;
          my $warning = ' ' . UVmessage::get("MENU_ERROR_WARNING") . ' ';
          my $length = length($warning);
          print "\n", '*' x (($config{rightmargin}-$length)/2), $warning,
                '*' x (($config{rightmargin}-$length)/2), "\n\n",
                wrap('', '', UVmessage::get("MENU_ERROR_TEXT")), "\n\n",
                '*' x $config{rightmargin}, "\n\n",
                UVmessage::get("MENU_ERROR_GETKEY");
          my $input = <STDIN>;
          next if ($input !~ /^y$/i);
          print "\n";
        }
      } else {
        @$errors = ();
      }
 
      system($config{clearcmd});
      print "\n", UVmessage::get("MENU_PROCESSING"), "\n";
      return "w";
    }
  }

  sub check_ballotid {
    my ($errors, $voter_addr, $ballot_id, $ids) = @_;

    return 0 unless ($config{personal});

    delete $errors->{NoBallotID};
    delete $errors->{WrongBallotID};
    delete $errors->{AddressNotRegistered};

    if ($$ballot_id) {
      if ($ids->{$$voter_addr}) {
        if ($ids->{$$voter_addr} ne $$ballot_id) {
          # ballot id incorrect
          $errors->{WrongBallotID} = UVmessage::get("MENU_WRONGBALLOTID");
        }
      } else {
        $errors->{AddressNotRegistered} = UVmessage::get("MENU_ADDRESSNOTREGISTERED");
      } 
    } else {
      $errors->{NoBallotID} = UVmessage::get("MENU_NOBALLOTID");
    }
  }

}


##############################################################################
# Menu for sorting out duplicate votings manually                            #
# Parameters: References to hashes with the paragraphs from the result file  #
#             and the default value                                          #
# Return value: selected menu item (1, 2 or 0)                               #
##############################################################################

sub dup_choice {
  my ($vote1, $vote2, $default) = @_;

  print STDERR "\n", UVmessage::get("MENU_DUP_VOTE"), "\n\n";
  print STDERR UVmessage::get("MENU_DUP_FIRST"), "\n";
  print STDERR "A: $vote1->{A}\n";
  print STDERR "N: $vote1->{N}\n";
  print STDERR "D: $vote1->{D}\n";
  print STDERR "K: $vote1->{K}\n";
  print STDERR "S: $vote1->{S}\n\n";
  print STDERR UVmessage::get("MENU_DUP_SECOND"), "\n";
  print STDERR "A: $vote2->{A}\n";
  print STDERR "N: $vote2->{N}\n";
  print STDERR "D: $vote2->{D}\n";
  print STDERR "K: $vote2->{K}\n";
  print STDERR "S: $vote2->{S}\n\n";
  print STDERR "1: ", UVmessage::get("MENU_DUP_DELFIRST"), "\n",
               "2: ", UVmessage::get("MENU_DUP_DELSECOND"), "\n",
               "0: ", UVmessage::get("MENU_DUP_DELNONE"), "\n\n";

  my $input;

  do {
    print STDERR UVmessage::get("MENU_PROMPT"), "[$default] ";
    $input = <STDIN>;
    chomp $input;
  } until ($input eq '' || ($input >= 0 && $input<3));

  return $input || $default;
}

1;
