"""
Jason Yau 
10/16/2025

Use to quickly run one student source code from Webcourses on Eustis and output results using stdout.

====================================

Usage:

Go to assignments, P3, Download a .c source code submission.

Copy strands.c file to temp Eustis folder like so (This assumes ~/temp/ exists already. If not, you will need to create the directory.): 
scp strands.c <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/

Copy this python script (p3_ind.py) to temp Eustis folder like so:
scp -r <path of p3_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 p3-sample.zip> <YOUR_NID>@eustis.eecs.ucf.edu:~/temp/
scp -r <path of p3-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/p3_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) -> tuple[int, int, 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, 0, len(exp_lines)
    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 10, matching_lines, len(exp_lines)
    else:
        return 10*matching_lines//len(exp_lines), matching_lines, len(exp_lines)

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}/p3-sample.zip")
if len(sample_cases_zip) != 1:
    print(f"p3-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}/p3-secret.zip")
if len(secret_cases_zip) != 1:
    print(f"p3-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}/p3-*/strands*.in"))
output_files = sorted(glob.glob(f"{extracted_path}/p3-*/strands*.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, matching_lines, exp_lines_count = 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 == 10:
                    comment += f"Test case #{i+1}: PASSED (+10)\n"
                    passed_cases += 1
                elif curr_case_points >= 1:
                    comment += f"Test case #{i+1}: PARTIAL ({matching_lines}/{exp_lines_count} LINES MATCHED LINES OF EXPECTED OUTPUT) (+{curr_case_points})\n"
                else:
                    comment += f"Test case #{i+1}: FAILED (WRONG OUTPUT. EITHER {matching_lines}/{exp_lines_count} LINES MATCHED LINES OF EXPECTED OUTPUT OR UNEXPECTED NUMBER OF LINES OUTPUTTED) (+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)*10}\n"
comment += f"Overall Passed Test cases: {passed_cases}/{len(input_files)}\n"

print(comment)

shutil.rmtree(extracted_path)