import gc

# ---- Default implementation when resource is missing ----------------------
PAGE_SIZE = 4096


def getMemoryLimit():
    """
    Get current memory limit in bytes.

    Return None on error.
    """
    return None


def setMemoryLimit(max_mem):
    """
    Set memory limit in bytes.
    Use value 'None' to disable memory limit.

    Return True if limit is set, False on error.
    """
    return False


def getMemorySize():
    """
    Read currenet process memory size: size of available virtual memory.
    This value is NOT the real memory usage.

    This function only works on Linux (use /proc/self/statm file).
    """
    try:
        statm = open('/proc/self/statm').readline().split()
    except IOError:
        return None
    return int(statm[0]) * PAGE_SIZE


def clearCaches():
    """
    Try to clear all caches: call gc.collect() (Python garbage collector).
    """
    gc.collect()
    # import re; re.purge()


try:
    # ---- 'resource' implementation -----------------------------------
    from resource import getpagesize, getrlimit, setrlimit, RLIMIT_AS

    PAGE_SIZE = getpagesize()

    def getMemoryLimit():  # noqa
        try:
            limit = getrlimit(RLIMIT_AS)[0]
            if 0 < limit:
                limit *= PAGE_SIZE
            return limit
        except ValueError:
            return None

    def setMemoryLimit(max_mem):  # noqa
        if max_mem is None:
            max_mem = -1
        try:
            setrlimit(RLIMIT_AS, (max_mem, -1))
            return True
        except ValueError:
            return False
except ImportError:
    pass


def limitedMemory(limit, func, *args, **kw):
    """
    Limit memory grow when calling func(*args, **kw):
    restrict memory grow to 'limit' bytes.

    Use try/except MemoryError to catch the error.
    """
    # First step: clear cache to gain memory
    clearCaches()

    # Get total program size
    max_rss = getMemorySize()
    if max_rss is not None:
        # Get old limit and then set our new memory limit
        old_limit = getMemoryLimit()
        limit = max_rss + limit
        limited = setMemoryLimit(limit)
    else:
        limited = False

    try:
        # Call function
        return func(*args, **kw)
    finally:
        # and unset our memory limit
        if limited:
            setMemoryLimit(old_limit)

        # After calling the function: clear all caches
        clearCaches()
