#!/usr/bin/perl

# DEFINES
my $USE_ADJUSTMENT = 1;

require "math.pl";
require "namemap.pl";

sub assign_letter_grade {
  my $rank = shift;
  my $total = shift;

  my $pos = $rank / $total;
  
  if ($pos < 1/16) { return 0; }
  elsif ($pos < 1/8) { return 1; }
  elsif ($pos < 1/4) { return 2; }
  elsif ($pos < 3/8) { return 3; }
  elsif ($pos < 1/2) { return 4; }
  elsif ($pos < 11/16) { return 5; }
  elsif ($pos < 12/16) { return 6; }
  elsif ($pos < 13/16) { return 7; }
  elsif ($pos < 14/16) { return 8; }
  elsif ($pos < 15/16) { return 9; }
  else { return 10; }
}

my @lgmap = ( 'A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-',
	      'D', 'F' );


sub assign_decile {
  my $rank = shift;
  my $total = shift;

  return 1 + 10 * ($rank  / ($total + 1));
}

# Input grades
my @ig = ();
my %tg = ();
my %sg = ();
my %taset = ();
my %stuset = ();

my @fields = ( "hw1", "hw2", "hw3", "hw4", "hw5", "hw6", "hw7", "hw8", "hw9",
	       "hw10", "mq1", "mq2", "mq3", "mq4", "mq5", "mq6", "mq7", "mq8", 
	       "mq9", "mq10", "mq11", "mq12", "mq13", "q1", "q2", "fx",
	       "tut" );

my @rfields = ( "hw1", "hw2", "hw3", "hw4", "hw5", "hw6", "hw7", "hw8", "hw9",
	       "hw10", "q1", "q2", "fx", "tut" );

my @afields = ( "hw1", "hw2", "hw3", "hw4", "hw5", "hw6", "hw7", "hw8", "hw9",
	       "hw10", "mq1", "mq2", "mq3", "mq4", "mq5", "mq6", "mq7", "mq8", 
	       "mq9", "mq10", "mq11", "mq12", "mq13", "tut" );

my @sfields = ( "q1", "q2", "fx" );

open GRADES, "<class.asc";
TOP: while (<GRADES>) {
  chop;
  @raw = split /:/;
  my $taname = shift @raw;
  if ($taname =~ /^\#(.*)$/) {
    # In the future we may wish to ignore this
    $taname = $1;
  }
  my $last = shift @raw;
  my $first = shift @raw;
  if (scalar @raw != scalar @fields) {
    print STDERR "Invalid line: $taname $last $first fields:" . scalar @raw . "\n";
    next TOP;
  }
  $taset{$taname} = 1;

  while (defined $stuset{$last}{$first}) {
    $last .= " X";
  }
  $stuset{$last}{$first} = 1;

  my @gl = ( $taname, $last, $first );
  my %gls = ();

  foreach my $field (@fields) {
    if (not scalar @raw) {
      print STDERR "Invalid line: $taname $last $first...processing anyway\n";
    }
    my $g = shift @raw;

    # Update TA grade list
    if (not defined $tg{$taname}{$field}{list}) {
      $tg{$taname}{$field}{list} = [];
    }
    if ($g ne "") {
      push @{ $tg{$taname}{$field}{list} }, $g;
    }

    if (not defined $sg{$field}{list}) {
      $sg{$field}{list} = [];
    }
    if ($g ne "") {
      push @{ $sg{$field}{list} }, $g;
    }

    $gls{$field}{raw} = $g;
  }

  push @gl, \%gls;
  push @ig, \@gl;
}

close GRADES;


# Find TA mean/stdev for all fields
foreach my $field (@fields) {
  foreach my $ta (keys %taset) {
    $tg{$ta}{$field}{mean} = mean(@{ $tg{$ta}{$field}{list} });
    $tg{$ta}{$field}{stdev} = std_dev(@{ $tg{$ta}{$field}{list} });
  }
}

# Find mean/stdev for synchronous fields
foreach my $field (@sfields) {
  $sg{$field}{mean} = mean(@{ $sg{$field}{list} });
  $sg{$field}{stdev} = std_dev(@{ $sg{$field}{list} });
}

# Recenter grades
foreach my $s (@ig) {
  foreach my $field (@afields) {
    $s->[3]->{$field}{rec} = renormalize($s->[3]->{$field}{raw},
					 $tg{$s->[0]}{$field}{mean},
					 $tg{$s->[0]}{$field}{stdev});
  }
  foreach my $field (@sfields) {
    $s->[3]->{$field}{rec} = renormalize($s->[3]->{$field}{raw},
					 $sg{$field}{mean},
					 $sg{$field}{stdev});
  }
}

# Aggregate and average
my %ffields = ( "hw" => 'hw\d+',
		"mq" => 'mq\d+',
		"q1" => 'q1',
		"q2" => 'q2',
		"fx" => 'fx',
		"tut" => 'tut' );
my %fmap = ();
foreach my $field (@fields) {
  foreach my $af (keys %ffields) {
    if ($field =~ /$ffields{$af}/) {
      $fmap{$field} = $af;
    }
  }
} 

# ... for recentered grades
foreach my $s (@ig) {
  foreach my $field (@fields) {
    if ($s->[3]->{$field}{raw} ne "") {
      if (not defined $s->[4]) {
	my %empty = ();
	$s->[4] = \%empty;
      }
      $s->[4]->{$fmap{$field}}{sum} = $s->[4]->{$fmap{$field}}{sum}
	+ $s->[3]->{$field}{rec};
      $s->[4]->{$fmap{$field}}{count} = $s->[4]->{$fmap{$field}}{count} + 1;
    }
  }
  foreach my $af (keys %ffields) {
    if (not $s->[4]->{$af}{count}) {
      $s->[4]->{$af}{avg} = 0;
    }
    else {
      $s->[4]->{$af}{avg} = $s->[4]->{$af}{sum} / $s->[4]->{$af}{count};
    }
  }
}

# ... for raw grades
foreach my $s (@ig) {
  foreach my $field (@fields) {
    if ($s->[3]->{$field}{raw} ne "") {
      if (not defined $s->[4]) {
	my %empty = ();
	$s->[4] = \%empty;
      }
      $s->[4]->{$fmap{$field}}{rawsum} = $s->[4]->{$fmap{$field}}{rawsum}
	+ $s->[3]->{$field}{raw};
      $s->[4]->{$fmap{$field}}{rawcount} = $s->[4]->{$fmap{$field}}{rawcount} + 1;
    }
  }
  foreach my $af (keys %ffields) {
    if (not $s->[4]->{$af}{rawcount}) {
      $s->[4]->{$af}{rawavg} = 0;
    }
    else {
      $s->[4]->{$af}{rawavg} = $s->[4]->{$af}{rawsum} / $s->[4]->{$af}{rawcount};
    }
  }
}

# Compute final recentered grades
my %ffperc = ( "hw" => .3,
	       "mq" => .05,
	       "q1" => .1,
	       "q2" => .1,
	       "fx" => .2,
	       "tut" => .25 );
my @finalgrades = ();
my %fgbyta = ();
foreach my $s (@ig) {
  my $o = 0;
  foreach my $af (keys %ffields) {
    $o += $s->[4]->{$af}{avg} * $ffperc{$af};
  }
  $s->[5]->{rec} = $o;
  push @finalgrades, $o;
  if (not defined $fgbyta{rec}{$s->[0]}) {
    $fgbyta{rec}{$s->[0]} = [ $o ];
  }
  else {
    push @{ $fgbyta{rec}{$s->[0]} }, $o;
  }
}

# Compute final raw grades (Only God knows why)
foreach my $s (@ig) {
  my $o = 0;
  foreach my $af (keys %ffields) {
    $o += $s->[4]->{$af}{rawavg} * $ffperc{$af};
  }
  $s->[5]->{raw} = $o;
  if (not defined $fgbyta{raw}{$s->[0]}) {
    $fgbyta{raw}{$s->[0]} = [ $o ];
  }
  else {
    push @{ $fgbyta{raw}{$s->[0]} }, $o;
  }
}

my $fg_mean = mean(@finalgrades);
my $fg_stdev = std_dev(@finalgrades);

print STDERR "Final mean: $fg_mean  std_dev: $fg_stdev\n";

# Determine rank
my @sgrades = sort { -($a->[5]->{rec} <=> $b->[5]->{rec}) } @ig;
my @slngrades = sort { $a->[1] cmp $b->[1] } @ig;

my $rank = 0;
foreach my $s (@sgrades) {
  $s->[6] = ++$rank;
  $s->[7] = assign_letter_grade($rank, scalar @sgrades);
}

# Print the grades in pretty and usable formats
open PRETTY, ">pretty.asc";
open FGRADES, ">final.asc";

print PRETTY "RECENTERED UNSORTED\n\n";
print PRETTY "Rank     TA     Last Name  First Name   Tut     HWG      MQ      Q1      Q2      FX    final   dec\n";
print PRETTY "---- ---------- ---------- ----------  ------  ------  ------  ------  ------  ------  ------  ---\n";

foreach my $s (@slngrades) {
  my $otut = $s->[4]->{tut}{avg};
  my $ohw = $s->[4]->{hw}{avg};
  my $omq = $s->[4]->{mq}{avg};
  my $oq1 = $s->[4]->{q1}{avg};
  my $oq2 = $s->[4]->{q2}{avg};
  my $ofx = $s->[4]->{fx}{avg};
  my $fg = $s->[5]->{rec};
  my $rank = $s->[6];
  my $decile = assign_decile($rank, scalar @ig);

  my $ln = $s->[1];
  my $fn = $s->[2];
  my $ta = $s->[0];

  # Adjust the homework grade for the TA
  # Fuckers
  #  my $adj = $taqadj{$ta};
  #  my $hg = $ohg + $adj;

  print FGRADES "$ta\t$ln\t$fn\t$otut\t$ohw\t$omq\t$oq1\t$oq2\t$ofx\t$fg\t$rank\n";
  printf PRETTY "%-4d %-10s %-10s %-10s  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %3d\n",
  $rank,
  (substr $ta,0,10), (substr $ln,0,10), (substr $fn,0,10),
  $otut,$ohw,$omq,$oq1,$oq2,$ofx,$fg,$decile;
}
close FGRADES;
close PRETTY;

open SORTED, ">sorted.asc";

print SORTED "RECENTERED SORTED\n\n";
print SORTED "Rank     TA     Last Name  First Name   Tut     HWG      MQ      Q1      Q2      FX    final   dec\n";
print SORTED "---- ---------- ---------- ----------  ------  ------  ------  ------  ------  ------  ------  ---\n";

foreach my $s (@sgrades) {
  my $otut = $s->[4]->{tut}{avg};
  my $ohw = $s->[4]->{hw}{avg};
  my $omq = $s->[4]->{mq}{avg};
  my $oq1 = $s->[4]->{q1}{avg};
  my $oq2 = $s->[4]->{q2}{avg};
  my $ofx = $s->[4]->{fx}{avg};
  my $fg = $s->[5]->{rec};
  my $rank = $s->[6];
  my $decile = assign_decile($rank, scalar @ig);

  my $ln = $s->[1];
  my $fn = $s->[2];
  my $ta = $s->[0];

  # Adjust the homework grade for the TA
  # Fuckers
  #  my $adj = $taqadj{$ta};
  #  my $hg = $ohg + $adj;

  print FGRADES "$ta\t$ln\t$fn\t$otut\t$ohw\t$omq\t$oq1\t$oq2\t$ofx\t$fg\t$rank\n";
  printf SORTED "%-4d %-10s %-10s %-10s  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %3d\n",
  $rank,
  (substr $ta,0,10), (substr $ln,0,10), (substr $fn,0,10),
  $otut,$ohw,$omq,$oq1,$oq2,$ofx,$fg,$decile;
}

print SORTED "\n\n";

close SORTED;

open TA, ">ta.asc";

# Finally, print out TA statistics
print TA "PER TA STATISTICS\n\n";
print TA "          ";
foreach $f (@rfields) {
  printf TA " %3s", (substr $f,0,3);
}
printf TA "\n";

print TA "          ";
foreach $f (@rfields) {
  print TA " ---";
}
printf TA "\n";

my @tas = sort keys %taset;
foreach $ta (@tas) {
  printf TA "%-10s", (substr $ta,0,10);

  foreach $f (@rfields) {
    printf TA " %3d", $tg{$ta}{$f}{mean};
  }
  printf TA "\n";

  print TA "          ";
  foreach $f (@rfields) {
    printf TA " %3d", $tg{$ta}{$f}{stdev};
  }
  printf TA "\n\n";
}

close TA;


# Output the raw grade list
open RAWGRADES, ">rawgrades.asc";

print RAWGRADES "RAW UNSORTED\n\n";
print RAWGRADES "dec                                  ";
foreach $f (@rfields) {
  printf RAWGRADES " %3s", (substr $f,0,3);
}
printf RAWGRADES "\n";

print RAWGRADES "---                                  ";
foreach $f (@rfields) {
  print RAWGRADES " ---";
}
printf RAWGRADES "\n";

foreach $s (@slngrades) {
  my $ln = $s->[1];
  my $fn = $s->[2];
  my $ta = $s->[0];
  my $rank = $s->[6];

  printf RAWGRADES "%-4d %-10s %-10s %-10s",
  $rank,
  (substr $ta,0,10), (substr $ln,0,10), (substr $fn,0,10);

  foreach $f (@rfields) {
    printf RAWGRADES " %3d", $s->[3]->{$f}{raw};
  }

  printf RAWGRADES "\n";
}

close RAWGRADES;


open RAWAGG, ">rawagg.asc";

print RAWAGG "NON-RECENTERED UNSORTED\n\n";
print RAWAGG "Rank     TA     Last Name  First Name   Tut     HWG      MQ      Q1      Q2      FX    final   dec\n";
print RAWAGG "---- ---------- ---------- ----------  ------  ------  ------  ------  ------  ------  ------  ---\n";

foreach my $s (@slngrades) {
  my $otut = $s->[4]->{tut}{rawavg};
  my $ohw = $s->[4]->{hw}{rawavg};
  my $omq = $s->[4]->{mq}{rawavg};
  my $oq1 = $s->[4]->{q1}{rawavg};
  my $oq2 = $s->[4]->{q2}{rawavg};
  my $ofx = $s->[4]->{fx}{rawavg};
  my $fg = $s->[5]->{raw};
  my $rank = $s->[6];
  my $decile = assign_decile($rank, scalar @ig);

  my $ln = $s->[1];
  my $fn = $s->[2];
  my $ta = $s->[0];

  # Adjust the homework grade for the TA
  # Fuckers
  #  my $adj = $taqadj{$ta};
  #  my $hg = $ohg + $adj;

  printf RAWAGG "%-4d %-10s %-10s %-10s  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %6.2f  %3d\n",
  $rank,
  (substr $ta,0,10), (substr $ln,0,10), (substr $fn,0,10),
  $otut,$ohw,$omq,$oq1,$oq2,$ofx,$fg,$decile;
}
close RAWAGG;











