#!/usr/bin/env perl
use strict;
use warnings;
use Storable;
use Carp;
use feature ':5.10';

my %info_for; #main hash that contains all voter information.

my %menu_num = (  #dispatch table to hold all the menu options
   1 => \&add_voter,
   2 => \&poll_voter,
   3 => \&print_info,
   4 => \&show_summary,
   5 => \&show_poll,
   6 => \&save_data,
   7 => \&load_data,
   8 => sub { exit },
   d => sub { require Data::Dumper; print Data::Dumper::Dumper(\%info_for); },
);

sub menu {
   {
      local $\ = "\n"; #don't type \n after each print.
      print 'Please choose from the following options:';
      print '1) Add New Voter';
      print '2) Poll Voter';
      print '3) Print Voter Info';
      print '4) Show Voter Summary';
      print '5) Show Poll Results';
      print '6) Save Data';
      print '7) Load Data';
      print '8) Exit';
   }
   chomp (my $input = <STDIN>);
   if (exists ($menu_num{$input})) {
      $menu_num{$input}->();
   } else {
      print 'Invalid selection.  Please choose from 1 - 8.';
      menu();
   }
}

#main program here.  Just continuously prompt for
#menu option, call that subroutine, and return to
#main menu.

menu() while 1;

sub add_voter_info { #used as helper to add_voter
   (@_ == 3 or @_ == 4) or croak 'usage: add_voter_info($name, $info, $req, $check_ref?)';
   my ($name, $info, $req, $check_ref) = @_;
   
   print "Please enter $name\'s $info";
   print " (or leave blank if declining to answer)" unless $req;
   print "\n";
   chomp (my $value = <STDIN>);
   if ($req) {
      while ($value eq '') {
         print "\$info is not optional.  Please reenter.\n";
         chomp ($value = <STDIN>);
      }
   } else {
      if ($value eq '') {
         $info_for{$name}{$info} = undef;
         return;
      }
   }

   #first check to see if this new value matches the condition.
   if (defined($check_ref)) {
      until ($check_ref->($value)) {
         print "Invalid selection for $info. Please try again:\n";
         chomp($value = <STDIN>);
         if ($value eq '' and !$req) {
            $info_for{$name}{$info} = undef;
         }
      }
   }

   $info_for{$name}{$info} = $value;
}

sub add_voter {

   #get required info
   print "Please enter the new voter's name: ";
   chomp (my $name = <STDIN>);
   if (exists $info_for{$name}) {
      print "Invalid selection.  $name already exists.\n";
      return;
   }
   $info_for{$name} = { };
   add_voter_info($name, 'Date of Birth (yyyy-mm-dd)', 1, 
      sub { 
         my ($D, $M, $Y) = (localtime())[3,4,5];
         $M += 1;
         $Y += 1900;
         my $date = shift;
         return 0 unless $date =~ /^(\d{4})-(\d{2})-(\d{2})/ and $2 <= 12 and $3 <= 31 ;
         my ($y, $m, $d) = ($1, $2, $3);
         return 0 if $Y - $y < 18;
         return 0 if $Y - $y == 18 and $M < $m;
         return 0 if $Y - $y == 18 and $M == $m and $D < $d;
         return 1;
      }
   );
 
   add_voter_info($name, 'address', 1);

   #get optional info
   add_voter_info($name, 'party affiliation', 0);
   add_voter_info($name, 'years voted (comma separated)', 0, 
      sub { $_[0] =~ /^\d{4}(?:,\d{4})*$/ },
   );
   for my $year (split /,/, $info_for{$name}{'years voted (comma separated)'}) {
      add_voter_info($name, "candidate voted for in $year", 0);
   }

}

sub print_info {
   print "Please enter the name of a voter\n";
   chomp (my $name = <STDIN>);
   if (!exists $info_for{$name}) {
      print "Sorry, no such voter '$name'\n";
      return;
   }
   print "Name: $name\n";
   print "Date of Birth: $info_for{$name}{'Date of Birth (yyyy-mm-dd)'}\n";
   print "Address: $info_for{$name}{address}\n";
   print "Party affiliation: ", ($info_for{$name}{'party affiliation'} // '<declined>'), "\n";
   if (defined $info_for{$name}{'years voted (comma separated)'}) {
      my @years = split ',', $info_for{$name}{'years voted (comma separated)'};
      for my $year (sort { $a <=> $b } @years) {
         print "Voted for in $year: ", ($info_for{$name}{"candidate voted for in $year"} // '<declined>'), "\n";
      }
   } else {
      print "Voting history: <declined>\n";
   }
   if (exists $info_for{$name}{'intended candidate'}) {
      print "Voting intent: ", ($info_for{$name}{'intended candidate'} // '<declined>'), "\n";
   } else {
      print "Not yet polled\n";
   }
}

sub poll_voter {
   print "Please enter the name of a voter\n";
   chomp (my $name = <STDIN>);
   if (!exists $info_for{$name}) {
      print "Sorry, no such voter '$name'\n";
      return;
   }
   add_voter_info($name, 'intended candidate', 0);
}

sub show_summary {
   print "Sort by (N)ame, (A)ge, (P)arty, or (V)oting intent?\n";
   chomp (my $sort = lc <STDIN>);
   if ($sort !~ /^[napv]$/) {
      print "Invalid response.\n";
      return;
   }
   my %sort_key = ( n => 'name', a => 'Date of Birth (yyyy-mm-dd)', p => 'party affiliation', v => 'intended candidate' );
   my @voters = sort { 
                   ($info_for{$a}{$sort_key{$sort}} // '') cmp ($info_for{$b}{$sort_key{$sort}} // '') 
                   or
                   $a cmp $b
                } keys %info_for;
   @voters = reverse @voters if $sort eq 'a';  #age is opposite of date of birth

   print "All voters, sorted by $sort_key{$sort}\n";
   for my $voter (@voters) {
      print "Name: $voter\n";
      my $dob = $info_for{$voter}{'Date of Birth (yyyy-mm-dd)'};
      my ($D, $M, $Y) = (localtime)[3,4,5];
      $M += 1;
      $Y += 1900;
      my $age = $Y - substr($dob, 0, 4);
      $age -= 1 if $M < substr($dob, 5, 2);
      $age -= 1 if $M == substr($dob, 5, 2) and $D < substr($dob, 8, 2);
      print "Age: $age\n";
      print "Party: ", $info_for{$voter}{'party affiliation'} // '<declined>', "\n";
      if (exists $info_for{$voter}{'intended candidate'}) {
         print "Voting Intent: ", $info_for{$voter}{'intended candidate'} // '<declined>', "\n";
      } else {
         print "Not yet polled\n";
      }
      print "\n";
   }
}

sub show_poll {
  
   my %voters_for;
   my %voters_by_party_for;
   my %voters_by_prev_votes_for; 
   for my $voter (keys %info_for) {
      my $cand = $info_for{$voter}{'intended candidate'};
      next unless defined $cand;
      my $party = $info_for{$voter}{'party affiliation'} // '<declined>';
      my $prev  = (($info_for{$voter}{'years voted (comma separated)'} // '') =~ tr/,//);
     
      $voters_for{$cand}++;
      $voters_by_party_for{$party}{$cand}++;
      $voters_by_prev_votes_for{$prev}{$cand}++;
   }

   print "Total votes for each candidate:\n";
   for my $cand (sort keys %voters_for) {
      print "$cand - $voters_for{$cand}\n";
   }

   print "\n";

   print "Total votes by party affiliation:\n";
   for my $party (sort keys %voters_by_party_for) {
      print "$party:\n";
      for my $cand (sort keys %{$voters_by_party_for{$party}}) {
         print "   $cand - $voters_by_party_for{$party}{$cand}\n";
      }
   }

   print "\n";

   print "Total votes by previous number of votes:\n";
   for my $prev (sort { $a <=> $b } keys %voters_by_prev_votes_for) {
      print "$prev previous votes:\n";
      for my $cand (sort keys %{$voters_by_prev_votes_for{$prev}}) {
         print "   $cand - $voters_by_prev_votes_for{$prev}{$cand}\n";
      }
   }
}

      


sub save_data {
   print "Enter a file name: \n";
   chomp (my $file = <STDIN>);
   store(\%info_for, $file);
}

sub load_data {
   print "Enter a file name: \n";
   chomp (my $file = <STDIN>);
   %info_for = %{ retrieve($file) };
}
