default persistent.mods_enabled = set()
default mods_parsed = set()

init python:
    import json
    import os
    import renpy.error as rpy_error
    from renpy.parser import ParseError

    mods_list = dict()

    def import_mods():
        global mods_list

        all_files = renpy.list_files()
        mods = filter(lambda x: x.endswith(".json"), all_files)

        for i, manifest in enumerate(mods):
            path = os.path.split(manifest)[0]
            files = filter(lambda x: path in x, all_files)
            scripts = filter(lambda x: x.endswith(".rpym"), files)
            logo = "{}/logo.webp".format(path)

            if not renpy.loadable(logo):
                logo = "#000"

            # Read manifest
            with renpy.open_file(manifest) as f:
                data = json.load(f)

            modname = data.get("Name", None)

            if not modname:
                continue

            mods_list[modname] = data
            mods_list[modname]["Files"] = files
            mods_list[modname]["Path"] = path
            mods_list[modname]["LoadOrder"] = i # TODO: Make load order customisable
            mods_list[modname]["Logo"] = logo

        for mod in list(persistent.mods_enabled):
            if not mods_list.get(mod, None):
                persistent.mods_enabled.remove(mod)
        return

    def parse_mods():
        if main_menu or _menu:
            return

        for mod in list(persistent.mods_enabled):
            if mod in mods_parsed:
                continue

            path = mods_list[mod]["Path"]
            files = mods_list[mod]["Files"]

            for file in files:
                if not file.endswith(".rpym"):
                    continue

                fn = os.path.split(file)[1]

                with renpy.open_file(file) as s:
                    data = s.read()

                print "Loading '{}'".format(mod)

                #renpy.load_module(os.path.splitext(file)[0])

                try:
                    renpy.load_string(data, filename="game/{}/{}".format(path, fn))
                except Exception as e:
                    print "Loading '{}' has failed.\nFile: {}\nError: {}".format(mod, fn, e)
            mods_parsed.add(mod)

        renpy.execute_default_statement(False)
        return

    def toggle_mod(mod):
        if not main_menu:
            renpy.notify("Mods can be enabled or disabled from the main menu only.")
            return

        mods = persistent.mods_enabled

        if mod in mods:
            renpy.notify("Mod disabled.")
            mods.remove(mod)
        else:
            renpy.notify("Mod Enabled.")
            mods.add(mod)

    #
    # Custom parser w/ exception handling
    #

    def parse_script(fn, filedata=None, linenumber=1):
        renpy.game.exception_info = 'While parsing ' + fn + '.'

        try:
            lines = renpy.parser.list_logical_lines(fn, filedata, linenumber)
            nested = renpy.parser.group_logical_lines(lines)
        except ParseError as e:
            renpy.parser.parse_errors.append(e.message)

            if not fn.endswith(".rpym"):
                return None

        l = renpy.parser.Lexer(nested)

        rv = renpy.parser.parse_block(l)

        if renpy.parser.parse_errors:
            if fn.endswith(".rpym"):
                renpy.store.report_parse_errors(fn, linenumber, renpy.parser.parse_errors)
            return None

        if rv:
            rv.append(renpy.parser.ast.Return((rv[-1].filename, rv[-1].linenumber), None))

        return rv

    def report_parse_errors(file, linenumber, errors):

        if not errors:
            return False

        dp, fn = os.path.split(file)

        error_f, error_fn = rpy_error.open_error_file(os.path.join(dp, "errors.txt"), "w")

        with error_f:
            error_f.write(u"\ufeff") # BOM
            error_f.write(u"I'm sorry, but errors were detected in your mod script.\nPlease correct the errors listed below, and try again.\n\n")

            for i in errors:
                if not isinstance(i, str):
                    i = str(i, "utf-8", "replace")

                error_f.write(i)
                error_f.write(u"\n\n")

                # We need to remove reported errors to avoid incorrectly detecting .rpym format in the next call.
                renpy.parser.parse_errors.remove(i)

            error_f.write(u"Game Version: {}\nRen'Py Version: {}\n{}".format(renpy.store.version, renpy.version_only, str(time.ctime())))

        try:
            if renpy.game.args.command == "run": # @UndefinedVariable
                renpy.exports.launch_editor([error_fn], 1, transient=1)
                renpy.exports.launch_editor([file], linenumber)
        except:
            pass

        return True

    # We need to monkey patch our new parser with exception handlers for mods.
    renpy.parser.parse = parse_script

    # Note: Exception handling doesn't seem to be necessary at the moment.
    # Note2: Catching runtime errors and finding their file of origin might be tricky...
    # Note3: Might be worth 'sandboxing' mods parsing in the future
    #        to be able to catch all errors prior to executing them on main store. Maybe.
    # Note4: Pickling monkey patched functions that are stored does not seem to be possible from within load_string
    #        due to saves pickling their data much earlier than mods are loaded.
    #
    # def exception_handler(short, full, file):
    #     renpy.display.error.report_exception(short, full, file)

    # define config.exception_handler = exception_handler

init:
    $ import_mods()
    $ config.after_load_callbacks.append(parse_mods)