# Source code for moderngl_window

    """
    General helper functions aiding in the boostrapping of this library.
    """
    # pylint: disable = redefined-outer-name, too-few-public-methods
    import argparse
    import logging
    import os
    import sys
    import weakref

    from pathlib import Path
    from typing import List, Type, Optional

    import moderngl
    from moderngl_window.context.base import WindowConfig, BaseWindow
    from moderngl_window.timers.clock import Timer
    from moderngl_window.conf import settings
    from moderngl_window.utils.module_loading import import_string
    from moderngl_window.utils.keymaps import KeyMapFactory, KeyMap, QWERTY, AZERTY  # noqa

    __version__ = "2.4.5"

    IGNORE_DIRS = [
        "__pycache__",
        "base",
    ]

    # Add new windows classes here to be recognized by the command line option --window
    WINDOW_CLASSES = ["glfw", "headless", "pygame2", "pyglet", "pyqt5", "pyside2", "sdl2", "tk"]

    OPTIONS_TRUE = ["yes", "on", "true", "t", "y", "1"]
    OPTIONS_FALSE = ["no", "off", "false", "f", "n", "0"]
    OPTIONS_ALL = OPTIONS_TRUE + OPTIONS_FALSE

    # Quick and dirty debug logging setup by default
    # See: https://docs.python.org/3/howto/logging.html#logging-advanced-tutorial
    logger = logging.getLogger(__name__)

    def setup_basic_logging(level: int):
        """Set up basic logging

        Args:
            level (int): The log level
        """
        if level is None:
            return

        # Do not add a new handler if we already have one
        if not logger.handlers:
            logger.propagate = False
            logger.setLevel(level)
            ch = logging.StreamHandler()
            ch.setLevel(logging.DEBUG)
            ch.setFormatter(
                logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
            )
            logger.addHandler(ch)

    class ContextRefs:
        """Namespace for window/context references"""
        WINDOW: Optional[BaseWindow] = None
        CONTEXT: Optional[moderngl.Context] = None

    def activate_context(window: BaseWindow = None, ctx: moderngl.Context = None):
        """
        Register the active window and context.
        If only a window is supplied the context is taken from the window.
        Only a context can also be passed in.

        Keyword Args:
            window (window): The window to activate
            ctx (moderngl.Context): The moderngl context to activate
        """
        ContextRefs.WINDOW = window
        ContextRefs.CONTEXT = ctx
        if not ctx:
            ContextRefs.CONTEXT = window.ctx

    def window():
        """Obtain the active window"""
        if ContextRefs.WINDOW:
            return ContextRefs.WINDOW

        raise ValueError("No active window and context. Call activate_window.")

    def ctx():
        """Obtain the active context"""
        if ContextRefs.CONTEXT:
            return ContextRefs.CONTEXT

        raise ValueError("No active window and context. Call activate_window.")

    def get_window_cls(window: str = None) -> Type[BaseWindow]:
        """
        Attempt to obtain a window class using the full dotted
        python path. This can be used to import custom or modified
        window classes.

        Args:
            window (str): Name of the window

        Returns:
            A reference to the requested window class. Raises exception if not found.
        """
        logger.info("Attempting to load window class: %s", window)
        return import_string(window)

    def get_local_window_cls(window: str = None) -> Type[BaseWindow]:
        """
        Attempt to obtain a window class in the moderngl_window package
        using short window names such as ``pyglet`` or ``glfw``.

        Args:
            window (str): Name of the window

        Returns:
            A reference to the requested window class. Raises exception if not found.
        """
        window = os.environ.get("MODERNGL_WINDOW") or window
        if not window:
            window = "pyglet"

        return get_window_cls("moderngl_window.context.{}.Window".format(window))

    def find_window_classes() -> List[str]:
        """
        Find available window packages
        Returns:
            A list of available window packages
        """
        # In some environments we cannot rely on introspection
        # and instead return a hardcoded list
        try:
            return [
                path.parts[-1]
                for path in Path(__file__).parent.joinpath("context").iterdir()
                if path.is_dir() and path.parts[-1] not in IGNORE_DIRS
            ]
        except Exception:
            return WINDOW_CLASSES

    def create_window_from_settings() -> BaseWindow:
        """
        Creates a window using configured values in :py:attr:`moderngl_window.conf.Settings.WINDOW`.
        This will also activate the window/context.

        Returns:
            The Window instance
        """
        window_cls = import_string(settings.WINDOW["class"])
        window = window_cls(**settings.WINDOW)
        activate_context(window=window)
        return window

    # --- The simple window config system ---

    def run_window_config(config_cls: WindowConfig, timer=None, args=None) -> None:
        """
        Run an WindowConfig entering a blocking main loop

        Args:
            config_cls: The WindowConfig class to render
        Keyword Args:
            timer: A custom timer instance
            args: Override sys.args
        """
        setup_basic_logging(config_cls.log_level)
        parser = create_parser()
        config_cls.add_arguments(parser)
        values = parse_args(args=args, parser=parser)
        config_cls.argv = values
        window_cls = get_local_window_cls(values.window)

        # Calculate window size
        size = values.size or config_cls.window_size
        size = int(size[0] * values.size_mult), int(size[1] * values.size_mult)

        # Resolve cursor
        show_cursor = values.cursor
        if show_cursor is None:
            show_cursor = config_cls.cursor

        window = window_cls(
            title=config_cls.title,
            size=size,
            fullscreen=config_cls.fullscreen or values.fullscreen,
            resizable=values.resizable
            if values.resizable is not None
            else config_cls.resizable,
            gl_version=config_cls.gl_version,
            aspect_ratio=config_cls.aspect_ratio,
            vsync=values.vsync if values.vsync is not None else config_cls.vsync,
            samples=values.samples if values.samples is not None else config_cls.samples,
            cursor=show_cursor if show_cursor is not None else True,
            backend=values.backend,
        )
        window.print_context_info()
        activate_context(window=window)
        timer = timer or Timer()
        config = config_cls(ctx=window.ctx, wnd=window, timer=timer)
        # Avoid the event assigning in the property setter for now
        # We want the even assigning to happen in WindowConfig.__init__
        # so users are free to assign them in their own __init__.
        window._config = weakref.ref(config)

        # Swap buffers once before staring the main loop.
        # This can trigged additional resize events reporting
        # a more accurate buffer size
        window.swap_buffers()
        window.set_default_viewport()

        timer.start()

        while not window.is_closing:
            current_time, delta = timer.next_frame()

            if config.clear_color is not None:
                window.clear(*config.clear_color)

            # Always bind the window framebuffer before calling render
            window.use()

            window.render(current_time, delta)
            if not window.is_closing:
                window.swap_buffers()

        _, duration = timer.stop()
        window.destroy()
        if duration > 0:
            logger.info(
                "Duration: {0:.2f}s @ {1:.2f} FPS".format(
                    duration, window.frames / duration
                )
            )

    def create_parser():
        """Create an argparser parsing the standard arguments for WindowConfig"""
        parser = argparse.ArgumentParser()

        parser.add_argument(
            "-wnd",
            "--window",
            choices=find_window_classes(),
            help="Name for the window type to use",
        )
        parser.add_argument(
            "-fs",
            "--fullscreen",
            action="store_true",
            help="Open the window in fullscreen mode",
        )
        parser.add_argument(
            "-vs", "--vsync", type=valid_bool, help="Enable or disable vsync",
        )
        parser.add_argument(
            "-r",
            "--resizable",
            type=valid_bool,
            default=None,
            help="Enable/disable window resize",
        )
        parser.add_argument(
            "-s",
            "--samples",
            type=int,
            help="Specify the desired number of samples to use for multisampling",
        )
        parser.add_argument(
            "-c",
            "--cursor",
            type=valid_bool,
            help="Enable or disable displaying the mouse cursor",
        )
        parser.add_argument(
            "--size", type=valid_window_size, help="Window size",
        )
        parser.add_argument(
            "--size_mult",
            type=valid_window_size_multiplier,
            default=1.0,
            help="Multiplier for the window size making it easy scale the window",
        )
        parser.add_argument(
            "--backend",
            help="Specify context backend. This is mostly used to enable EGL in headless mode",
        )
        return parser

    def parse_args(args=None, parser=None):
        """Parse arguments from sys.argv

        Passing in your own argparser can be user to extend the parser.

        Keyword Args:
            args: override for sys.argv
            parser: Supply your own argparser instance
        """
        parser = parser or create_parser()
        return parser.parse_args(args or sys.argv[1:])

    # --- Validators ---

    def valid_bool(value):
        """Validator for bool values"""
        value = value.lower()
        if value is None:
            return None

        if value in OPTIONS_TRUE:
            return True

        if value in OPTIONS_FALSE:
            return False

        raise argparse.ArgumentTypeError(
            "Boolean value expected. Options: {}".format(OPTIONS_ALL)
        )

    def valid_window_size(value):
        """
        Validator for window size parameter.

        Valid format is "[int]x[int]". For example "1920x1080".
        """
        try:
            width, height = value.split("x")
            return int(width), int(height)
        except ValueError:
            pass

        raise argparse.ArgumentTypeError(
            "Valid size format: int]x[int]. Example '1920x1080'",
        )

    def valid_window_size_multiplier(value):
        """Validates window size multiplier

        Must be an integer or float greater than 0
        """
        try:
            val = float(value)
            if val > 0:
                return val
        except ValueError:
            pass

        raise argparse.ArgumentTypeError("Must be a positive int or float",)