import threading

from pandemonium.bindings cimport Object


cdef bint __pythonscript_verbose = False


cdef class ModExposedClass:
    cdef object kls
    cdef int refcount

    def __init__(self, object kls):
        self.kls = kls
        self.refcount = 1


# /!\ Those containers are strictly private /!\
# They contain class objects that are referenced from Pandemonium without refcounting,
# so droping an item from there will likely cause a segfault !
cdef dict __modules_with_exposed_class = {}
cdef list __all_exposed_classes = []
cdef object __exposed_classes_lock = threading.Lock()


cdef object get_exposed_class(str module_name):
    with __exposed_classes_lock:
        try:
            return (<ModExposedClass>__modules_with_exposed_class[module_name]).kls
        except KeyError:
            return None


cdef void set_exposed_class(object cls):
    cdef ModExposedClass mod
    cdef str modname = cls.__module__

    # Use a threadlock to avoid data races in case pandemonium loads/unloads scripts in multiple threads
    with __exposed_classes_lock:

        # We must keep track of reference counts for the module when reloading a script,
        # pandemonium calls pythonscript_script_init BEFORE pythonscript_script_finish
        # this happens because Pandemonium can make multiple PluginScript instances for the same resource.

        # Pandemonium calls
        try:
            mod = __modules_with_exposed_class[modname]
        except KeyError:
            __modules_with_exposed_class[modname] = ModExposedClass(cls)
        else:
            # When reloading a script, Pandemonium calls `pythonscript_script_init` BEFORE
            # `pythonscript_script_finish`. Hence we drop replace the old class
            # here but have to increase the refcount so
            mod.kls = cls
            mod.refcount += 1

        # Sometimes Pandemonium fails to reload a script, and when this happens we end
        # up with a stale PyObject* for the class, which is then garbage collected by Python
        # so next time a script is instantiated from Pandemonium we end up with a sefault :(
        # To avoid this we keep reference forever to all the classes.
        # TODO: This may be troublesome when running the Pandemonium editor given the classes are
        # reloaded each time they are modified, hence leading to a small memory leak...
        __all_exposed_classes.append(cls)


cdef void destroy_exposed_class(object cls):
    cdef ModExposedClass mod
    cdef str modname = cls.__module__

    # Use a threadlock to avoid data races in case pandemonium loads/unloads scripts in multiple threads
    with __exposed_classes_lock:

        try:
            mod = __modules_with_exposed_class[modname]
        except KeyError:
            print(f'Error: class module is already destroyed: {modname}')
        else:
            if mod.refcount == 1:
                del __modules_with_exposed_class[modname]
                # Not safe to ever get rid of all references...
                # see: https://github.com/touilleMan/pandemonium-python/issues/170
                # and: https://github.com/pandemoniumengine/pandemonium/issues/10946
                # sometimes script reloading craps out leaving dangling references
                # __all_exposed_classes.remove(modname, cls)
            else:
                mod.refcount -= 1
