"""
Jason Yau 
10/04/2025

Use to quickly run one student source code from Webcourses on Eustis and output results using stdout.

====================================

Usage:

Go to assignments, P2, Download a .c source code submission.

Copy spellingbee.c file to temp Eustis folder like so (This assumes ~/temp/ exists already. If not, you will need to create the directory.): 
scp spellingbee.c <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.
python3 ~/temp/p2_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
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

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}/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]


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 == 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"
    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)