# -*- coding: utf-8 -*-


import asyncio
import logging

from aiotk import cancel
from asyncio import AbstractEventLoop
from asyncio import Task  # noqa: F401
from typing import Callable, Optional


class PeriodicTask:
    """Periodically run a coroutine in the background.

    .. code-block:: python

       async def my_task():
           pass

       async with PeriodicTask(my_task, 30.0):
           # do something for a long time.

    .. versionadded:: 0.5

    """

    def __init__(self, func: Callable,
                 interval: float,
                 loop: Optional[AbstractEventLoop]=None) -> None:
        self._loop = loop or asyncio.get_event_loop()
        self._func = func
        self._ival = interval
        self._task = None  # type: Optional[Task]

    # NOTE: cannot declare type for return value here because the class
    #       definition is not completed...
    async def __aenter__(self):
        """Schedule the background task."""
        assert self._task is None
        self._task = self._loop.create_task(self._run())
        return self

    async def __aexit__(self, *args) -> None:
        """Stop the background task and wait for it to complete."""
        assert self._task
        await cancel(self._task)
        self._task = None

    async def _run(self) -> None:
        """Periodically run the task and wait until the schedule."""
        while True:
            try:
                await self._func()
            except asyncio.CancelledError:
                raise
            except Exception:
                logging.exception('Executing periodic task.')
            await asyncio.sleep(self._ival)
