#!/s/std/bin/perl

#use Perl::Unsafe::Signals;
use lib '/u/c/s/cs302/public/html-www/forms';
use cs302;
use Pod::Usage;
use Text::Wrap;

# importation statements
# Getopt for command-line option processing; ANSIColor for error notification
use Getopt::Long;
use Term::ANSIColor qw(:constants);
use strict;
use File::Basename;
use DB_File;

#use Tie::File::AsHash;

$Term::ANSIColor::AUTORESET = 1;

our $VERSION = 2.2;

#constants for the &$LogMessage function
use constant ERROR => 1;
use constant WARNING => 2;
use constant MESSAGE => 3;

use lib '.';
use DBI;
use CSL::Pg;

###############################################################################
#
#
#   CONSTANTS
#
#
###############################################################################

###############################################################################
# TODO: Update these constants for each Assignment
###############################################################################

my $programNum              = 'p4'; # p1, p2, etc
my $handinDirName           = 'p4'; # p1, p2, p4_phase1, p4_phase2, etc
my $standardID              = 'main';
my $mainClass               = 'BattleSweeper';
my $mainClassFileName       = $mainClass . '.java';

my $EC_standardID           = 'extra';
my $EC_mainClass            = 'BattleSweeperEC';
my $EC_mainClassFileName    = $EC_mainClass  . '.java';
###############################################################################

###############################################################################
# TODO: Update these constants for each semester
###############################################################################

my $largeLectureInstructor  = 'deppeler';
my @allInstructors 	        = ("deppeler");  # list of instructors who don't grade
my $talogin                 = 'sbrown';   # CS login of TA who can answer questions about grading
###############################################################################

###############################################################################
#
#
#   CONFIGURATION VARIABLES
#
#
###############################################################################

###############################################################################
# SHOULD NOT NEED TO UPDATE THESE CONSTANTS OFTEN
###############################################################################

my $readmeFile              = '.README';
my $studentReadmeFile       = '.README';
my $outputFileName          = 'roster';
my $centralLogRoot          = '/p/course/cs302/private/grading';
my $configFile 		        = '/p/course/cs302/public/html-www/forms/cs302.config';

my $assignmentRootDir       = '/p/course/cs302/public/html/programs';
my $rosterRootDir           = "/p/course/cs302/public/html/programs/$programNum/grading";
my $makefile                = "$rosterRootDir/Makefile";
my $readingFromDB = 1;

my $mode = 'lecture';       # either 'lab' or 'lecture'
my $sort = 'name';          # lab, name, or login
my $logLevel = 5;           # 0 - 5
my $clean = 0;              # delete all existing files first?
my $printHelp = 0;          # print help from pod?
my @students;               # student logins

my $gradingDir;             # root dir for compilation, must be writable
my $classPath;              # passed to javac on the command line
my @baseJavaFiles;          # java files passed from JAVA_FILES var, with optional packages
my %javaFilePackages = ();  # packages for elements of @javaFiles
my @optionalJavaFiles = ("Review.java","ReviewEC.java"); # optional Java files
my @otherFiles;             # questions.txt, JavaDoc, etc
my @optionalOtherFiles;     # Java files for extra credit


# lecture-mode only options
my @excludedStudents = ();  # students to exclude from the lecture
my @extraStudents = ();     # students to add to the list


# top-level messages
my @topLevelMessages = ();

# If only lab TA's are going to evaluate set onlylabmode to 1;
#
# If some Instructors are going to evaluate their students while others are not then
# set onlylabmode to 1 and change @allInstructors to include those who are not evaluating
#
# If all small section instructors are going to evaluate their students then
# set onlylabmode to 0
#
my @currentLectures;
my $onlylabmode = 1;
###############################################################################

###############################################################################
# Compute current year, month, and semester name
# The subfolder in handin and the db schema are named 201209 etc.
# and handin files are found in this sub directory
###############################################################################
my $year;
my $yearmonth; # 201209 for subfolder in handin for this semester
my $semester;
{
    my @timeComponents = localtime time;
    $year = $timeComponents[5] + 1900;
    my $month = $timeComponents[4] + 1;
    if ($month==1 or $month==2 or $month==3 or $month==4 or $month==5) {
        $semester = 'Spring';
		$yearmonth = $year . "01";
    }
    elsif ($month==6 or $month==7 or $month==8) {
        $semester = 'Summer';
		$yearmonth = $year . "06";
    }
    elsif ($month==9 or $month==10 or $month==11 or $month==12) {
        $semester = 'Fall';
		$yearmonth = $year . "09";
    }
}
###############################################################################

###############################################################################
# figure out the username / home directory of the user currently executing the script
my ($taUserName, undef, undef, undef, undef, undef, undef, $taHome) = getpwuid($<);
###############################################################################
    
###############################################################################
# run time
my $time = `date "+%Y-%m-%d %H:%S:%M"`;
chomp $time;
###############################################################################

print"
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xx Processing $programNum Handin for students assigned to $taUserName $semester $year xxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n";

#
#
#   UTILITY FUNCTIONS
#
#

# creates warning and error strings for the log function / notice list
sub MakeErrorString   {  return (shift)  }
sub MakeWarningString {  return (shift)  }

# handindir($lab,$login,$yearmonth)
sub handindir {
    # set the current semester's directory handin/201209/login/p1, etc.
    return "/p/course/cs302/handin/$yearmonth/$_[1]/$handinDirName";
}

# Log($level,$error,$text);
# Prints $text with the appropriate level of indentation
# If $error is true, prints the message in scary bold-red
sub Log {
    my ($level,$error,$text) = @_;
    if (not defined $logLevel or $level <= $logLevel) {
        print (' ' x (2 * ($level-1)));
        print ($error ? MakeErrorString("$text\n") : "$text\n");
    }
}

# creates string for the log database
# LogString($partnerName, $submitted)
sub LogString {
    my ($partnerName, $submitted) = @_;
    my $base = "$time ($mode)";
    my $submittedString = $submitted ? '(this student submitted)' : '(partner submitted)';
    return $partnerName ? "$base - partner $partnerName $submittedString" : $base;
}

#
#
#   OPTION PARSING
#
#

my @extraDefinitions;

# take options from ENV if they're present
my ($key, $value);
while (($key, $value) = each(%ENV)) {
    if ($key =~ /^CS302_(.+)/) {
        unshift @ARGV, "--$1=$value";
    }
}


my $graderLogin = getlogin();
$mode = 'lab';

my $fchar = substr($graderLogin,0,1);
my $schar = substr($graderLogin,1,1);

# Create a directory in the ta's filespace to hold all student grade files
system 'mkdir','-p',"/afs/cs.wisc.edu/u/$fchar/$schar/$graderLogin/cs302_results_$programNum";
$gradingDir = "/afs/cs.wisc.edu/u/$fchar/$schar/$graderLogin/cs302_results_$programNum";
$classPath = '/afs/cs.wisc.edu/p/course/cs302/public/html/programs/$programNum/grading';


#print "Reading the $configFile\n";
my %config = readConfig($configFile);

#print "Making the DB connection\n";
my $dbh = makeDbConnection(\%config);
$dbh->{ChopBlanks} = 1;
makeDbNonQuery($dbh, "ALTER ROLE ". $graderLogin . " SET search_path = '" . $config{'db_search_path'} . "'"); 

#print "Search path = " . $config{'db_search_path'} . "\n";

# DEB: changed category_id to assignment_id
# DEB: changed report_file_id='main' to standard_id='main'
my $query = "SELECT student_login From grader_assignments WHERE grader_login='$graderLogin' AND assignment_id='$programNum' AND standard_id='$standardID'";
#print "QUERY: $query \n";

my @tempResult = makeDbQuery($dbh, $query, ['student_login']);
my $tempResultSize = @tempResult;
print "Hello, $graderLogin. Let's start grading!! ";
print "If you have any question, email ($talogin at wisc.edu)\n\n";
print "Created the $gradingDir \n";
print "<List of your Students(#:$tempResultSize)>\n";

my @students;
for (my $count = 0; $count < $tempResultSize; $count++) {
        $students[$count] = $tempResult[$count]{student_login};
	print $students[$count].", ";
}
print "\n\n";

#my @students =('kpark38');
$sort='name';

 
# Main program class name
@baseJavaFiles = ($mainClassFileName); # TODO add other class names here

# Extra Credit File Name
@optionalOtherFiles = ($EC_mainClassFileName); # TODO add other class names here

print "programNum = $programNum \n";

# process_recompile script which is responsible for running the tests.
my $process_recompile = "/p/course/cs302/public/html/programs/${programNum}/grading/${programNum}_process_recompile";

# default HANDIN_DIR_NAME with PRORGRAM_NUM
$handinDirName ||= $programNum;

# process interpolations
my %interpolations = ();
sub ProcessInterpolations {
    my ($valueRef) = shift;
    if (ref $valueRef eq 'SCALAR') {
        $$valueRef =~ s/\$(\w+)/exists $interpolations{$1} ? $interpolations{$1} : "\$$1"/eg;
        $$valueRef =~ s/\$\{(\w+)\}/exists $interpolations{$1} ? $interpolations{$1} : "\$$1"/eg;
    }
    else {
        foreach (@$valueRef) {
            $_ =~ s/\$(\w+)/exists $interpolations{$1} ? $interpolations{$1} : "\$$1"/eg;
            $_ =~ s/\$\{(\w+)\}/exists $interpolations{$1} ? $interpolations{$1} : "\$$1"/eg;
        }
    }
}            

$interpolations{USER} = $taUserName;
$interpolations{HOME} = $taHome;
$interpolations{MODE} = $mode;
$interpolations{ASSIGNMENT_DIR} = "$assignmentRootDir/$programNum";

ProcessInterpolations \$gradingDir;
ProcessInterpolations \$programNum;
$interpolations{PROGRAM_NUM} = $programNum;
ProcessInterpolations \$classPath;
$interpolations{CLASSPATH} = $classPath;

while (@extraDefinitions) {
    my $name = shift @extraDefinitions;
    my $value = shift @extraDefinitions;
    ProcessInterpolations \$value;
    $interpolations{$name} = $value;
}

ProcessInterpolations \@students;


if ($printHelp) {
    pod2usage(  -msg => '' , 
                -exitval => 1,
                -verbose => 2,
                -output => \*STDOUT );
}

#
#
#   OPTION PROCESSING AND CLEANSING
#
#

# split up filenames that were joined with spaces and convert to packages
my @javaFiles = ();
foreach my $javaFile (split(/\s+/,join(' ',@baseJavaFiles,@optionalJavaFiles))) {
    my ($fileName, $package) = split ':', $javaFile;
    if ($package) {
        my $packageDir = $package;
        $packageDir =~ s|\.|/|g;
        $javaFilePackages{$fileName} = $packageDir;
    }
    push @javaFiles, $fileName;
	print "Added $fileName to list of java files expected \n";
}

@otherFiles         = split(/\s+/,join(' ',@otherFiles));
@optionalOtherFiles = split(/\s+/,join(' ',@optionalOtherFiles));
@optionalJavaFiles  = split(/\s+/,join(' ',@optionalJavaFiles));
@students           = split(/\s+/,join(' ',@students));
@excludedStudents   = split(/\s+/,join(' ',@excludedStudents));
@extraStudents      = split(/\s+/,join(' ',@extraStudents));

# check to make sure the grading directory exists
if (not -d $gradingDir) {
    print stderr "Error: Grading directory '$gradingDir' does not exist\n";
    exit -1;
}

# make sure we're in a reasonable mode
if ($mode ne 'lecture' and $mode ne 'lab') {
    print stderr "Error: invalid mode '$mode'\n";
    exit -1;
}

# print help if requested, or if we do not have all the necessary options
if ($printHelp
    or not $gradingDir or not @javaFiles or not $classPath or not $programNum
    or ($mode eq 'lab' and not @students))
{
    pod2usage(  -msg => '' , 
                -exitval => 1,
                -verbose => 2,
                -output => \*STDOUT );
}


my %config = readConfig($configFile);
$dbh->disconnect(); # Disconnect before reconnecting.
my $dbh = makeDbConnection(\%config);
$dbh->{ChopBlanks} = 1; # Use this to trim whitespace
                        # from shorter logins


#
#
#   SETUP
#
#


# clear out old data if requested
mkdir "$gradingDir/students";
if ($clean) {
    Log 1, 0, "Cleaning out old files";
    `rm -rf $gradingDir/students/*`;
    #*/
}

# output roster 
open my $studentData, '>', "$gradingDir/$outputFileName";

# open central logging database
my $centralLogDirectory = "$centralLogRoot/$year-$semester/$programNum";
#system 'mkdir', '-p', $centralLogDirectory;
my %gradingRecords;
#tie my %gradingRecords, 'Tie::File::AsHash', "$centralLogDirectory/$programNum-$taUserName.txt", split => "\t"
#    or die "Problem tying \%gradingRecords: $!";


sub FindStudentRecord($@) {
    my ($studentLogin, @instructors) = @_;
    my %studentRecord;
    
    # make sure this student wasn't already graded by another TA
 
	#newnewnewnew
	my $query = "SELECT login, lab, name FROM people WHERE login=\'$studentLogin\'"; 
	#my $query = "SELECT * from students";
	my @queryResult = makeDbQuery($dbh, $query, ['login', 'lab', 'name']);
	#for (my $i=0; $i<=$#queryResult
	#print "test $queryResult[0]{name}, $queryResult[0]{login}, $queryResult[0]{lab}\n";
	#exit;
	return {	lastName => $queryResult[0]{name},
				section => 0,
				firstName => "",
				login => $queryResult[0]{login},
				lab => $queryResult[0]{lab}
			};	
			
	#"SELECT login, lab 
	#FROM students ORDER BY login", ['login', 'lab']);

}

Log 1, 0, "Building student list for $taUserName\n";
my @studentRecords = ();
my $currentLecture;


if ($mode eq 'lecture') {
    @students = split "\n", `cat $rosterRootDir/$taUserName/*.logins`; #*/
    $currentLecture = $taUserName;
}

else {
    # lab mode
    if ($onlylabmode == 1) {
		@currentLectures = @allInstructors;
	}
	else {
		$currentLecture = $largeLectureInstructor;
	}
}


# remove any students from the excluded list
my %excludedStudentsHash = map { ($_ => 1) } @excludedStudents;
@students = grep { not exists $excludedStudentsHash{$_} } @students;

# add each student from the current-lecture list
foreach my $studentLogin (@students) {
	if ($onlylabmode == 1) {
		push @studentRecords, FindStudentRecord($studentLogin, @currentLectures);
	}
	else {
		push @studentRecords, FindStudentRecord($studentLogin, $currentLecture);
	}
}

# add additional students, from any lecture
foreach my $extraStudent (@extraStudents) {
    my ($lecture, $login) = split ':', $extraStudent;
    push @studentRecords, FindStudentRecord($login, $lecture);
}

#
#
#   PROCESS STUDENT SUBMISSIONS
#
#

Log 1, 0, "Processing assignment $programNum in $mode mode for grader '$taUserName'...";

# if we can't parse readme files, unknown partners will be assigned one of these usernames
my $unknownPartnerIndex = 0;


# process the student records
foreach my $student (@studentRecords) {
    # skip blank records of students that were graded by other TAs
    next if not $student;
    my ($lastName, $firstName, $id, $login, $labNumber) = @$student{qw(lastName firstName id login lab)};
    Log 2, 0, "\nProcessing files for student '$login'";
    
    $gradingRecords{$login} = &LogString;
    
    my $filesToGrade  = 1;  # did this student turn in files we have to grade?
    my $partnerHandin = 0;  # did the partner hand in files?
    my $compileErrors = 0;  # did we encounter any compilation errors?
    my @flags = ();         # extra info for the roster file: worked with a partner, was missing a file, etc
    my $fileCount = 0;      # count how mIany files the student turned in
    
    # function that prints to the console and writes a message to the log. Parameters
    #   $status: one of ERROR, WARNING, MESSAGE. Determines the redness / boldness of the output
    #   $level: Log level. If 0, will not be printed to the console.
    #   $message: Message text. Should be short.
    my $LogMessage = sub {
        my ($status, $level, $message) = @_;
        if ($status==ERROR) {
            if ($level) {
                Log $level, 1, $message;
            }
            push @flags, MakeErrorString($message);
        }
        elsif ($status==WARNING) {
            if ($level) {
                Log $level, 0, $message;
            }
            push @flags, MakeWarningString($message);
        }
        elsif ($status==MESSAGE) {
            if ($level) {
                Log $level, 0, $message;
            }
            push @flags, $message;
        }
    };
        
    # make the grading directory
    my $studentDir = "$gradingDir/students/$login";
    $interpolations{STUDENT_DIR} = $studentDir;
    $interpolations{STUDENT} = $login;
    my $handinDir = handindir $labNumber, $login, $yearmonth;
    mkdir $studentDir;
    
	#extra credit submitted or not. Variable is set if the optional file is found
	my $extraCredit = 0;
	my $tempCounter=0;
	my $extraCredit1=0;
	my $extraCredit2=0;
	
	# creating logging directory and name-based symlink
   
    # Create list of almost all files (not . and ..  , but does include .downloads and .reports)
    my @handinFiles = split /\s+/, `ls -A $handinDir`;
    
    # check for a readme file

	if ($readingFromDB == 1)
	{

		my $partnerLogin;
		my $studentLogin = $login;

        # DEB: changed for Fall 2012
		my $query = "SELECT lead_login, member_login FROM team_members WHERE confirmed = 't' \n " .
			" AND assignment_id = '$programNum' \n " .
			" AND lead_login is not null\n " . 
			" AND (lead_login = \'$studentLogin\' OR \n member_login = \'$studentLogin\')"; 

		#print "QUERY: $query \n";
		my @queryResult = makeDbQuery($dbh, $query, ['lead_login', 'member_login']);
		if ($#queryResult > -1)
		{
            Log 3, 0, "Student worked with a partner";
			&$LogMessage(MESSAGE, 0, "Student worked with a Partner");
			if ($queryResult[0]{lead_login} eq $studentLogin) {
				$partnerLogin = $queryResult[0]{member_login};
			}
			else {
				$partnerLogin = $queryResult[0]{lead_login};
			}	
			
			my $query1 = "SELECT name, login, lab FROM people WHERE login = \'$partnerLogin\'";
			my @q1Result = makeDbQuery($dbh, $query1, ['name', 'login', 'lab']);
            #&$LogMessage(MESSAGE, 0, "Partner: $p2Data{'Last Name'}, $p2Data{'First Name'} (".$p2Data{'Lab Section'}."/$p2Data{Login})");
			if ($#q1Result > -1) {
				&$LogMessage(MESSAGE, 0, "Partner Login: $partnerLogin, Lab: $q1Result[0]{'lab'}, Name: $q1Result[0]{'name'}");
			}
			else {
				&$LogMessage(MESSAGE, 0, " Partner login: $partnerLogin, Could not retrieve other information");
			}	
				

			#if ($queryResult[0]{submitting} eq $studentLogin) {
			# DEB: changed submitting to lead_login
			if ($queryResult[0]{lead_login} eq $studentLogin) {
				&$LogMessage(MESSAGE, 0, "This student handed in files");
    	        Log 4, 0, "This student handed in files";
                $gradingRecords{$login} = &LogString($partnerLogin,1);
                $gradingRecords{$partnerLogin} = &LogString($login,0);
			}
			else {
				&$LogMessage(MESSAGE, 0, "Partner handed in files");
    	        Log 4, 0, "Partner handed in files";
        	    delete $gradingRecords{$login};
            	$filesToGrade = 0;
				$partnerHandin = 1;
			}	
		}
		else {
			&$LogMessage(MESSAGE, 0, "This student worked alone");
   	        Log 4, 0, "This student worked alone";
		}	
	}
	
	else
	{

		
    my $hasReadme = 0;
	my $studentReadmeFile = $readmeFile;
    	{
        STUDENT_FILES: foreach my $studentFile (@handinFiles) {
            if (lc $studentFile eq lc $readmeFile) {
                $studentReadmeFile = $studentFile;
                $hasReadme = 1;
                if ($studentFile ne $readmeFile) {
                    &$LogMessage(WARNING, 0, "$readmeFile miscased: '$studentFile'");
                }
                last STUDENT_FILES;
            }
        }
        
		# README files are no longer used to define teams.  Teams are found by db query 
        if ($hasReadme) {
            Log 3, 0, "Student worked with a partner";
            system 'cp', "$handinDir/$studentReadmeFile", "$studentDir/$studentReadmeFile";
            chmod 0600, "$studentDir/$studentReadmeFile";
			print "Found and copied $studentDir/$studentReadmeFile \n";
            
            # data for the first and second partners in README.txt
            my @partnerData = ({ }, { });
        
			my $partnerNum = 0;
            # parse apart the README
            open my $partnerFile, '<', "$handinDir/$studentReadmeFile";
            while (<$partnerFile>) {
                s/\s*$//;   # stupid DOS-formatted text files - who uses carriage returns in this day and age?
                #next unless /^Partner/; # skip unimportant lines
            
                #my ($partnerNum, $lineRecord, $data) = /^Partner (\d)\s+([^:]+):\s*(.+)$/;
                #$partnerData[$partnerNum-1]{$lineRecord} = $data;

				if (/^PARTNER/) {
					$partnerNum++;
					next;
				}
				else { 
					if (/^Assignment/){
						next;
					}
					else {
						if (/^Date/) {
							next;
						}
						else {
							if (!(/:/)) {
								next;
							}
						}
					}
				}
				my ($lineRecord, $data) = /^\s*([^:]+)\s*:\s*(.+)\s*$/;
				$partnerData[$partnerNum-1]{$lineRecord} = $data;
				#print "$_\n";
				#print "line $lineRecord, $data\n";
            }
            
            # add a note that this student worked with a partner
            my %p2Data = %{$partnerData[ lc($partnerData[0]{Login}) eq lc($login) ? 1 : 0 ]};
            &$LogMessage(MESSAGE, 0, "Partner: $p2Data{'Last Name'}, $p2Data{'First Name'} (".$p2Data{'Lab Section'}."/$p2Data{Login})");
            my $partnerLogin = lc $p2Data{'Login'};
            $fileCount++;
        
            # check to see if this partner handed in anything
            my ($partnerLabSection) = ($p2Data{'Lab Section'} =~ /(\d\d\d)/);
            my $lcPartnerHandinDir = handindir($partnerLabSection, $partnerLogin);
            #Log 4, 0, "Checking partner's directory '$lcPartnerHandinDir' for files";
            if (grep { -f "$handinDir/$_"} @javaFiles) {
                Log 4, 0, "This student handed in files";
                if (not $partnerLogin) {
                    $partnerLogin = "unknown-$unknownPartnerIndex";
                    $unknownPartnerIndex++;
                }
                $gradingRecords{$login} = &LogString($partnerLogin,1);
                $gradingRecords{$partnerLogin} = &LogString($login,0);
            }
            else {
		
				my $partnerH = 1;
				foreach my $hf (@handinFiles) {
					if (@javaFiles =~ m/$hf/i) {
						$partnerH = 0;
						print "$hf\n";
					}
				}
				if ($partnerH == 1) {
					&$LogMessage(MESSAGE, 0, "Partner handed in files");
    	            Log 4, 0, "Partner handed in files";
        	        delete $gradingRecords{$login};
            	    $filesToGrade = 0;
                	$partnerHandin = 1;
				}
            }
            
            if ($partnerLogin and $partnerLabSection) {
                Log 4, 0, "Partner login: $partnerLogin";
                # check for the partner's readme file
                my $partnerReadmeFile = '';
                PARTNER_FILES: foreach my $partnerFile (split /\s+/, `ls -A $lcPartnerHandinDir 2>/dev/null`) {
                    if ($partnerFile eq $readmeFile) {
                        $partnerReadmeFile = $partnerFile;
                        last PARTNER_FILES;
                    }
                    elsif (lc $partnerFile eq lc $readmeFile) {
                        &$LogMessage(WARNING, 0, "Partner $readmeFile miscased: '$partnerFile'");
                        $partnerReadmeFile = $partnerFile;
                        last PARTNER_FILES;
                    }
                }

                if ($hasReadme and not $partnerReadmeFile) {
                    &$LogMessage(ERROR, 4, "Missing partner $readmeFile");
                }
                
            }
            else {
                &$LogMessage(ERROR, 4, "Could not parse README.txt");
            }
        }
    }
    } 
    
    # process java files
    #if (not @handinFiles =~ /java/){
    #    $filesToGrade = 0;
    #    &$LogMessage(ERROR, 3, "Partner Handed in File");
	#}	
    if (not $partnerHandin and not @handinFiles) {
        $filesToGrade = 0;
        &$LogMessage(ERROR, 3, "Handin directory empty");
    }
    elsif (not $partnerHandin) {
        # copy files
        Log 3, 0, "Copying files";
        foreach (@javaFiles) {
            my $package = $javaFilePackages{$_};
            if (-f "$handinDir/$_") {
                if (not -f "$studentDir/$package/$_") {
                    if ($package) {
                        system 'mkdir', '-p', "$studentDir/$package";
                    }

                    Log 4, 0, "Copying $_";
                    system 'cp', "$handinDir/$_", "$studentDir/$package/$_";
                    chmod 0600, "$studentDir/$package/$_";
                }
				else {
                    Log 4, 0, "Copying $studentDir/$_";
                    system 'cp', "$handinDir/$_", "$studentDir/$_";
                    chmod 0600, "$studentDir/$_";
				}
                $fileCount++;
                if ((join(" ", @optionalJavaFiles)) =~ /$_/) {
                    &$LogMessage(MESSAGE, 4, "Optional file $_ handed in");
                }
            }
            elsif ((join(" ", @optionalJavaFiles)) !~ /$_/) {
                &$LogMessage(ERROR, 4, "Missing file: $_");
            }
        }
        
        foreach (@otherFiles) {
            if (-f "$handinDir/$_") {
                system 'cp', "$handinDir/$_", "$studentDir/$_";
                chmod 0600, "$studentDir/$_";
            }
            else {
                &$LogMessage(ERROR, 4, "Missing file: $_");
            }
        }
	
	
    foreach (@optionalOtherFiles) {
        $tempCounter++;
# When you have 2 input files for extra credit.
#	$extraCredit1=1;
#	$extraCredit2=1;

		if (-f "$handinDir/$_") {
			if($tempCounter == 1){
				$extraCredit1=1;
# When you have 2 input files for 1 extra credit program.
				$extraCredit2=1;

			}
			elsif($tempCounter==2){
				$extraCredit2=1;
			}

            system 'cp', "$handinDir/$_", "$studentDir/$_";
            chmod 0600, "$studentDir/$_";
            &$LogMessage(MESSAGE, 4, "Handed in optional file '$_'. Student will be evaluated for extra credit");
			$extraCredit=1;
		}
	}
        
	# check for extra files, and copy any files that are not . .. .downloads .reports (or other hidden files)
	print "CHECK FOR EXTRA FILES\n";
    foreach my $fileName (@handinFiles) {
	    # if it is not in one of the expected file lists, it is an excess file
		# not all excess files should be copied
		# some excess files were renamed (this should probably not be done now)
		
		# DO NOT CONSIDER FILE TO BE EXTRA IF EXPLICITLY INCLUDED IN ANOTHER FILE SET
        if (not ((grep /$fileName/, @javaFiles, @otherFiles, @optionalJavaFiles, @optionalOtherFiles) or $fileName eq $studentReadmeFile)) {
		
			my $excessFile = 1;
			# if extra file is one of java files, copy and rename correctly 
            #foreach my $jf (@javaFiles) {
			#	if ($jf =~ /$fileName/i) {  # if jf matches ignoring case, rename correctly
			#                               #(UNNEEDED??? NOW THAT FORMS FORCES CORRECT FILENAMES)
			#		system 'cp', "$handinDir/$fileName", "$studentDir/$jf";
            #		chmod 0600, "$studentDir/$jf";
			#
			#		&$LogMessage(WARNING, 4, "Miscased file '$fileName'\n has been renamed in $studentDir ");
			#		&$LogMessage(WARNING, 4, "Run process_uncompiled for this student");
			#		$excessFile = 0;
			#	}
			#}

			# DON'T COPY .downloads and .reports or other hidden files
            if ($fileName =~ m/^\./i) {
                $excessFile = 0 # Don't sweat it over hidden files
            }

			# DO Copy other excess file to students grading directory
			if ($excessFile == 1) {
	            &$LogMessage(WARNING, 4, "Found and copied excess file '$fileName'");
				system "cp $handinDir/$fileName $studentDir/$fileName";
          		chmod 0600, "$studentDir/$fileName";
				#print "Copied $fileName to $studentDir\n";

			}
        }
    }

	# copy the Makefile
	system "cp $makefile $studentDir/Makefile";

	# run the make file
	#system "$studentDir/make";

  }
   
  # compile source code and run tests
  if (!$partnerHandin) {
    Log 5, 0, "Running $process_recompile";
    system("$process_recompile $login")
  }

  # add grading mark and notes to the student's record
  #$$student{gradeMark} = ($filesToGrade ? ON_GREEN('X') : ' ');
  $$student{gradeMark} = ($filesToGrade ? 'X' : ' ');
  $$student{notes} = \@flags;
  $$student{name} = "$$student{lastName}, $$student{firstName}";
}

#
#
#   PRINT OUTPUT
#
#

$Text::Wrap::columns = 100;
my $tab = ' ' x 50;

# sort by lab, login, or name (defined as 'lastName, firstName' above)
my @sortedRecords = sort { $$a{$sort} cmp $$b{$sort} } @studentRecords;

#my $separator = ON_BLUE(' ');
my $separator = ' ';

my $messages = join "\n  ", ('', @topLevelMessages);

open my $studentFile, '>', "$gradingDir/$outputFileName";
print $studentFile <<"END";
process_handin for assignment $programNum
Run $time by $taUserName in $mode mode

Messages:$messages


LastName          FirstName      Lab  Login       Notes
-----------------------------------------------------------------------------------------
END

foreach my $studentRecord (@sortedRecords) {
    my %studentHash = %$studentRecord;
    printf $studentFile "%-17s %-13s  %s  %-8s %s$separator %s\n",
        @studentHash{qw{lastName firstName lab login gradeMark}}, shift @{$studentHash{notes}};
    foreach my $note (@{$studentHash{notes}}) {
        print $studentFile "$tab$note\n";
    }
}
close $studentFile;


# close the log file
untie %gradingRecords;

# Close the db connection
$dbh->disconnect();
print "\n"


__END__


=head1 NAME

process_handin - Copy and compile CS302 program submissions

=head1 SYNOPSIS 

process_handin [--help] [--clean] [--configfile=filename] --option1 value1 ...

=head1 OPTIONS 

=over 8 

=item B<--clean> 

Completely removes all student subdirectories in the grading directory

=item B<--help> 

Prints this help.

=item B<--configfile=filename>

Reads in grading configuration from a file. The file should have one option per line, in C<NAME=value> format. Blank lines and lines commented with C<#> will be ignored.

=item B<--option value>

Sets a grading configuration option. See the "Configuration Options" section for more information on available options.

=back 

=head1 DESCRIPTION

C<process_handin> is a program that sets up a grading environment for the University of Wisconsin Computer Science 302 class. It copies student program files to a separate directory, compiles them, runs optional autograders, and reports errors and missing files.

=head2 Processing Modes

C<process_handin> can be run in two modes: lab and lecture. Lab mode should be used by lab TAs, who will be grading students from the large lectures. Lecture mode is for use by the small-lecture instructors. In lab mode, you must specify the specific student logins and their lab numbers. In lecture mode, the program will use your username find the student files from your section automagically.

=head2 Configuring process_handin

C<process_handin> is controlled by numerous configuration options. These options can be specified on the command line, in the form C<--OPTION_NAME=value> or C<--OPTION_NAME value>. When specifying option values with spaces on the command line, surround the option with double quotes: 
    
    process_handin --MODE=lab --SORT name --JAVA_FILES "MyClass.java MyTesterClass.java"

Configuration options can also be passed as environment variables, in which case the name must be prepended with C<CS302_>. E.g., the option C<GRADING_DIR> can be passed as the environment variable C<CS302_GRADING_DIR>.

You can also put all the configuration options into a single text file, and specify that file name on the command line with the option C<--configfile=filename>. This file should contain one option per line, in C<KEY_NAME=value> format. Any blank lines or lines starting with a C<#> will be ignored. For readability, there can be extra space around the equals sign, so that the following two lines are equivalent:

    JAVA_FILES=MyClass.java MyOtherClass.java

    JAVA_FILES   =   MyClass.java     MyOtherClass.java

Example configuration files are at the end of this documentation.

An option specified on the command line or in a configuration file will override the same option specified in an environment variable. If an option is specified on the command line I<and> in a configuration file, the command line option will take precedence.



=head2 Configuration Options

=head3 Provided Options

Two configuration options will be automatically set by C<process_handin>:

=over 4

=item B<HOME>

Set to the user's home directory.

=item B<ASSIGNMENT_DIR>

Set to the assignment web directory, based on the value of C<PROGRAM_NUM>. The base is C<~cs302/public/html/assignments>, so the value of this option for program C<p1> would be C</p/course/cs302/public/html/programs/p1>. This option will be undefined until C<PROGRAM_NUM> is set.

=back


=head3 Variable Interpolation

Configuration variables can be interpolated. For example, if your username is C<talogin>, the following two values for C<GRADING_DIR> will be equivalent:

    GRADING_DIR = /u/t/a/talogin/private/grading
    
    GRADING_DIR = $HOME/private/grading

A variable must be defined before it can be interpolated. The following configuration variables can be interpolated:

=over 4

=item C<HOME> (defined automatically)

=item C<PROGRAM_NUM>

=item C<ASSIGNMENT_DIR> (defined automatically)

=item C<CLASSPATH>

=item C<MODE>

=item C<LOG_LEVEL>

=item C<HANDIN>

=item C<SORT>

=back


=head3 User-Defined Options

You can define your own configuration variables, using the C<define> pseudo-option. These options will then be available for interpolation. The following lines defines the variable C<GRADING_CLASSPATH>, then uses it in the next lines:

    define  =   GRADING_CLASSPATH=$CLASSPATH:$ASSIGNMENT_DIR/grading/p4_part1_grading.jar
    AUTO_GRADER =   [Color] java -classpath $GRADING_CLASSPATH grading.part1.TestAll Color
    AUTO_GRADER =   [Pixel] java -classpath $GRADING_CLASSPATH grading.part1.TestAll Pixel


=head3 Available Options

The following list describes all the user-specified configuration options supported by C<process_handin>:

=over 4

=item B<MODE>

Sets the processing mode. Must be either 'lecture' or 'lab'. Defaults to 'lab'.

=item B<GRADING_DIR>

(REQUIRED) The root grading directory. You must have full write permissions for this directory. It should probably be somewhere in your home directory. This option will not expand ~, so make sure you specify the complete path: C</u/m/y/mylogin/private/grading>. You can also use the C<$HOME> variable: C<$HOME/private/grading>. The following files and directories will by created in C<GRADING_DIR>:

=over 4

=item students/

This directory will contain a subdirectory for each student in your section. All their files will be copied into this directory; subdirectories will be created for .java files that are in packages. All their code will be compiled in these subdirectories, and any compilation errors will be reported in a file with the name C<MyClass.error>. Also, any output from failed autograder tests will be left in the subdirectory.

=item roster

This file will contain the outcome of the compilation run. For each student, their login name, partner name, and any status messages (partner name, compilation errors, failed autograder tests, missing files, etc) will be printed. A green 'X' indicates that the student has turned in files you need to grade.

=back


=item B<PROGRAM_NUM>

(REQUIRED) The number of the program, as determined by the assignnment web page directory: C<p1>, C<p2>, C<p4_part1>, etc.

=item B<HANDIN_DIR>

(REQUIRED) The name of the handin directory of the assignment, if it is different from the value of C<PROGRAM_NUM>. For example, if you are grading C<p4_part1>, but the handin directories have been set up to use C<p4_phase1>, set C<PROGRAM_NUM> to the former value and C<HANDIN_DIR> to the latter value.

=item B<CLASSPATH>

(REQUIRED) The classpath with which the .java files will be compiled. Used to include provided .jar files. When compiling student code, the current working directory will be added to this path. The value of the current C<CLASSPATH> environment variable will not be used.

Typically, C<CLASSPATH> should be set to the assignment jar file. The predefined variable C<ASSIGNMENT_DIR> can be used for this purpose. The following two configuration lines are equivalent:

    CLASSPATH   =   $ASSIGNMENT_DIR/files/p4_part1.jar
    
    CLASSPATH   =   /p/course/cs302/public/html/assignments/p4_part1/files/p4_part1.jar


=item B<JAVA_FILES>

The names of java files to compile, separated by whitespace. The amount of whitespace is variable; these two lines are equivalent:

    JAVA_FILES=Coin.java CoinApp.java CoinTester.java
    
    JAVA_FILES=Coin.java       CoinApp.java        CoinTester.java

If the java files should reside in specific packages, the file names can be followed with by a colon and the package name:

    JAVA_FILES=EmailAddress.java:rsvp.model     Main.java:rsvp

The program will automatically create the proper subdirectories for the packages and place the files in them. This will cause errors if the students did not put the proper C<package> declarations in their source code.


=item B<OPTIONAL_JAVA_FILES>

The names of optional Java files, in the same format as the C<JAVA_FILES> option. If the files in this list are handed in, they will be copied and processed as normal, and a note will be added to the log. If not, no error will be raised.


=item B<OTHER_FILES>

A whitespace-separated list of other files that must be submitted, such as C<questions.txt>, JavaDoc files, and C<expected.out>.

=item B<OPTIONAL_FILES>

A whitespace-separated list of optional non-java files. No error will be raised if these files are not turned in.

=item B<LOG_LEVEL>

A number that controls the specificity of the log messages. 0 is the most general, 5 the most specific. Messages above this threshold will not be printed. Defaults to 5, which will print all messages.
 
=item B<SORT>

Determines the sorting order of the output roster. Must be one of C<lab>, C<login>, or C<name>. Defaults to C<name>.

=item B<STUDENTS>

A whitespace-separated list of students logins that will be graded. This list will be used when running in lab mode:

    STUDENTS=jschmoe janedoe bbobson

These students must be enrolled in one of the two large lectures. Lab mode cannot be used to grade students from the small lectures. This option will be ignored when running in lecture mode.

=item B<EXCLUDE_STUDENTS>

For lecture mode, a whitespace-separated list of Students logins from the lecture that will be I<not> be processed. This option is ignored when running in lab mode.

=item B<EXTRA_STUDENTS>

A white-spaced separated list of C<instructor:login> pairs, representing additional students to be processed. Students in the C<EXTRA_STUDENTS> list may come from any lecture, as specified by the instructor field. This option may be used when running in lab or lecture mode. In lab mode, you must use this option if you have to grade a student from one of the small lectures; likewise, when running in lecture mode, you must use this option to grade a student from one of the large lectures.

=back



=head2 Logging

The C<process_handin> script keeps logs in a central repository, which is defined in the script file itself. Every TA who uses the program must have write access to this directory. The following logs will be created

=over 2

=item TA File

Every TA will have a log file located at C<$LOG_DIR/year-semester/$ASSIGNMENT_NUM/$ASSIGNMENT_NUM-talogin.txt>. This file contains a record of every student the TA processed for this assignment. Each line of the file has the student name, TA login, processing time and mode, and a note of any partner the student worked with. Examples:

    malnor	weinrich: 2008-02-21 18:38:31 (lecture)
    ehanson	weinrich: 2008-02-23 14:53:21 (lecture) - partner sekhon (partner submitted)

=item Student Directories

For each assignment, a directory for each student will be created at C<$LOG_DIR/year-semester/$ASSIGNMENT_NUM/login/s/student>, where the C<login> directory contains one subdirectory for each first letter of the student logins. If any compilation or autograder errors are encountered, the output files will be copied to the student's log directory.


=back


=head2 Sample Configuration File

The following is a sample configuration file for Program 1 of Winter 2007:

    MODE=lecture
    GRADING_DIR=/u/t/a/talogin/grading/P1
    PROGRAM_NUM=p1
    CLASSPATH=/u/c/s/cs302/public/html/assignments/p1/files/CoinUtility.jar
    JAVA_FILES=Coin.java CoinApp.java CoinTester.java
    OTHER_FILES=questions.txt expected.out Coin.html
    LOG_LEVEL=5
    SORT=lab

This is an example of a lab mode configuration file for the same assignment, using comments, additional defined and yinterpolated variables, extra space before the option values, and separate lines for multi-valued options to improve readability:

    MODE        = lab
    LOG_LEVEL   = 5
    SORT        = name
    
    STUDENTS    = jschmoe
    STUDENTS    = janedoe
    STUDENTS    = bbobson
    
    PROGRAM_NUM = p1
    GRADING_DIR = $HOME/private/teaching/P1
    CLASSPATH   = $ASSIGNMENT_DIR/files/CoinUtility.jar
    
    JAVA_FILES  = Coin.java
    JAVA_FILES  = CoinApp.java
    JAVA_FILES  = CoinTester.java
    
    OTHER_FILES = questions.txt
    OTHER_FILES = expected.out
    OTHER_FILES = Coin.html


=head1 BUGS

None known.

=head1 COPYRIGHT

Copyright 2007 Andrew Weinrich,   weinrich@wisc.edu,  http://pages.cs.wisc.edu/~weinrich

