#!/usr/bin/perl -w

use warnings;
use strict;
use File::stat;
use Time::localtime;
use Time::HiRes qw(usleep ualarm gettimeofday tv_interval);
use Date::Calc qw/Delta_Days/;
use Getopt::Long;
use Term::ANSIColor qw(:constants);
use POSIX qw/ceil/;
use List::MoreUtils qw(first_index);
use List::MoreUtils qw/ uniq /;

# I N I T    P  U  T  T  Y     W  I  N  D  O  W     C  A  P  T  I  O  N 
system('echo -ne "\033]0;Apr-24 S25-007-XC1-HornersMethod\007"');	


local $| = 1; # autoflush STDOUT 
print RESET;

my $debug=0;
my $confirm="N";   #"Y" OR "N"  interactive prompt before writing feedback ?
my $commit="Y";

GetOptions("commit=s" => \$commit,  	# --commit=N GRADES AND DISPLAYS EXAMS BUT DOES NOT COMMIT SCORE/FEEDBACK TO DISK
	   "confirm=s" => \$confirm,    # --confirm=Y MUST HIT <RET> BEFORE ADVANCE NEXT STUDENT
	   "debug=s" => \$debug,    	# --confirm=Y MUST HIT <RET> BEFORE ADVANCE NEXT STUDENT
	);

my $year="2025";
my $term="SPRING";
my $course="007";
my $assn="XC1"; 
my $assnLabel = "XC1-HornersMethod";
my $gradingDir = "XC1-HornersMethod";
my $srcFileBaseName = "Horner";
my $srcFileName = "Horner.java";
my $dueDate = "2025 04 24";


print RESET,BOLD,WHITE "\n$course $year $term $assn: debug=$debug  confirm=$confirm  commit=$commit   press<RET> to start grading  "; <STDIN>;

my $timeout=10; # THIS ASSN SHOULD NOT NEED MORE THAN 1 SEC. WE GIVE IT 10
my $secsBetweenScriptRuns=30;

my @monthAbbrevs = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my @monthNums    = qw(01 02 03 04 05 06 07 08 09 10 11 12);
my @daysPerMonth    = qw( 31 28 31 30 31 30 31 31 30 31 30 31);
if ($year%100 == 0 && $year%400 == 0) { $daysPerMonth[1]=29; }
my %monthAbbrev2Num; my %monthNum2DaysPerMonth;
foreach my $i ( 1..12 )
{
    $monthAbbrev2Num{$monthAbbrevs[$i-1]} = $monthNums[$i-1];
    $monthNum2DaysPerMonth{$monthNums[$i-1]} = $daysPerMonth[$i-1];
}

my $curve=0;
my $handinPath="/afs/cs.pitt.edu/public/incoming/hoffmant/$course-handin";
my $scriptsPath='/afs/cs.pitt.edu/public/incoming/hoffmant/scripts';
my $rosterPath="/$scriptsPath/$course-roster.txt";
my $configsPath="/$scriptsPath/configs.txt";

require "$scriptsPath/gradeLib.pl";
require "$scriptsPath/DateTimeLib.pl";


# Make a roster of studentIDs to grade
my @roster;
chdir $handinPath;
my @listing = `ls "$handinPath"`;
foreach my $id (@listing)
{
	chomp $id; $id =~ s/\r//;
	next if !-d "$handinPath/$id";
	push @roster, $id;
}
@roster = sort @roster; # aaj123 bjc65 cde12 def56 ....
my %params = map { $_ => 1 } @roster;

die("ERROR?: no student dirs found in $handinPath\n") if ( scalar(@roster) < 1 );

my $studentToGrade;
if (scalar(@ARGV)>0)
{
	$studentToGrade=shift;
	chomp $studentToGrade;
	if( !exists($params{$studentToGrade}) )
		{ die( "No such student: $studentToGrade in $term $year cs $course\n"); } 
}
else
{
    $studentToGrade = "all";
}

# ------ L O O P   O V E R   A L L   D I R S   L O O K I N G   F O R   N E W   S U B M I S S I O N S -------

my @gradedStuff;

while(1)
{	my @nonSubmitters;	
	my $gradedThisPass=0;
	my $alreadyGraded=0;
	my @scores;
	my $studentID;
	foreach $studentID  (@roster)  # qw(zz-tim zz-boyo zz-cinwell) 
	{	chdir "$handinPath";
		if ($studentToGrade ne "all" && $studentToGrade ne $studentID  )
		{
			print "SKIPPING <$studentID> != <$studentToGrade>\n";
			next;
		}
		
		if ($debug) { print RESET,BOLD,WHITE,"...looking for $studentID/$assn/1/$srcFileBaseName.java\t"; }

		next if ( !-d $studentID); # skip the $coursebackread.pl script. Its a program file, not an ID
		my @feedbackSummaryFields = getFeedbackFields($assn, "$handinPath/$studentID/$assn", "$srcFileBaseName.java");
		my $verGraded =     $feedbackSummaryFields[0];  # -1 MEANS NO FOLDER,   0 MEANS  "NOTHNG GRADED", 1,2,3,4,etc i.e. LATEST GRADED VERSION (not necc latest submitted though )
		my $score =         $feedbackSummaryFields[1];  # "95" OR "-" IF NOT GRADED
		my $gradedBy =     $feedbackSummaryFields[2];  # "djurado" OR "-" IF NOT GRADED
		my $dateSubmitted = $feedbackSummaryFields[3];  # DATE GRADED VER WAS SUBMITTED OR "-" IF NOT GRADED
		my $dateGraded =    $feedbackSummaryFields[4];  # DATE GRADED VER WAS GRADED OR "-" IF NOT GRADED
		my $verLatest =     0+$feedbackSummaryFields[5];  # NUM VERS SUBMITTED "0" IF NOT SUBMITTED
		my $dateOfLatestVer=$feedbackSummaryFields[6];  # SUBMISSION DATE ON HIGHEST VER FOLDER SUBMITTED    OR "-" IF NO SUBMISSION
		my $feedbackLink =  $feedbackSummaryFields[7];  # WEB HYPERLINK TO FEEDBACK FILE    OR "-" IF NO SUBMISSION
	
		if ($verGraded == -1 )  # <hoffman/project-03> NO ASSN FLDR
		{ 	print STDERR "$studentID: NO $assn FOLDER?"; sleep(2);		
			next;
		} 
		elsif ($verLatest == 0) # ASSN FOLDER PRESENT BUT NO SUBM FLDR
		{ 
			if ($debug) { print "$studentID: $assn Not yet submitted\n"; <STDIN>; }
			push @nonSubmitters, "$studentID";
			next;
		}
		elsif ($verGraded == $verLatest)
		{
			++$alreadyGraded;
			my $thisGrade = sprintf("%-6s %-3s #%02d", $studentID, $score, $verLatest  );
			push @gradedStuff,  $thisGrade;
			push @scores, $score;
			if ($debug) { print STDERR "$studentID/$assn GRADED=$score\%\n"; <STDIN>;}
			next;
		}
		elsif ($verGraded<$verLatest)
		{	
			#------------------------------------------------------------------------
			# STEP 1: CREATE LOCAL COPY OF SUBMISSION DIR AND ITS FILES  
	   
			chdir  "$scriptsPath/$course-AutoGraders/$gradingDir";			
			mkdir  "$studentID";
			chdir  "$studentID";
			
			if (!-e "$handinPath/$studentID/$assn/$verLatest/$srcFileName") 
			{
				print STDERR "...BUT $studentID is missing $srcFileName file\n"; 
				sleep(2); next;
			}
			else 
			{ 
				`cp "$handinPath/$studentID/$assn/$verLatest/$srcFileName" .`; 
			}
			
			#----------------------------------------------------------------------------------------------
			# STEP #2 COMPILE / EXECUTE AND CAPTURE OUTPUT 
			#----------------------------------------------------------------------------------------------
			
			my $XC1Comments="";
			my $XC1Score=0; 
			my $XC1runtime = 999;
			
			# GRADE SUBMISSION
			
			print "$studentID: calling grade()\n"; 
			
			grade( $studentID, $assn, $verLatest,\$XC1Comments, \$XC1Score, \$XC1runtime ); 
			$gradedThisPass++;
			
			# WRITE OUT FEEDBACK TO THIS STUDENT'S FB FILE
			
			print "$studentID: calling writeFeedack()\n";
			writeFeedback( $studentID, $assn, $verLatest, $XC1Score, "AutoGrader", $XC1runtime, $XC1Comments ); 
			
			# UPDATE THIS STUDENT'S ASSN FEEDBACK.TXT FILE 
			
			print "Updated $studentID/$assn/feedback.txt\n";	
			
			# COPY THE UPDATED FEEDBACK.TXT FILE INTO THE SUBM DIR OF VERSION JUST GRADED. 			
			
			`cp "$handinPath/$studentID/$assn/feedback.txt"  "$handinPath/$studentID/$assn/$verLatest/"`;
			print "Copied $studentID/$assn/feedback.txt into v:$verLatest dir\n";
			
			# UPDATE (ONLY) THIS STUDENT'S INDIVIDUAL GRADESHEET. INDIVIDUAL GRADING NO LONGER TRIGGERS MASS GRADESHEET UPDATES
			my $cwd = 'pwd';
			chdir $scriptsPath;
			`./updOneSheet.pl "$course" "$studentID"`; 
			chdir $cwd ;
			print "Updated $studentID/$studentID-grades.html\n";
			
		} # END ELSIF ($verGraded<$verLatest)
		
    } # END FOR EACH STUDENT ID IN THE ROSTER


	@gradedStuff = sort @gradedStuff;
    my $numStudentsPrinted=0;
	my $numStudentsPerLine = 12;  
	my $numNeedBackRed=0;
	my $numUnBackRedWith100=0;
	my $hoffmantSubmitted=0;
	my $numLates=0;
	my $linesPrinted=0;
	
	# GRADELINE LOOP
	
	my $lateIDs = "";   # based ONLY on if their feedback file contained "LATE DEDUCTED"
	print "\n";
	for my $gradeLine ( @gradedStuff)
    {	
		chomp $gradeLine; # gradeLine  => "wrm23  80 #02"	
		my $id = (split /\s+/,$gradeLine)[0];
		my $score = 0 + (split /\s+/,$gradeLine)[1]; 

		my $ver =  (split /\s+/,$gradeLine)[2]; $ver =~ s/#//; $ver = 0 + $ver;
		my $daysLate = daysLate( $assn, $id, $dueDate, "$handinPath/$id/$assn/$ver/$srcFileBaseName.java" );

		if ( ($daysLate > 0) && (`grep "LATE AND DEDUCTED" "$handinPath/$id/$assn/feedback.txt"`) && (! `grep "YOUR 0 REDUCED" "$handinPath/$id/$assn/feedback.txt"`) )
		{ 
			++$numLates; $lateIDs .= "$id "; $lateIDs .= uc($id) . " "; 
			print BOLD,YELLOW;
		}			
		
		my $brFlag= `grep "<#READER" "$handinPath/$id/$assn/feedback.txt"`;
		if (!$brFlag) 
		{	
			if ($score >= 100) 	{  ++$numNeedBackRed; print RESET,BOLD,RED; }
		} 
		else { print RESET,BOLD,GREEN; }
		
#		$gradeLine =~ s/ /_/g;		# KLUGE ALERT!! :( invisible newline or space ?? must be replaced with something printable ????
		$gradeLine = uc $gradeLine;
#		$gradeLine =~ s/_/ /g;		# UN-KLUDGE :( back to spaces
		
		my $utaID = $year."ta";
		if ( $id ne $utaID && !`grep -i  "$id" "$rosterPath"` ) { die "\nUNKNOWN ID:<$id>\n";  } # UNKNOWN ID. ONLY ACCEPTABLE NON STUDENT is the generic UTA id: 2023ta etc.	
		print( $gradeLine, RESET);
		if (++$numStudentsPrinted%$numStudentsPerLine == 0 ) { print "\n" ; ++$linesPrinted; } else { print "  ";  }

    }
# ==================================== P  R  I  N  T      F  O  O  T  E  R =======================================

if ( $numStudentsPrinted % $numStudentsPerLine != 0) { 	print("\n");  }

    foreach my $sec ( 0..$secsBetweenScriptRuns)
    {   	print RESET,BOLD,WHITE;
		printf(" -%-02d sec:", $secsBetweenScriptRuns-$sec );
		print RESET,BOLD,GREEN, " $srcFileBaseName.java  SUBM:$numStudentsPrinted/", scalar(@roster);
		print RESET,BOLD,RED, "  BR:",RESET,BOLD,WHITE, "$numNeedBackRed", BOLD,YELLOW," LT:$numLates", RESET,BOLD,WHITE;
		
		print "\r";
		sleep 1;
    }
	@gradedStuff = (); 
	print "\n";
} # END while (1)	

#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
#	                                        S  U  B  R  O  U  T  I  N  E    G  R  A  D  E
#. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


sub grade   # grade( $studentID, $assn, $verLatest,\$XC1Comments, \$XC1Score, \$XC1runtime ); 
{
	my $studentID = shift @_;
	my $assn = shift @_;
	my $verLatest = shift @_;
	my $comments = shift @_;
	my $score = shift @_;
	my $runtime = shift @_;


	my $dateTimeStamp =sysDate2GraderStamp (`date`); 	

	$$comments = "\n<#AUTOGRADER $course $assn $gradingDir $dateTimeStamp>\n" .
	"THE AUTOGRADER FEEDBACK BELOW IS AUTOMATED SCORING OF OUTPUT CORRECTNESS ONLY. YOUR SCORE\n" . 
	"COULD CHANGE IF YOU CHEATED BY CALLING SOME LIBRARY FUNCTION TO DO THe CONVERSION FOR YOU\n";

 	#                      C  O  M  P  I  L  E     S  T  U  D  E  N  T     C  O  D  E
	print( RESET,BOLD,GREEN, "\\nCOMPILING --> /usr/local/jdk-22/bin/javacc $srcFileBaseName.java > stdOut.txt 2>&1\n");
	$$comments .= "\\nCOMPILING --> /usr/local/jdk-22/bin/javacc $srcFileBaseName.java\n";
	`/usr/local/jdk-22/bin/javac $srcFileBaseName.java > stdOut.txt 2>&1`;
	
	if ( !-z 'stdOut.txt' ) 
	{	print RESET,BOLD,RED; 
		print "COMPILATION FAILED!: $srcFileBaseName.java failed compilation. It produced these errors or warnings|\n" . `cat stdOut.txt` . "\n:"; 
		print RESET,BOLD,WHITE;
		$$comments .= "COMPILATION FAILED!: $srcFileBaseName.java failed compilation. It produced these errors or warnings|\n" . `cat stdOut.txt` . "\n:"; 
	}

	#                      E  X  E  C  U  T  E    S  T  U  D  E  N  T     C  O  D  E	
	my %timeoutResults;  
	my $result= "TIMED_OUT";
	my $startTime;
	my $stopTime;
	$startTime = [gettimeofday];

	print( RESET,BOLD,GREEN, "\n\nEXECUTING --> java $srcFileBaseName on these inputs: 146 -1005 1 0 5 -5\n" );
	print RESET,BOLD,WHITE;

	$$comments .= "\n\nEXECUTING --> java $srcFileBaseName ON THESE INPUTS: 146, -1005, 1, 0, -5\n\n"; 

	
	my @operands =  ( "146",  "-1005",   "1",   "0",   "-5" );
	my @expResult = (  146,    -1005,     1,     0,     -5 );
	my $yourNumOfCorrectResults = 0; 
	if ( -e 'stdOut.txt' ) { `rm -f stdOut.txt`; }
	if ( -e 'stdErr.txt' ) { `rm -f stdErr.txt`; }

                     
	$$comments .= "#YOUR__EXECUTION#   YOUR OUPUT\n"; 
	print          " #YOUR EXECUTION#   YOUR OUPUT\n"; 
	foreach my $i ( 0..scalar(@operands)-1 )
	{	
		my $operand = $operands[$i]; my $expResult = $expResult[$i];
		eval
		{	local $SIG{ALRM} = sub { die "alarm\n" };
			alarm $timeout; #    @ needs \@'d (escaped) 
			#sleep(1);
			`(/usr/local/jdk-22/bin/java Horner $operand > stdOut.txt ) >& stdErr.txt`;	
 			$$comments .= sprintf("java Horner %5s\t", $operand );
			printf("java Horner %5s\t", $operand ); 
			$result = "COMPLETED";
			alarm 0;
		};	
		$stopTime = [gettimeofday];
		$$runtime = tv_interval $startTime, $stopTime;
		$timeoutResults{"int_CW"}=$result;
		
		my $yourResult = "WRONG";
		
		if ( $result eq "TIMED_OUT"  )
		{
			$$score=0;
			$$comments .= " TIMED OUT in $timeout sec.\n$assn score=$$score\%\n";
			return;
		}
		elsif ( -e "stdErr.txt" && !-z "stdErr.txt" ) # the capture of stderr has something in it :(
		{
			$$score=0;
			$$comments .=  "RUNTIME ERRORS!\n" . `head -n3 stdErr.txt` . "\t<many repeat lines deleted>\n$assn score=$$score\%\n"; 
			return;		
		}
		else
		{	
			$yourResult = `tail -n1 stdOut.txt`; chomp $yourResult;
			$yourResult += 0;
			$$comments .= sprintf("%5s", $yourResult);
			printf("%5s", $yourResult);
		}
		
		if ( $yourResult != $expResult )
		{	
			print "\tIN-CORRECT :( score = 0\n"; #<STDIN>;
			$$comments .=  "\tIN-CORRECT :( score = 0\n"; 
		}
		else
		{	
			++$yourNumOfCorrectResults;
			print "\tCORRECT :) Your # of correct results = $yourNumOfCorrectResults/5\n"; #<STDIN>;
			$$comments .=  "\tCORRECT :) Your # of correct results = $yourNumOfCorrectResults/5\n";
		}		
	} # END FOR EACH INPUT INTO MULT
	
	if ( $yourNumOfCorrectResults < 5 ) 
		{ $$score = 0; } 
	else
		{ $$score = 100; }
		
	$$comments .= "\nOn the 5 inputs tested you got $yourNumOfCorrectResults correct\n" . 
	"This extra credit problem is all/or nothing. Your AUTOGRADER score is $$score\n\n";
} # ------------------------------------------E N D    S U B   G R A D E--------------------------------

sub writeFeedback  # 	writeFeedback( $studentID, $assn, $verLatest, $XC1Score, "AutoGrader", $XC1runtime, $XC1Comments ); 
{
	my $studentID=shift @_;
	my $assn = shift @_;
	my $verLatest = shift @_;
	my $score = shift @_;
	my $grader = shift @_;
	my $runtime = shift @_;
	my $comments = shift @_;
	
	my $srcFilePath = "$handinPath/$studentID/$assn/$verLatest/$srcFileBaseName.java";
	my $daysLate = daysLate( $assn, $studentID, $dueDate, $srcFilePath );
	if ($daysLate > 0)
	{
		my $lateRemarks = "YOUR $assn SUBMITTED $daysLate DAYs LATE AND DEDUCTED 20% PER DAY. YOUR $score REDUCED TO ";
		$score -= 20 * $daysLate;
		$score = 0 if ( $score < 0);
		$lateRemarks .= "$score\n";
		$comments = "$comments\n$lateRemarks\n";
	}
	my $fb = "$verLatest,$score,$grader\n$runtime\n$comments</#AUTOGRADER>\n";
	print 	"\n--------------------------------------------------------------------------------\n" .
			"Here is feedback for: $studentID/$assn\n" . $fb;
	print "press <RET> to commit <$verLatest,$score> for $studentID >";  
	if ($confirm eq "Y") { <STDIN>; } 
	
	my $FBfilePath = "$handinPath/$studentID/$assn/feedback.txt";
	open(FEEDBACK,">$FBfilePath") || die ("Can't open $FBfilePath for writing\n");
	print FEEDBACK $fb;
	close(FEEDBACK);
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# requires: use Date::Calc qw/Delta_Days/; AT TOP Of FILE 
sub daysLate 
{	my $assign =shift @_; 
	my $studentID=shift @_;
	my $dueDateStr = shift @_; # "2016 10 30"
	my $srcFilePath   = shift @_; # full path to file being graded
	
	# DON'T AUTOMATICALL JUST USE DUE DATE FROM TOP OF THIS FILE. CHECK TO SEE IF THIS STUDENT HAS A DUE DATE EXTENSION
	# FOR THIS ASSIGNMENT IN HS$course/config/assignExceptions.json

	my $path2AssnExceptions = "/afs/cs.pitt.edu/usr0/hoffmant/public/html/HS$course/config/assignExceptions.json";
		#if ( -f $path2AssnExceptions ) { die "FOUND file: $path2AssnExceptions"; }
	my $grepMatchLine = `grep "$assn" "$path2AssnExceptions"`;
		#print "grepMatchLine returned this: $grepMatchLine\n"; # see comment below for what that line looks like
		# "project-01": { "aiy7":{"newExpire":[12,18,2023]}}, "2023ta":{"newExpire":[12,10,2023]} }, "mlk68":{"newExpire":[12,18,2023]} }
	my $subMatch="n/a"; 
	if ( $grepMatchLine =~ m/$studentID/ ) 
	{
		$subMatch = $grepMatchLine =~ m/$studentID":{"newExpire":\[\d+,\d+,\d+\]/;
		$subMatch = $&;
		#print "subMatch=$subMatch in pregMatchLine!\n"; 
		
		my @subMatchFields = split( /\[|\]/,$subMatch);
		#print "subMatch[1]=$subMatchFields[1]\n";
		$subMatch=$subMatchFields[1];
		@subMatchFields=split( /,/,$subMatch); # ['12']['10']['2023']
		my $yyyy=$subMatchFields[2]; my $mm=$subMatchFields[0]; my $dd=$subMatchFields[1];
		#print "yyyy=$yyyy\n"; print "mm=$mm\n"; print "dd=$dd\n";
		$dueDateStr = "$yyyy $mm $dd"; # dueDateStr is OVERWRITTEN with extended date AND in same format as orig from this file 
	}	
		
	# NOW THAT THE DUE DATE IS THE EXTENDED DATE (IF THERE WAS AN EXTENSION) WE CAN GO AHEAD DO SAME OLD CALCULATIONS
		
	my @dueDate = split ' ',$dueDateStr; #[2016][10][30]
	my @submDate = (0,0,0);
	my $fileDate=ctime(stat($srcFilePath)->mtime); chomp $fileDate;
	my @fileDateFields = split(' ',$fileDate); # [Tue][Nov][1][22:25:17][2015]
	$submDate[0]=$fileDateFields[4]; 
	$submDate[1]=$fileDateFields[1];  $submDate[1]= $monthAbbrev2Num{$submDate[1]};
	$submDate[2]=$fileDateFields[2]; # [2016][11][1]
	
#	DO IT THE PERL WAY (thank you Bob!)
	my $daysLate = Delta_Days(@dueDate, @submDate);
	return $daysLate;
}

