"""
utils.py
weavebj1 - 2025

Basic repository needs

Part of python-grader library
"""

import os
import sys
import builtins
import logging

# These need to be separate imports because it works...
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
from matplotlib.figure import Figure


def repo_path():
    """
    Gets the path of the repository (breaks if this file moves)
    """
    return os.path.dirname(__file__)


def in_repo(search_file):
    """
    Returns the path of a file if it exists
    """
    search_dir = repo_path()
    for root, _, files in os.walk(search_dir):
        for file in files:
            if search_file == file:
                return os.path.join(root, file)
    return None


def running_in_exe():
    """
    Returns true if the code is running in an exe file
    """
    return getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')


def exe_resource_path():
    """
    Retrieves the path for files in the exe that are placed into a temp directory
    """
    return getattr(sys, '_MEIPASS')


def guarantee_path(path):
    """
    Makes sure that the file is on the system path
    """
    sys.path.append(os.path.dirname(os.path.abspath(path)))


def file_strip(path):
    """
    Removes all except for the base name (including a ./ at the start)
    """
    filename = os.path.basename(path)
    return os.path.splitext(filename)[0].replace('.','')


def verify_number_of_outputs(outputs, n):
    """
    Returns errors (and the correct number of elements)
    if the number of outputs is not the expected number
    
    Outputs: A tuple from some file
    n: The number of expected values in an output
    """

    if outputs is None or len(outputs) != n:
        e = ValueError("Returned improper number of outputs")
        return (e,)*4

    return outputs

def disable_breakpoints():
    """
    Disables any user breakpoints from running
    """
    sys.breakpointhook = nothing_function()


def log_verbose(message, *args, **kwargs):
    """
    Runs a log at an intermediate level
    """
    logging.log(logging.VERBOSE, message, *args, **kwargs)

# Diagnostic support
logging.addLevelName(logging.INFO-5, "VERBOSE")
logging.VERBOSE = logging.INFO-5
setattr(logging, 'verbose', log_verbose)

def disable_printing():
    """
    Disables just the print function
    """

    def new_print(*args, **kwargs):
        if len(args) > 0:
            log_verbose(*args, **kwargs)
        else:
            log_verbose("", *args, **kwargs)

    builtins.print = new_print

def close_plots():
    """
    Run once in a while to clear out student plots. Hacky solution
    """
    plt.close("all")


def disable_plotting():
    """
    Disables the plotting, figure display, and figure saving for matplotlib
    """

    matplotlib.use("agg")
    plt.close('all')
    Axes.plot = disable_function()
    Axes.legend = disable_function()
    Figure.savefig = disable_function()
    plt.show = disable_function()


def nothing_function(noutputs=0):
    """
    Does nothing. Used to replace breakpoint
    """
    return ([],)*noutputs


def disable_function(noutputs=0):
    """
    Replaces function with a dummy that does nothing

    noutputs: determines how many outputs the function will provide (empty)
    """

    def nope(*args, **kwargs):
        return nothing_function(noutputs=noutputs)

    return nope
