import numpy as np
from tabulate import tabulate
from datetime import datetime
import pyfiglet
from unitgrade2 import msum
import unittest
from unitgrade2.unitgrade2 import UTextResult
import inspect
import os
import argparse
import time

parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: 
To run all tests in a report: 

> python assignment1_dp.py

To run only question 2 or question 2.1

> python assignment1_dp.py -q 2
> python assignment1_dp.py -q 2.1

Note this scripts does not grade your report. To grade your report, use:

> python report1_grade.py

Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:

> python -m course_package.report1

see https://docs.python.org/3.9/using/cmdline.html
""", formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)')
parser.add_argument('--showexpected',  action="store_true",  help='Show the expected/desired result')
parser.add_argument('--showcomputed',  action="store_true",  help='Show the answer your code computes')
parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')

def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
    args = parser.parse_args()
    if question is None and args.q is not None:
        question = args.q
        if "." in question:
            question, qitem = [int(v) for v in question.split(".")]
        else:
            question = int(question)

    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:
        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")

    if unmute is None:
        unmute = args.unmute
    if passall is None:
        passall = args.passall

    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,
                                          show_tol_err=show_tol_err)


    if question is None:
        print("Provisional evaluation")
        tabulate(table_data)
        table = table_data
        print(tabulate(table))
        print(" ")

    fr = inspect.getouterframes(inspect.currentframe())[1].filename
    gfile = os.path.basename(fr)[:-3] + "_grade.py"
    if os.path.exists(gfile):
        print("Note your results have not yet been registered. \nTo register your results, please run the file:")
        print(">>>", gfile)
        print("In the same manner as you ran this file.")


    return results


def upack(q):
    # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()])
    h =[(i['w'], i['possible'], i['obtained']) for i in q.values()]
    h = np.asarray(h)
    return h[:,0], h[:,1], h[:,2],

class UnitgradeTextRunner(unittest.TextTestRunner):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class SequentialTestLoader(unittest.TestLoader):
    def getTestCaseNames(self, testCaseClass):
        test_names = super().getTestCaseNames(testCaseClass)
        # testcase_methods = list(testCaseClass.__dict__.keys())
        ls = []
        for C in testCaseClass.mro():
            if issubclass(C, unittest.TestCase):
                ls = list(C.__dict__.keys()) + ls
        testcase_methods = ls
        test_names.sort(key=testcase_methods.index)
        return test_names

def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                    show_progress_bar=True,
                    show_tol_err=False,
                    big_header=True):

    from src.unitgrade2.version import __version__
    now = datetime.now()
    if big_header:
        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
    else:
        b = "Unitgrade"
    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
    print(b + " v" + __version__ + ", started: " + dt_string+ "\n")
    # print("Started: " + dt_string)
    s = report.title
    if hasattr(report, "version") and report.version is not None:
        s += " version " + report.version
    print(s, "(use --help for options)" if show_help_flag else "")
    # print(f"Loaded answers from: ", report.computed_answers_file, "\n")
    table_data = []
    t_start = time.time()
    score = {}
    loader = SequentialTestLoader()

    for n, (q, w) in enumerate(report.questions):
        if question is not None and n+1 != question:
            continue
        suite = loader.loadTestsFromTestCase(q)
        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
        q_title_print = "Question %i: %s"%(n+1, qtitle)
        print(q_title_print, end="")
        q.possible = 0
        q.obtained = 0
        q_ = {} # Gather score in this class.
        from src.unitgrade2.unitgrade2 import UTextTestRunner
        UTextResult.q_title_print = q_title_print # Hacky
        UTextResult.show_progress_bar = show_progress_bar # Hacky.
        UTextResult.number = n
        UTextResult.nL = report.nL

        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)

        possible = res.testsRun
        obtained = len(res.successes)

        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun

        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
        score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
        q.obtained = obtained
        q.possible = possible

        s1 = f" * q{n+1})   Total"
        s2 = f" {q.obtained}/{w}"
        print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )
        print(" ")
        table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"])

    ws, possible, obtained = upack(score)
    possible = int( msum(possible) )
    obtained = int( msum(obtained) ) # Cast to python int
    report.possible = possible
    report.obtained = obtained
    now = datetime.now()
    dt_string = now.strftime("%H:%M:%S")

    dt = int(time.time()-t_start)
    minutes = dt//60
    seconds = dt - minutes*60
    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")

    from src.unitgrade2.unitgrade2 import dprint
    dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",
           last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)

    # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")

    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])
    results = {'total': (obtained, possible), 'details': score}
    return results, table_data
