#-----------------------------------------------------------------------------
#  Copyright (C) 2013 Min RK
#
#  Distributed under the terms of the 2-clause BSD License.
#-----------------------------------------------------------------------------

from contextlib import contextmanager

import ctypes
import ctypes.util

objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
_ = ctypes.cdll.LoadLibrary(ctypes.util.find_library('Foundation'))

void_p = ctypes.c_void_p
ull = ctypes.c_uint64

objc.objc_getClass.restype = void_p
objc.sel_registerName.restype = void_p
objc.objc_msgSend.restype = void_p
objc.objc_msgSend.argtypes = [void_p, void_p]

msg = objc.objc_msgSend

def _utf8(s):
    """ensure utf8 bytes"""
    if not isinstance(s, bytes):
        s = s.encode('utf8')
    return s

def n(name):
    """create a selector name (for methods)"""
    return objc.sel_registerName(_utf8(name))

def C(classname):
    """get an ObjC Class by name"""
    ret = objc.objc_getClass(_utf8(classname))
    assert ret is not None, "Couldn't find Class %s" % classname
    return ret

# constants from Foundation

NSActivityIdleDisplaySleepDisabled             = (1 << 40)
NSActivityIdleSystemSleepDisabled              = (1 << 20)
NSActivitySuddenTerminationDisabled            = (1 << 14)
NSActivityAutomaticTerminationDisabled         = (1 << 15)
NSActivityUserInitiated                        = (0x00FFFFFF | NSActivityIdleSystemSleepDisabled)
NSActivityUserInitiatedAllowingIdleSystemSleep = (NSActivityUserInitiated & ~NSActivityIdleSystemSleepDisabled)
NSActivityBackground                           = 0x000000FF
NSActivityLatencyCritical                      = 0xFF00000000

def beginActivityWithOptions(options, reason=""):
    """Wrapper for:
    
    [ [ NSProcessInfo processInfo] 
        beginActivityWithOptions: (uint64)options
                          reason: (str)reason
    ]
    """
    NSProcessInfo = C('NSProcessInfo')
    NSString = C('NSString')
    
    objc.objc_msgSend.argtypes = [void_p, void_p, void_p]
    reason = msg(NSString, n("stringWithUTF8String:"), _utf8(reason))
    objc.objc_msgSend.argtypes = [void_p, void_p]
    info = msg(NSProcessInfo, n('processInfo'))
    objc.objc_msgSend.argtypes = [void_p, void_p, ull, void_p]
    activity = msg(info,
        n('beginActivityWithOptions:reason:'),
        ull(options),
        void_p(reason)
    )
    return activity

def endActivity(activity):
    """end a process activity assertion"""
    NSProcessInfo = C('NSProcessInfo')
    objc.objc_msgSend.argtypes = [void_p, void_p]
    info = msg(NSProcessInfo, n('processInfo'))
    objc.objc_msgSend.argtypes = [void_p, void_p, void_p]
    msg(info, n("endActivity:"), void_p(activity))

_theactivity = None

def nope():
    """disable App Nap by setting NSActivityUserInitiatedAllowingIdleSystemSleep"""
    global _theactivity
    _theactivity = beginActivityWithOptions(
        NSActivityUserInitiatedAllowingIdleSystemSleep,
        "Because Reasons"
    )

def nap():
    """end the caffeinated state started by `nope`"""
    global _theactivity
    if _theactivity is not None:
        endActivity(_theactivity)
        _theactivity = None

def napping_allowed():
    """is napping allowed?"""
    return _theactivity is None

@contextmanager
def nope_scope(
        options=NSActivityUserInitiatedAllowingIdleSystemSleep,
        reason="Because Reasons"
    ):
    """context manager for beginActivityWithOptions.
    
    Within this context, App Nap will be disabled.
    """
    activity = beginActivityWithOptions(options, reason)
    try:
        yield
    finally:
        endActivity(activity)

__all__ = [
    "NSActivityIdleDisplaySleepDisabled",
    "NSActivityIdleSystemSleepDisabled",
    "NSActivitySuddenTerminationDisabled",
    "NSActivityAutomaticTerminationDisabled",
    "NSActivityUserInitiated",
    "NSActivityUserInitiatedAllowingIdleSystemSleep",
    "NSActivityBackground",
    "NSActivityLatencyCritical",
    "beginActivityWithOptions",
    "endActivity",
    "nope",
    "nap",
    "napping_allowed",
    "nope_scope",
]
