import unittest
import argparse
import threading
import queue
from sccd.runtime.event import Event
from sccd.runtime.controller import Controller
from lib.loader import Loader
from lib.os_tools import *

class PyTestCase(unittest.TestCase):
    def __init__(self, src_file, builder):
        unittest.TestCase.__init__(self)
        self.src_file = src_file
        self.builder = builder

    def __str__(self):
        return self.src_file

    def runTest(self):
        # Build & load
        module = self.builder.build_and_load(self.src_file)
        inputs = module.Test.input_events
        expected = module.Test.expected_events # list of lists of Event objects
        model = module.Model()

        controller = Controller(model)

        # generate input
        for i in inputs:
            controller.add_input(i)

        pipe = queue.Queue()

        def model_thread():
            try:
                # Run as-fast-as-possible, always advancing time to the next item in event queue, no sleeping.
                # The call returns when the event queue is empty and therefore the simulation is finished.
                controller.run_until(None, pipe)
            except Exception as e:
                pipe.put(e, block=True, timeout=None)
                return
            pipe.put(None, block=True, timeout=None)

        # start the controller
        thread = threading.Thread(target=model_thread)
        thread.start()

        # check output
        slot_index = 0
        while True:
            output = pipe.get(block=True, timeout=None)
            if isinstance(output, Exception):
                thread.join()
                raise output # Exception was caught in Controller thread, throw it here instead.
            elif output is None:
                self.assertEqual(slot_index, len(expected), "Less output was received than expected.")
                thread.join()
                return
            else:
                self.assertLess(slot_index, len(expected), "More output was received than expected.")
                exp_slot = expected[slot_index]
                # print("slot:", slot_index, ", events: ", output)

                self.assertEqual(len(exp_slot), len(output), "Slot %d length differs: Expected %s, but got %s instead." % (slot_index, exp_slot, output))

                # sort both expected and actual lists of events before comparing,
                # in theory the set of events at the end of a big step is unordered
                key_f = lambda e: "%s.%s"%(e.port, e.name)
                exp_slot.sort(key=key_f)
                output.sort(key=key_f)

                for (exp_event, event) in zip(exp_slot, output):
                    matches = True
                    if exp_event.name != event.name :
                        matches = False
                    if exp_event.port != event.port :
                        matches = False
                    if len(exp_event.parameters) != len(event.parameters) :
                        matches = False
                    for index in range(len(exp_event.parameters)) :
                        if exp_event.parameters[index] !=  event.parameters[index]:
                            matches = False

                self.assertTrue(matches, "Slot %d entry differs: Expected %s, but got %s instead." % (slot_index, exp_slot, output))
                slot_index += 1


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description="Run SCCD tests.",
        epilog="Set environment variable SCCDDEBUG=1 to display debug information about the inner workings of the runtime.")
    parser.add_argument('path', metavar='PATH', type=str, nargs='*', help="Tests to run. Can be a XML file or a directory. If a directory, it will be recursively scanned for XML files.")
    parser.add_argument('--build-dir', metavar='BUILD_DIR', type=str, default='build', help="Directory for built tests. Defaults to 'build'")
    args = parser.parse_args()

    src_files = get_files(args.path, filter=filter_xml)

    # builder = Builder(args.build_dir)
    builder = Loader()
    suite = unittest.TestSuite()
    for src_file in src_files:
        suite.addTest(PyTestCase(src_file, builder))

    if len(src_files) == 0:
        print("No input files specified.")
        print()
        parser.print_usage()
    else:
        unittest.TextTestRunner(verbosity=2).run(suite)
