"""
Jason Yau 
09/16/2025

Use to quickly run one student source code from Webcourses on Eustis and output results using stdout.

====================================

Usage:

Go to assignments, P1, Download a .c source code submission.

Copy archive.c file to temp Eustis folder like so (This assumes ~/temp/ exists already. If not, you will need to create the directory.): 
scp archive.c <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.
python3 ~/temp/p1_ind.py 1 # to print output and expected output of test case 1, etc, etc.

====================================

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
import sys

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

print_file = 0
if len(sys.argv) > 1:
    print_file = int(sys.argv[1])

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)

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]


source_code_files = glob.glob(f"{script_directory}/*.c")+glob.glob(f"{script_directory}/*.C")
if len(source_code_files) != 1:
    print(f"The number of .c source code files is not 1. Exiting...")
    exit(1)
source_code_file = source_code_files[0]


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
comment = ""
passed_cases = 0
case_points = 0

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 i == print_file-1:
                    print("\nOutput:")
                    print(execute_result.stdout)
                    print("\nExpected:")
                    print(open(output_files[i], "r").read())
                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"
    os.remove(executable)
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(comment)

shutil.rmtree(extracted_path)