"""
Jason Yau 
09/16/2025

Use to quickly run all student source code from Webcourses on Eustis and output a csv using stdout.

====================================

Usage:

Go to assignments, P1, Download submissions. A file named submissions.zip (or similar) will download

Copy submissions.zip file to temp Eustis folder like so (This assumes ~/temp/ exists already. If not, you will need to create the directory.): 
scp -r <path of submissions.zip> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/

Copy this python script (p1.py) to temp Eustis folder like so:
scp -r <path of p1.py> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/

Copy both test cases zip file to temp Eustis folder like so:
scp -r <path of p1-sample.zip> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/
scp -r <path of p1-secret.zip> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/

ssh into eustis
ssh <YOUR_NID>@eustis.eecs.ucf.edu

Run the python script which outputs a csv using stdout. You can copy the file over to your local machine via rsync and open with Excel.
python3 ~/temp/p1.py > ~/temp/p1.csv

====================================

Make sure to delete all created files afterwards to be safe.
"""


import os
import glob
from zipfile import ZipFile
import shutil
import subprocess
import re

def grade_top_scorers(output: str, expected: str) -> int:
    if len(output) != len(expected):
        return 0
    for i in range(len(output)):
        o, e = output[i].split(), expected[i].split()
        if len(o) != len(e):
            return 0
        for i in range(len(o)):
            if o[i] != e[i]:
                return 0
    return 1

def grade_top_scorer(output: str, expected: str) -> int:
    o, e = output.split(), expected.split()
    if len(o) != len(e):
        return 0
    for i in range(len(o)):
        if o[i] != e[i]:
            return 0
    return 1
    

def grade_case(output: str, expected: str, case_input: str) -> int:
    out_lines = output.splitlines()
    out_lines = list(filter(lambda line: line and line.strip(), out_lines))
    exp_lines = expected.splitlines()
    queries = case_input.splitlines()[-3:]
    if not (queries[0] == "1" or queries[0] == "2"):
        queries = case_input.splitlines()[-2:]
    assert(len(queries) == 2 or len(queries) == 3)
    score = 0
    if queries[0] == "1":
        if queries[1] == '1':
            score += 5*grade_top_scorer(output=out_lines[0], expected=exp_lines[0])
        else:
            score += 5*grade_top_scorers(output=out_lines, expected=exp_lines)
    else:
        if queries[1] == "1":
            score += 2*grade_top_scorer(output=out_lines[0], expected=exp_lines[0])
            score += 3*grade_top_scorers(output=out_lines[1:], expected=exp_lines[1:])
        else:
            score += 3*grade_top_scorers(output=out_lines[:-1], expected=exp_lines[:-1])
            score += 2*grade_top_scorer(output=out_lines[-1:][0], expected=exp_lines[-1:][0])
    return score

script_path = os.path.abspath(__file__)
script_directory = os.path.dirname(script_path)

extracted_path = f"{script_directory}/extracted"
if (len(glob.glob(extracted_path)) != 0):
    shutil.rmtree(extracted_path)

submissions_zip = glob.glob(f"{script_directory}/submissions*.zip")
if len(submissions_zip) != 1:
    print(f"There were {len(submissions_zip)} submissions*.zip found. Exiting.")
    exit(1)
with ZipFile(submissions_zip[0], 'r') as zip_object:
    zip_object.extractall(extracted_path)

sample_cases_zip = glob.glob(f"{script_directory}/p1-sample.zip")
if len(sample_cases_zip) != 1:
    print(f"p1-sample.zip not found. Exiting.")
    exit(1)
with ZipFile(sample_cases_zip[0], 'r') as zip_object:
    zip_object.extractall(extracted_path)

secret_cases_zip = glob.glob(f"{script_directory}/p1-secret.zip")
if len(secret_cases_zip) != 1:
    print(f"p1-secret.zip not found. Exiting.")
    exit(1)
with ZipFile(secret_cases_zip[0], 'r') as zip_object:
    zip_object.extractall(extracted_path)

input_files = sorted(glob.glob(f"{extracted_path}/p1-*/archive*.in"))
output_files = sorted(glob.glob(f"{extracted_path}/p1-*/archive*.out"))
# Make sure we don't screw this part up.
assert len(input_files) == len(output_files)
for i in range(len(input_files)):
    assert input_files[i].split(".in")[0] in output_files[i].split(".out")[0]

print(f"\"student name\",\"points from test cases\",\"passed cases\",\"comment to write\",\"late\",\"correct file name\",\"compiled\"")

source_code_files = sorted(glob.glob(f"{extracted_path}/*.c")+glob.glob(f"{extracted_path}/*.C"))
for source_code_file in source_code_files:
    file_name = os.path.basename(source_code_file)
    student_name = file_name.split('_')[0]
    student_file_name = file_name.split('_')[len(file_name.split('_'))-1]
    late = "LATE" in file_name.upper()

    executable = f"{source_code_file.split(".c")[0]}.exe"
    gcc_result = subprocess.run(["gcc", source_code_file, "-o", executable], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    compiled = gcc_result.returncode == 0

    passed_cases = 0
    case_points = 0
    comment = ""
    
    if compiled:
        for i in range(len(input_files)):
            with open(input_files[i], "r") as fin:
                try:
                    execute_result = subprocess.run([executable], stdin=fin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=10, text=True, universal_newlines=True, encoding='mac_roman')
                    if not execute_result.stdout or not execute_result.stdout.strip():
                        comment += f"Test case #{i+1}: FAILED (BLANK OUTPUT) (+0)\n"
                        continue
                    curr_case_points = grade_case(execute_result.stdout, open(output_files[i], "r").read(), open(input_files[i], "r").read())
                    case_points += curr_case_points
                    if curr_case_points == 5:
                        comment += f"Test case #{i+1}: PASSED (+5)\n"
                        passed_cases += 1
                    elif curr_case_points == 3:
                        comment += f"Test case #{i+1}: PARTIAL (TOP SCORER FOR EVERY GAME WAS CORRECT) (+3)\n"
                    elif curr_case_points == 2:
                        comment += f"Test case #{i+1}: PARTIAL (OVERALL TOP SCORER WAS CORRECT) (+2)\n"
                    else:
                        comment += f"Test case #{i+1}: FAILED (WRONG OUTPUT) (+0)\n"
                except subprocess.TimeoutExpired:
                    comment += f"Test case #{i+1}: FAILED (TIME LIMIT EXCEEDED) (+0)\n"
    else:
        comment += "Did not compile.\n"

    comment += f"\nOverall Points from Test cases: {case_points}/{len(input_files)*5}\n"
    comment += f"Overall Passed Test cases: {passed_cases}/{len(input_files)}\n"
    
    print(f"\"{student_name}\",\"{case_points}\",\"{passed_cases}\",\"{comment}\",\"{late}\",\"{re.match(r"archive[-]{0,1}[\d]{0,5}.c", student_file_name) != None}\",\"{compiled}\"")

shutil.rmtree(extracted_path)