"""
Jason Yau 
10/04/2025

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

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

Usage:

Go to assignments, P2, 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 (p2.py) to temp Eustis folder like so:
scp -r <path of p2.py> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/

Copy both test cases zip file to temp Eustis folder like so:
scp -r <path of p2-sample.zip> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/
scp -r <path of p2-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/p2.py > ~/temp/p2.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
from collections import Counter
    
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()
    if len(out_lines) != len(exp_lines):
        return 0
    all_match = True
    for i in range(len(out_lines)):
        if out_lines[i] != exp_lines[i]:
            all_match = False
            break
    if all_match:
        return 5
    if (Counter(out_lines) == Counter(exp_lines)):
        return 1
    return 0

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}/p2-sample.zip")
if len(sample_cases_zip) != 1:
    print(f"p2-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}/p2-secret.zip")
if len(secret_cases_zip) != 1:
    print(f"p2-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}/p2-*/spellingbee*.in"))
output_files = sorted(glob.glob(f"{extracted_path}/p2-*/spellingbee*.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 == 1:
                        comment += f"Test case #{i+1}: FAILED (CORRECT WORDS, BUT WERE NOT IN ORDER) (+0)\n"
                        case_points -= 1
                    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"spellingbee[-]{0,1}[\d]{0,5}.c", student_file_name) != None}\",\"{compiled}\"")

shutil.rmtree(extracted_path)