import malcat
import inspect
import enum
import os

class TransformError(ValueError): pass



class Transform:

    def __call__(self, *args, **kwargs):
        # simple shortcut
        return self.run(*args, **kwargs)

    def __repr__(self):
        return f"Transform {self.__class__.name} (defined at {inspect.getfile(self.__class__)})"

    @classmethod
    def get_infos(transform_class):
        params = []
        parameters = inspect.signature(transform_class.run).parameters
        for i, el in enumerate(parameters.items()):
            name, p = el
            if i == 0:
                pass
            elif i == 1:
                if name != "data":
                    raise TransformError("Invalid prototype for {}: first argument is not 'data', but '{}'".format(transform_class.__name__, name))
                if p.annotation != bytes:
                    raise TransformError("Invalid prototype for {}: 'data' types annotation is not 'bytes', but '{}'".format(transform_class.__name__, p.annotation))
            else:
                enum_values = []
                if p.annotation == inspect.Parameter.empty:
                    raise TransformError("Parameter '{}' of {} has no type annotation".format(name, transform_class.__name__))
                supported_types = {
                    bool: malcat.TransformParameter.BOOL,
                    int: malcat.TransformParameter.INT,
                    float: malcat.TransformParameter.FLOAT,
                    bytes: malcat.TransformParameter.DATA,
                    str: malcat.TransformParameter.STRING
                }
                if type(p.annotation) in (list, tuple):
                    nannot = malcat.TransformParameter.ENUM
                    if len(p.annotation) == 0:
                        raise TransformError("Parameter '{}' of {} has an empty enum".format(name, transform_class.__name__))

                    for e in p.annotation:
                        if type(e) != str:
                            raise TransformError("Parameter '{}' of {}: enums should be annotated with a list/tuple of _strings_, got {} in list".format(name, transform_class.__name__, type(e)))
                    enum_values = p.annotation
                else:
                    nannot = supported_types.get(p.annotation, None)
                if nannot is None:
                    raise TransformError("Parameter '{}' of {} has unsupported type: {}".format(name, transform_class.__name__, p.annotation))
                default = p.default

                if default == inspect.Parameter.empty:
                    raise TransformError("Parameter '{}' of {} has no default value".format(name, transform_class.__name__))
                if type(p.annotation) in (list, tuple) and type(default) != str or type(p.annotation) not in (list, tuple) and type(default) != p.annotation:
                    raise TransformError("Parameter '{}' of {} has unsupported type default: {}".format(name, transform_class.__name__, type(default)))
                if type(p.annotation) in (list, tuple) and default not in p.annotation:
                    raise TransformError("Parameter '{}' of {} has its default value not in the enum".format(name, transform_class.__name__))
                if type(default) == bytes:
                    default = bytearray(default)    # because of pybind auto conversion issue
                params.append(malcat.TransformParameter(name, nannot, default, enum_values))
        if hasattr(transform_class, "name"):
            name = transform_class.name
        else:
            name = transform_class.__name__
        if hasattr(transform_class, "category"):
            category = transform_class.category.lower()
        else:
            category = ""
        if hasattr(transform_class, "icon"):
            icon = transform_class.icon
        else:
            icon = "wxART_FIND_REGEX"
        doc = inspect.getdoc(transform_class)
        return malcat.TransformInfo(transform_class.__name__, name, category, doc, params, os.path.abspath(inspect.getfile(transform_class)), icon)

    def run(self, data:bytes, **kwargs):
        raise NotImplementedError
