from asyncio import sleep
from time import monotonic, process_time

SLEEP_GRANULARITY: float = 1 / 50
SLEEP_IDLE: float = SLEEP_GRANULARITY / 20.0


async def wait_for_idle(
    min_sleep: float = SLEEP_GRANULARITY, max_sleep: float = 1
) -> None:
    """Wait until the process isn't working very hard.

    This will compare wall clock time with process time. If the process time
    is not advancing at the same rate as wall clock time it means the process is
    idle (i.e. sleeping or waiting for input).

    When the process is idle it suggests that input has been processed and the state
    is predictable enough to test.

    Args:
        min_sleep: Minimum time to wait.
        max_sleep: Maximum time to wait.
    """
    start_time = monotonic()

    while True:
        cpu_time = process_time()
        # Sleep for a predetermined amount of time
        await sleep(SLEEP_GRANULARITY)
        # Calculate the wall clock elapsed time and the process elapsed time
        cpu_elapsed = process_time() - cpu_time
        elapsed_time = monotonic() - start_time

        # If we have slept the maximum, we can break
        if elapsed_time >= max_sleep:
            break

        # If we have slept at least the minimum and the cpu elapsed is significantly less
        # than wall clock, then we can assume the process has finished working for now
        if elapsed_time > min_sleep and cpu_elapsed < SLEEP_IDLE:
            break
