"""Functions for acquiring a process lock"""
import socket
import hashlib
import contextlib
import time
from typing import Optional
import __main__ as main_obj


class LockError(Exception):
    """Raised when a rads.lock cannot be acquired"""

    __module__ = 'rads'


@contextlib.contextmanager
def lock(name: Optional[str] = None):
    """Context manager to create an abstract UNIX socket which will behave as
    a "lock". This can be used en lieu of /var/run file pids/locks to determine
    if an instance of a process is already running and ensure the lock is not
    left behind if the process terminates prematurely

    Args:
        name: unique name of the lock; defaults to filename of ``__main__``
    Raises:
        LockError: if the "lock" already existed
    """
    if name is None:
        # caveat: this will raise AttributeError
        # if run in an interactive python shell
        name = main_obj.__file__
    name = name.encode('utf-8')
    lock_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    try:
        lock_socket.bind('\0%s' % hashlib.sha256(name).hexdigest())
    except socket.error as exc:
        raise LockError(name) from exc
    try:
        yield
    finally:
        lock_socket.close()


lock.__module__ = 'rads'


@contextlib.contextmanager
def wait_lock(
    name: Optional[str] = None,
    sleep: float = 0.2,
    max_sleep: Optional[int] = None,
):
    """Context manager to wait until a rads.lock() can be acquired, and do so

    Args:
        name: unique name of the lock; defaults to filename of ``__main__``
        sleep: secs before trying to acquire the lock again
        max_sleep: max number of tries to acquire the lock
    Raises:
        LockError: if max_sleep is exceeded
    """
    slept = 0.0
    while True:
        try:
            with lock(name):
                yield
                return
        except LockError:
            time.sleep(sleep)
            slept += sleep
            if max_sleep is not None and slept >= max_sleep:
                raise


wait_lock.__module__ = 'rads'
