from ..core.block import Block
from ..core.exceptions import SemanticError, PGSyntaxError, add_prefix
from ..core.model_loader import model_from_string
from ..core.parsing import parse_model
from ..core.registrar import default_library, Library

# noinspection PyUnresolvedReferences
import procgraph.components  # @UnusedImport
import unittest
import traceback


# load standard components


def define_generic(nin, nout):
    # noinspection PyUnusedLocal
    class Generic(Block):
        Block.alias("generic_in%d_out%d" % (nin, nout))
        for i in range(nin):
            Block.input(str(i))
        for i in range(nout):
            Block.output(str(i))


for nin in range(0, 6):
    for nout in range(0, 6):
        define_generic(nin, nout)


class PGTestCase(unittest.TestCase):
    def check_syntax_ok(self, model_spec):
        """ Tests that the given string can parse OK.
            Returns parsed models. """
        try:
            parsed = parse_model(model_spec)
            return parsed
        except Exception as e:
            traceback.print_exc()
            self.fail("Could not parse model:\n%s\nGot error: %s" % (self.write_fancy(model_spec), e))

    def check_semantic_ok(self, model_spec, config={}):
        """ Tests that the given string can parse OK and we can create a model.
             Note that a syntax error is translated into a test Error,
             not a failure.
        """
        # Don't pollute the main library with unit tests
        library = Library(parent=default_library)

        try:
            model = model_from_string(model_spec, config=config, library=library)
            model.init()
            return model
        except SemanticError as e:
            traceback.print_exc()

            self.fail(
                "Semantic error for not parse model:\n%s\n"
                "Got error: %s" % (self.write_fancy(model_spec), e)
            )
        except BaseException as e:
            self.fail(
                "Other error for not parse model:\n%s\n" "Got error: %s" % (self.write_fancy(model_spec), e)
            )

    def check_syntax_error(self, model_spec):
        """ Tests that the given string parsing gives a syntax error. """
        try:
            parsed = parse_model(model_spec)
            self.fail(
                "Expected SemanticError for model:\n%s\nParsed as:\n%s"
                % (self.write_fancy(model_spec), parsed)
            )
        except PGSyntaxError as e:
            e.__str__()
            return e

    def check_semantic_error(self, model_spec, config={}):
        """ Tests that the given string parses ok
            but gives a semantic error. """
        # make sure we can parse it, before
        parsed = parse_model(model_spec)

        try:
            library = Library(parent=default_library)
            model = model_from_string(model_spec, config=config, library=library)
            model.init()
            # Note: we only have check_semantic_error, perhaps we need
            # another for the tests that do need init()?
            self.fail("Expected SemanticError for model:\n%s\n%s" % (self.write_fancy(model_spec), parsed))

        except SemanticError as e:
            pass
            # make sure we can write it as a string
            e.__str__()
            return e

    def check_execution_throws(self, possible_errors, model_spec, config={}):
        """ Tests that the model execution throws the specific error
            semantic error. Returns the exception if it is thrown. """
        library = Library(parent=default_library)
        model = model_from_string(model_spec, config=config, library=library)
        try:
            model.init()
            while model.has_more():
                model.update()
            model.finish()
            self.fail(
                "Expected one of %s for model:\n%s" % (str(possible_errors), self.write_fancy(model_spec))
            )
        except possible_errors as e:
            # make sure we can write e as a string:
            str(e)
            return e

    def write_fancy(self, model_spec):
        return add_prefix(model_spec, "  |")


class VerifyBlock(Block):
    """
        This debug block verifies that config.x == config.y
        and throws an exception if that's not the case.
    """

    Block.alias("verify")

    Block.config("x")
    Block.config("y")

    def init(self):
        if self.config.x != self.config.y:
            raise SemanticError('Oops: "%s" != "%s".' % (self.config.x, self.config.y), self)
