import asyncio
from typing import Optional


def get_loop(running: bool = False, enforce_running: bool = False) -> asyncio.AbstractEventLoop:
    """Gracefully fetch a loop.

    The function tries to get an event loop via :func:`asyncio.get_event_loop`.
    On fail, returns a new loop using :func:`asyncio.new_event_loop`.

    Parameters
    ----------
    running: :class:`bool`
        Indicates if the function should get a loop that is already running.

    enforce_running: :class:`bool`
        If ``running`` is ``True``, indicates if :exc:`RuntimeError`
        should be raised if there is no current loop running.
    """
    loop: Optional[asyncio.AbstractEventLoop]

    if running:
        try:
            loop = asyncio._get_running_loop()

        except Exception:  # an error might occur actually
            loop = None

        if loop is not None:
            return loop

        if enforce_running:
            raise RuntimeError("No running event loop.")

    try:
        loop = asyncio.get_event_loop()

        if loop.is_running() and not running:
            # loop is running while we have to get the non-running one,
            # let us raise an error to go into <except> clause.
            raise ValueError("Current event loop is already running.")

        if loop.is_closed():
            # same here, fall into <except> clause if the loop is closed
            raise ValueError("Current event loop is closed.")

    except Exception:
        loop = asyncio.new_event_loop()

    return loop