"""
A basic caching module for xnu debug macros to use.
It is recommended to use [Get|Save][Static|Dynamic]CacheData() apis for
your caching needs. These APIs will handle the case of clearing caches when
a debugger continues and stops or hit a breakpoint.

Use Static caches for data that will not change if the program is run and stopped again. e.g. typedata, version numbers etc.
An example invocation could be like
def getDSYMPathForUUID(uuid):
    # Get the data from cache
    cached_data = caching.GetStaticCacheData('dsym.for.uuid', {})

    if uuid in cached_data:
        return cached_data[uuid]
    else:
        path = #get info for uuid
        cached_data[uuid] = path

    # save the cached_data object to cache.
    caching.SaveStaticCacheData('dsym.for.uuid', cached_data)

    return cached_data[uuid]

And use Dynamic caches for things like thread data, zones information etc.
These will automatically be dropped when debugger continues the target
An example use of Dynamic cache could be as follows

def GetExecutablePathForPid(pid):
    # Get the data from cache
    cached_data = caching.GetDynamicCacheData('exec_for_path', {})

    if pid in cached_data:
        return cached_data[pid]
    else:
        exec_path = "/path/to/exec"  #get exec path for pid
        cached_data[pid] = path

    # save the cached_data object to cache.
    caching.SaveDynamicCacheData('exec_for_path', cached_data)

    return cached_data[pid]

"""

#Private Routines and objects

from configuration import *

import sys

"""
The format for the saved data dictionaries is
{
    'key' : (valueobj, versno),
    ...
}

The versno is an int defining the version of obj. In case of version mismatch it will set valueobj to default upon access.

"""
_static_data = {}
_dynamic_data = {}



def _GetDebuggerSessionID():
    """ A default callable function that _GetCurrentSessionID uses to
        identify a stopped session.
    """
    return 0

def _GetCurrentSessionID():
    """ Get the current session id. This will update whenever
        system is continued or if there is new information that would
        cause the dynamic cache to be deleted.

        returns: int - session id number.
    """
    session_id = _GetDebuggerSessionID()
    return session_id;


#Public APIs

def ClearAllCache():
    """ remove all cached data.
    """
    global _static_data, _dynamic_data
    _static_data = {}
    _dynamic_data = {}

def GetSizeOfCache():
    """ Returns number of bytes held in cache.
        returns:
            int - size of cache including static and dynamic
    """
    global _static_data, _dynamic_data
    return sys.getsizeof(_static_data) + sys.getsizeof(_dynamic_data)


def GetStaticCacheData(key, default_value = None):
    """ Get cached object based on key from the cache of static information.
        params:
            key: str - a unique string identifying your data.
            default_value : obj - an object that should be returned if key is not found.
        returns:
            default_value - if the static cache does not have your data.
            obj  - The data obj saved with SaveStaticCacheData()
    """
    global _static_data
    key = str(key)
    if key in _static_data:
        return _static_data[key][0]
    return default_value

def SaveStaticCacheData(key, value):
    """ Save data into the cache identified by key.
        It will overwrite any data that was previously associated with key
        params:
            key  : str - a unique string identifying your data
            value: obj - any object that is to be cached.
        returns:
            Nothing
    """
    global _static_data

    if not config['CacheStaticData']:
        return

    key = str(key)
    _static_data[key] = (value, _GetCurrentSessionID())
    return


def GetDynamicCacheData(key, default_value=None):
    """ Get cached object based on key from the cache of dynamic information.
        params:
            key: str - a unique string identifying cached object
            default_value : obj - an object that should be returned if key is not found.
        returns:
            default_value - if dynamic cache does not have data or if the saved version mismatches with current session id.
            obj  - The data obj saved with SaveDynamicCacheData()
    """
    global _dynamic_data
    key = str(key)
    if key in _dynamic_data:
        if _GetCurrentSessionID() == _dynamic_data[key][1]:
            return _dynamic_data[key][0]
        else:
            del _dynamic_data[key]

    return default_value


def SaveDynamicCacheData(key, value):
    """ Save data into the cache identified by key.
        It will overwrite any data that was previously associated with key
        params:
            key  : str - a unique string identifying your data
            value: obj - any object that is to be cached.
        returns:
            Nothing
    """
    global _dynamic_data

    if not config['CacheDynamicData']:
        return

    key = str(key)
    _dynamic_data[key] = (value, _GetCurrentSessionID())

    return
