#! /usr/local/bin/perl -w
use strict;
use Socket;

##################################################################
#   Score 2 weight
##################################################################

use vars qw ( %SCORE2WEIGHT  $USER_DEFINED_STRING );
$SCORE2WEIGHT{1} = 10;
$SCORE2WEIGHT{2} = 6;
$SCORE2WEIGHT{3} = 4;
$SCORE2WEIGHT{4} = 3;
$SCORE2WEIGHT{5} = 2;
$SCORE2WEIGHT{6} = 1;

$USER_DEFINED_STRING = "_user_";

##################################################################
#   flock constants
##################################################################
use vars qw ( $FLOCK_SH $FLOCK_EX $FLOCK_UN );
$FLOCK_SH = 1;
$FLOCK_EX = 2;
$FLOCK_UN = 8;


my @CONFIG_REQUIRED = ("type","title","votes");

use vars qw ( $cgi  $config );

use lib ".";
use CGI qw/:standard/;

##################################################################
#   Code
##################################################################
$cgi = new CGI;

my $cgi_url = "http://www.sdsc.edu/~bhuffake/survey/survey.cgi";

print $cgi->header();
my $config_file = $cgi->param("config");
#$config_file = "megatokyo.config";
$config = ProcessConfig( $config_file );
PrintPage( $cgi->param("page") );

sub PrintPage {
    my ($page_type) = @_;
#$page_type = "booth"; #DEBUG
    my $title = $config->{"title"};
    
    unless (defined $page_type) {
	$page_type = "main";
    }
    print "<head>\n";
    print "    <meta http-equiv=\"Pragma\" content=\"no-cache\">\n";
    print "    <title> $title Survey $page_type </title>\n";
    print "</head>\n";

    if ($page_type eq "top") {
	PrintTop($title);
    } elsif ($page_type eq "control") {
	PrintControl($config_file);

    } elsif ($page_type eq "intro") {
	PrintIntro($title);
    } elsif ($page_type eq "info") {
	PrintInfo($title);


    } elsif ($page_type eq "booth") {
	PrintBooth($config_file);
    } elsif ($page_type eq "count" || ($page_type eq "results")) {
	my %ip2score2vote;
	if ($page_type eq "count") {
	    %ip2score2vote = CountVotes();
	} else { 
	    %ip2score2vote = ReadVotes($config->{"votes"});
	}
	PrintResults(%ip2score2vote);

    } elsif ($page_type eq "relationship") {
	my %ip2score2vote = ReadVotes($config->{"votes"});
	PrintRelationship(%ip2score2vote);

    } else {
	PrintMain($config_file);
    }
    $cgi->end_html();
}

##################################################################
#   Support Pages
##################################################################

sub PrintTop {
    my ($title) = @_;

    print "    <b> $title Survey </b> \n";
    print "        written by <a href=\"mailto:bhuffaker\@sdsc.org\">\n";
    print "        Bradley Huffaker</a>.\n";
}

                      ############################################
sub PrintControl {
    my ($config_file) = @_;
    my $type = $config->{"type"};

    $cgi_url .= "?config=$config_file";
    print<<EOP;
    <br> <br>
    <font size = +1>
	<a href="$cgi_url\&page=intro" target="main">   intro page</a><br><br>
	<a href="$cgi_url\&page=booth" target="main">   voting booth</a><br><br>

	<a href="$cgi_url\&page=relationship" target="main"> voting relationships
	    </a><br>

	<a href="$cgi_url\&page=results" target="main">
results</a><br><br><br>

EOP
    foreach my $index (sort {$a<=>$b;} keys %{$config->{"url"}}) {
	my $url = $config->{"url"}{$index};
	my $title = $config->{"url_title"}{$index};
	my $target = $config->{"url_target"}{$index};
	print"	<a href=\"$url\" target=\"$target\"> $title </a><br><br>\n";
    }
    print "    </font>\n";
}    

                      ############################################
sub PrintIntro {
    my ($title) = @_;
  print<<EOP;
  <center>
        <br><br>
        <h1> Welcome to <br>
                the <br>
        $title Survey </h1>
  </center>
EOP
}
                      ############################################
sub PrintInfo {
    my ($title) = @_;
    print "<table boder=0>\n";
    print "<tr> <td>\n";
    print "    <table border=1>\n";
    my @score = reverse sort {$SCORE2WEIGHT{$a}<=>$SCORE2WEIGHT{$b};} 
	keys %SCORE2WEIGHT;
    print "            <tr> <th> rank </th> <th> weight </th></tr>\n";
    foreach my $score (@score) {
	print "        <tr> <td align=right> $score </th> ";
	print "     <td align=right> $SCORE2WEIGHT{$score} </th></tr>\n";
    }
    print "     </table>\n";
    print "  </td> <td>\n";
    my $type = $config->{"type"};
    my $num_scores = @score;
    print "You may select your top $num_scores $type"."s. <br>";
    print "This lists how a a given rank is wieghted ";
    print " when it is sumed for ranking between $type". "s. <br>\n";
    print "  </td> </tr>\n";
    print "</table>\n";
}

                      ############################################
sub PrintMain {
    my ($config_file) = @_;
    print<<EOP;
    <frameset rows="50, *">
	<frame src="$cgi_url?page=top\&config=$config_file">
	<frameset cols="150,*">
	  <frame src="$cgi_url?page=control\&config=$config_file" name="buttons">
	  <frame src="$cgi_url?page=intro\&config=$config_file" name="main">
	</frameset>
    </frameset>
EOP
}

##################################################################
#   Survey Pages
##################################################################
sub PrintBooth {
    my ($config_file) = @_;

    my $ip = $ENV{"REMOTE_ADDR"};
    my $host = $ENV{"REMOTE_HOST"};
    my $type = $config->{"type"};

    my %vote2score;
    my %ip2score2vote = ReadVotes($config->{"votes"});
    my @score = reverse sort {$SCORE2WEIGHT{$a}<=>$SCORE2WEIGHT{$b};} 
	keys %SCORE2WEIGHT;

    if ($ip2score2vote{$ip}) {
	print " You have already voted.<br>";
	print " You may always change your vote, but it will replace ";
	print "  your current one.\n";

	%vote2score = Vote2Score($ip, %ip2score2vote);
    } else {
	my $type = $config->{"type"};
	my $num_scores = @score;
	print "Please select your top $num_scores $type"."s. ";
	print $score[0]," is your most and ";
	print $score[$#score]," least favorite.\n";
    }
    print "<p/>\n";

    print $cgi->startform( "post", $cgi_url, $CGI::URL_ENCODED),"\n";
    print qq(<input type="hidden" name="page" value="count">\n);
    # Don't ask me why it keeps saying booth!
    #print $cgi->hidden({
	#name=>"page",
	#default=>["counter"]
	#}),"\n";
    print $cgi->hidden({
	name=>"config",
	default=>[$config_file]
	}),"\n";

    my $num_scores = @score;
    print " <table border=1> \n";
    print "   <tr> <td></td> <th colspan=$num_scores> ranking </th> </tr>\n";
    print "   <tr> <th> $type </th> ";
    foreach my $score (@score) {
	print " <th> $score </th> ";
    }
    print " </tr>\n";

    my %vote2total = Vote2Total(%ip2score2vote);
    my @votes = keys %vote2total;

    foreach my $vote  (sort @votes) {
	print "<tr> <td> ";
	#if ($object2url{$vote}) {
	#    print "<a href=\"$object2url{$vote}\" target=\"new\"> $vote </a> ";
	#} else {
	    print $vote;
	#}

	print " </tr>";
	foreach my $score (@score) {
	    print qq(<td align=center><input type="radio" );
	    if ($score eq $vote2score{$vote}) {
		print " checked ";
	    }
	    print qq(name="vote_$score" value="$vote"></td>);
	}
	print qq( </tr>\n);
    }
    foreach my $i (@score) {
	print "<tr> <td> ";
	my $name = $USER_DEFINED_STRING.$i;
	print $cgi->textfield({
	    name=>$name,
	    size=>15
	    });

	print " </tr>";
	foreach my $score (@score) {
	    print qq(<td><input type="radio" );
	    print qq(name="vote_$score" value="$name"></td>);
	}
	print qq( </tr>\n);
    }
    print "    </table>\n";
    print $cgi->submit({
	    name=>"action",
	    value=>"vote"
	    }),"\n";
    print $cgi->reset,"\n";
    print $cgi->endform,"\n";;
}

                      ############################################
sub PrintResults {
    my (%ip2score2vote) = @_;

    my %vote2score2freq;
    my %vote2total;
    foreach my $ip (keys %ip2score2vote) {
	foreach my $score (keys %{$ip2score2vote{$ip}}) {
	    my $vote = $ip2score2vote{$ip}{$score};
	    $vote2total{$vote} += $SCORE2WEIGHT{$score};
	    $vote2score2freq{$vote}{$score}++;
	}
    }

    my @scores = sort {$a<=>$b;} keys %SCORE2WEIGHT;
    my @votes = reverse sort {$vote2total{$a}<=>$vote2total{$b}} keys 
	%vote2total;

    if ($#votes < 0) {
	print "<center><b> Currently there are no votes.<b></center>";
    } else {
	my $type = $config->{"type"};
	my $num_scores = @scores;
	print "<table border=1>\n";
	print "  <tr> <th></th> <th> </th> ";
	print "  <th colspan=$num_scores> Number of Votes ";
	print "       <br> for each rank </th> <tr>\n";
	print "  <tr> <th></th><th> $type </th> \n";
	foreach my $score (@scores) {
	    print "    <th> $score </th>\n";
	}
	print " <th> score </th> ";
	print "  </tr>\n";

	my $index = 0;
	foreach my $vote (@votes) {
	    $index++;
	    my $total = $vote2total{$vote};
	    print " <tr>";
	    print "       <td> $index </td><td>";
	    print $vote;
	    print " </td>";
	    foreach my $score (@scores) {
		my $freq = $vote2score2freq{$vote}{$score};
		$freq = 0 if (!$freq);
		print " <td align=right> $freq </td> ";
	    }
	    print "       <td align=right> $total </td> ";
	    print " </tr>\n";
	}

	my $num_ips = keys %ip2score2vote;
	print "<tr> <td colspan=",$num_scores+3,"> &nbsp </td> </tr>\n";
	print "<tr> <th></th><th> weight </th>\n";
	foreach my $score (@scores) {
	    print "     <td align=right> $SCORE2WEIGHT{$score} </td>\n";
	}
	print "</tr>\n";
	print "<tr> <th></th><th> total votes </th> ";
	print " <td colspan=$num_scores> </td> <td align=right> ";
	print "         $num_ips </td></tr>\n";
	print "</table>\n";
	print "<br/>\n";
	print "<br/>\n";
    }
}

##################################################################
#   Survey Progress
##################################################################
sub CountVotes {

    my %score2vote;

    my %seen_vote;
    my @scores = sort {$a<=>$b;} keys %SCORE2WEIGHT;
    foreach my $score (@scores) {
	my $name = "vote_$score";
	if (defined $cgi->param($name)) {
	    my $vote =  $cgi->param($name);
	    if ($vote =~ /$USER_DEFINED_STRING\d+/) {
		$vote = $cgi->param($vote);
	    }
	    $vote =~ s/^\s+//;
	    $vote =~ s/\s+$//;
	    unless (defined $seen_vote{$vote}) {
		$score2vote{$score} = $vote;
	    } elsif ($seen_vote{$vote} < 2)  {
		print "<font color=red> error: </font> multiple votes \n";
		print " found for $vote.  Only the first will be used<p>\n";
	    }
	    $seen_vote{$vote}++;
	}
    }
    
    my $ip = $ENV{"REMOTE_ADDR"};
    my $votes_file = $config->{"votes"};
    my %ip2score2vote = ReadVotes($votes_file);

    my $ip_already_found;
    if (defined $ip2score2vote{$ip}) {
	$ip_already_found = 1;
	$ip2score2vote{$ip} = {};
    }

    foreach my $score (sort {$a<=>$b;} keys %score2vote) {
	$ip2score2vote{$ip}{$score} = $score2vote{$score};
    }

    #$if (defined $ip_already_found) {
	my %ip2bin;
	foreach my $ip (keys %ip2score2vote) {
	    my $bin = unpack("N",inet_aton($ip));
	    $ip2bin{$ip} = $bin;
	}

	rename $votes_file, $votes_file.".bkup";
	open(OUT, ">$votes_file") 
	    || DieHtml("Unable to open `$votes_file':$!");
	flock(OUT, $FLOCK_EX);
	foreach my $ip (sort {$ip2bin{$ip}<=>$ip2bin{$ip};} keys
	    %ip2score2vote) {
	    foreach my $score (sort {$a<=>$b;} keys %{$ip2score2vote{$ip}}) {
		print OUT "$ip:$score $ip2score2vote{$ip}{$score}\n";
	    }
	}
	flock(OUT, $FLOCK_UN);
	close OUT;
=cut
    } else {
	open(OUT, ">>$votes_file") 
	    || DieHtml("Unable to open `$votes_file':$!");
	flock(OUT, $FLOCK_EX);
	foreach my $score (sort {$a<=>$b;} keys %score2vote) {
	    if ($score2vote{$score} =~ /[^\s]/) {
		print OUT "$ip:$score $score2vote{$score}\n";
	    }
	}
	flock(OUT, $FLOCK_UN);
	close OUT;
    }
=cut
    chmod 0664, $votes_file;
    chmod 0664, $votes_file.".bkup";

    return %ip2score2vote;
}
                      ############################################

sub ReadVotes {
    my ($filename) = @_;

    unless (-f $filename) {
	return;
    }
    open (IN, "<$filename") 
	|| DieHtml("Unable to open votes file `$filename':$!");
    flock(IN, $FLOCK_SH);
    my %ip2score2vote;
    my $linenum = 0;
    while (<IN>) {
	s/#.*//g;
	$linenum++;
	if (/([^:]+):(\d+)\s+(.+)/) {
	    my ($ip, $score, $vote) = ($1, $2, $3);
	    $vote =~ s/^$USER_DEFINED_STRING//;
	    $vote =~ s/^\s+//;
	    $vote =~ s/\s+$//;
	    $ip2score2vote{$ip}{$score} = $vote;
	} else {
	    chop;
	    print "<font color=red> parse error `$filename'[$linenum]".$_
		."</font><br>\n";
	}
		
    }
    flock(IN, $FLOCK_UN);
    close IN;

    return %ip2score2vote;
}

                      ############################################
sub Vote2Score {
    my ($ip, %ip2score2vote) = @_;

    my %vote2score;
    foreach my $score (keys %{$ip2score2vote{$ip}}) {
	my $vote = $ip2score2vote{$ip}{$score};
	$vote2score{$vote} += $score;
    }
    return %vote2score;
}

                      ############################################
sub Vote2Total {
    my (%ip2score2vote) = @_;

    my %vote2total;
    foreach my $ip (keys %ip2score2vote) {
	foreach my $score (keys %{$ip2score2vote{$ip}}) {
	    my $vote = $ip2score2vote{$ip}{$score};
	    $vote2total{$vote} += $SCORE2WEIGHT{$score};
	}
    }
    return %vote2total;
}

##################################################################
#   Relationships
##################################################################
if (0) {
    my %ip2score2vote;
    my %vote2score;
    my %top_vote2vote2total;
    my @scores = reverse sort {$SCORE2WEIGHT{$a}<=>$SCORE2WEIGHT{$b};} 
	keys %SCORE2WEIGHT;
    foreach my $ip (keys %ip2score2vote) {
	my $top_vote = $ip2score2vote{$ip}{$scores[0]};
	if (defined $top_vote) {
	    foreach my $score (@scores) {
		my $vote = $ip2score2vote{$ip}{$score};
		if (defined $vote) {
		    $top_vote2vote2total{$top_vote}{$vote} +=
			$SCORE2WEIGHT{$score};
		    $vote2score{$vote} += $SCORE2WEIGHT{$score};
		}
	    }
	}
    }

    my @votes = reverse sort {$vote2score{$a}<=>$vote2score{$b};} keys 
	%top_vote2vote2total;

    print "<table border=1>\n";
    foreach my $top_vote (@votes) {
	print "  <tr> <th> $top_vote </th> ";
	my @votes = reverse sort {
	    $top_vote2vote2total{$top_vote}{$a}
	    <=> $top_vote2vote2total{$top_vote}{$b}}
	    keys %{$top_vote2vote2total{$top_vote}};
	shift @votes;
	foreach my $vote (@votes) {
	    print " <td> $vote </td> ";
	}
	print "</tr>\n";
    }
    print "</table>\n";
}

sub PrintRelationship {
    my %ip2score2vote = @_;

    my %vote2score;
    my %paths;
    my @scores = reverse sort {$SCORE2WEIGHT{$a}<=>$SCORE2WEIGHT{$b};} 
	keys %SCORE2WEIGHT;
    foreach my $ip (keys %ip2score2vote) {
	my $top_vote = $ip2score2vote{$ip}{$scores[0]};
	if (defined $top_vote) { # && $top_vote =~ /Boo/) {
	    my $current = \%paths;
	    foreach my $score (@scores) {
		my $vote = $ip2score2vote{$ip}{$score};
		unless (defined $vote) {
		    $vote = "none";
		}
		unless (defined $current->{$vote}) {
		    $current->{$vote} = {};
		}
		$current = $current->{$vote};
		$current->{"\0num_votes"}++;
		$vote2score{$vote} += $SCORE2WEIGHT{$score};
	    }
	}
    }

    SetNumChildren(\%paths);
    my @col;
    CreateRows(0, \@col, \%paths);
    PrintRows(\@col);
}

sub SetNumChildren {
    my ($paths,$parent, $space) = @_;
    my $height;
    foreach my $child (keys %{$paths}) {
	unless ($child =~ /^\0/) {
	    $height += SetNumChildren($paths->{$child},$child, $space.".");
	} 
    }
    if ($height == 0) {
	$height = 1;
    }
#print "$space $parent ($height)<br>";
    $paths->{"\0height"} = $height;
    return $height;
}

sub CreateRows {
    my ($parent_row, $rows, $children) = @_;
    
    my @children;
    foreach my $child (keys %{$children}) {
	unless ($child =~ /^\0/) {
	    push @children, $child;
	}
    }

    @children = reverse sort 
	{ $children->{$a}->{"\0num_votes"} <=> 
	  $children->{$b}->{"\0num_votes"}; }
	@children;

    my $row = $parent_row;
    foreach my $i (0..$#children) {
	my $child = $children[$i];
	push @{$rows->[$row]}, [$child, 
	    $children->{$child}->{"\0height"},
	    $children->{$child}->{"\0num_votes"}];
	$row += $children->{$child}->{"\0height"},
    }
    $row = $parent_row;
    foreach my $i (0..$#children) {
	my $child = $children[$i];
	
	CreateRows($row, $rows, $children->{$child});
	$row += $children->{$child}->{"\0height"},
    }
}

sub PrintRows {
    my ($row) = @_;

    print "<table border=1>\n";
    foreach my $col (@{$row}) {
	print "<tr>";
	foreach my $values (@{$col}) {
	    my ($vote, $hieght, $num_votes) = @{$values};
	    print "<td rowspan=$hieght> $vote ($num_votes) </td>\n";
	}
	print "</tr>";
    }
    print "</table>\n";
}

##################################################################
#   Configuration File
##################################################################

sub ProcessConfig {
    my ($filename) = @_;

    unless (defined $filename) {
	DieHtml("There must be a configuration file");
    }
    $filename =~ s/^\.\.//g;
    $filename = "configs/$filename";

    open (IN, "<$filename") 
	|| DieHtml("Unable to open config file `$filename':$!");
    my %config;
    my $linenum = 0;
    while (<IN>) {
	$linenum++;
	s/#.*//g;
	if (/([^=]+)=(.+)/) {
	    my ($key, $value) = ($1, $2);
	    $key =~ y/A-Z/a-z/;
	    if ($key =~ /url/) {
		if ($value =~ /^\s+(\d+)\s+(.*)/) {
		    my ($index, $value) = ($1, $2);
		    $config{$key}{$index} = $value;
		} else {
		    print "<font color=red>"
			." Unable to parse `$filename'[$linenum] $_"
			."</font><br>\n";
		}
	    } else {
		$config{$key} = $value;
	    }
	}
    }
    close IN;

    my @missing;
    foreach my $key (@CONFIG_REQUIRED) {
	unless (defined $config{$key}) {
	    push @missing, $key;
	}
    }
    if ($#missing > -1) {
	DieHtml("config `$filename' must contain:".join(", ", @missing));
    }
    return \%config;
}

sub DieHtml {
    print "\n<hr>\n";
    print "<strong> <font size = +3> Error: </font></STRONG> @_<br>\n";
    exit (-1);
}

