"""
Jason Yau 
10/31/2025

Use to quickly run one student source code from Webcourses on Eustis and output results using stdout.

====================================

Usage:

Go to assignments, P4, Download a .c source code submission.

Copy ranklist_*.c file to temp Eustis folder like so (This assumes ~/temp/ exists already. If not, you will need to create the directory.): 
scp ranklist_*.c <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/

Copy this python script (p4_ind.py) to temp Eustis folder like so:
scp -r <path of p4_ind.py> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/

Copy both test cases zip file to temp Eustis folder like so:
scp -r <path of p4-sample.zip> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/
scp -r <path of p4-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/p4_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
    matching_lines = 0
    for i in range(len(out_lines)):
        if out_lines[i] == exp_lines[i]:
            matching_lines += 1
    if matching_lines == len(out_lines):
        return 3
    else:
        return 0

print_file = 1
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}/p4-sample.zip")
if len(sample_cases_zip) != 1:
    print(f"p4-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}/p4-secret.zip")
if len(secret_cases_zip) != 1:
    print(f"p4-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}/p4-*/ranklist*.in"))
output_files = sorted(glob.glob(f"{extracted_path}/p4-*/ranklist*.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 not (len(source_code_files) == 1 or len(source_code_files) == 2):
    print(f"The number of .c source code files is not 1 or 2. Exiting...")
    exit(1)

comment = ""
total_case_points = 0
submitted_files = 0

output_1 = ""
output_2 = ""

for source_code_file in source_code_files:
    file_name = os.path.basename(source_code_file)
    case_points = 0
    passed_cases = 0
    submitted_files += 1
    if submitted_files > 2:
        comment += f"\n\nStudent submitted more than 2 source code files in their final submission. Skipped judging: {file_name}\n"
        continue
    
    if submitted_files == 2:
        comment += "\n\n"
    if "qs" in file_name:
        comment += f"---------\nExecution Points for Quick Sort Program:\n"
    elif "ms" in file_name:
        comment += f"---------\nExecution Points for Merge Sort Program:\n"
    else:
        comment += f"---------\nExecution Points for Program #{submitted_files}:\n"

    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

    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=5, text=True, universal_newlines=True, encoding='mac_roman')
                    if i == print_file-1:
                        if submitted_files == 1:
                            output_1 = execute_result.stdout
                        elif submitted_files == 2:
                            output_2 = execute_result.stdout
                    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 == 3:
                        comment += f"Test case #{i+1}: PASSED (+{curr_case_points})\n"
                        passed_cases += 1
                    else:
                        comment += f"Test case #{i+1}: FAILED (+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)*3}\n"
    comment += f"Overall Passed Test cases: {passed_cases}/{len(input_files)}\n"
    total_case_points += case_points

if submitted_files == 0:
    comment += f"Student did not submit any source code files.\n"
if submitted_files == 1:
    comment = f"Student only submitted one source code file.\n"+comment
if submitted_files >= 1:
    comment += "\n--------"
comment += f"\n\nTotal Execution Points: {total_case_points}/{2*len(input_files)*3}\n"

print(comment)

print("\n\nOutput #1:\n")
print(output_1)
print("\n\nOutput #2:\n")
print(output_2)
print("\n\nExpected Output:\n")
print(open(output_files[print_file-1], "r").read())


shutil.rmtree(extracted_path)