import time


# A cache of channel and repository info to allow users to install multiple
# packages without having to wait for the metadata to be downloaded more
# than once. The keys are managed locally by the utilizing code.
_channel_repository_cache = {}


def clear_cache():
    _channel_repository_cache.clear()


def get_cache(key, default=None):
    """
    Gets an in-memory cache value

    :param key:
        The string key

    :param default:
        The value to return if the key has not been set, or the ttl expired

    :return:
        The cached value, or default
    """

    struct = _channel_repository_cache.get(key, {})
    expires = struct.get('expires')
    if expires and expires > time.time():
        return struct.get('data', default)
    return default


def merge_cache_over_settings(destination, setting, key_prefix):
    """
    Take the cached value of `key` and put it into the key `setting` of
    the destination.settings dict. Merge the values by overlaying the
    cached setting over the existing info.

    :param destination:
        An object that has a `.settings` attribute that is a dict

    :param setting:
        The dict key to use when pushing the value into the settings dict

    :param key_prefix:
        The string to prefix to `setting` to make the cache key
    """

    existing = destination.settings.get(setting, {})
    value = get_cache(key_prefix + '.' + setting)
    if value:
        existing.update(value)
        destination.settings[setting] = existing


def merge_cache_under_settings(destination, setting, key_prefix, list_=False):
    """
    Take the cached value of `key` and put it into the key `setting` of
    the destination.settings dict. Merge the values by overlaying the
    existing setting value over the cached info.

    :param destination:
        An object that has a `.settings` attribute that is a dict

    :param setting:
        The dict key to use when pushing the value into the settings dict

    :param key_prefix:
        The string to prefix to `setting` to make the cache key

    :param list_:
        If a list should be used instead of a dict
    """

    value = get_cache(key_prefix + '.' + setting)
    if value:
        existing = destination.settings.get(setting)
        if existing:
            if list_:
                # Prevent duplicate values
                base = dict(zip(value, [None] * len(value)))
                for val in existing:
                    if val in base:
                        continue
                    value.append(val)
            else:
                value.update(existing)
        destination.settings[setting] = value


def set_cache(key, data, ttl=300):
    """
    Sets an in-memory cache value

    :param key:
        The string key

    :param data:
        The data to cache

    :param ttl:
        The integer number of second to cache the data for
    """

    _channel_repository_cache[key] = {
        'data': data,
        'expires': time.time() + ttl
    }


def set_cache_over_settings(destination, setting, key_prefix, value, ttl):
    """
    Take the value passed, and merge it over the current `setting`. Once
    complete, take the value and set the cache `key` and destination.settings
    `setting` to that value, using the `ttl` for set_cache().

    :param destination:
        An object that has a `.settings` attribute that is a dict

    :param setting:
        The dict key to use when pushing the value into the settings dict

    :param key_prefix:
        The string to prefix to `setting` to make the cache key

    :param value:
        The value to set

    :param ttl:
        The cache ttl to use
    """

    existing = destination.settings.get(setting, {})
    existing.update(value)
    set_cache(key_prefix + '.' + setting, value, ttl)
    destination.settings[setting] = value


def set_cache_under_settings(destination, setting, key_prefix, value, ttl, list_=False):
    """
    Take the value passed, and merge the current `setting` over it. Once
    complete, take the value and set the cache `key` and destination.settings
    `setting` to that value, using the `ttl` for set_cache().

    :param destination:
        An object that has a `.settings` attribute that is a dict

    :param setting:
        The dict key to use when pushing the value into the settings dict

    :param key_prefix:
        The string to prefix to `setting` to make the cache key

    :param value:
        The value to set

    :param ttl:
        The cache ttl to use
    """

    if value:
        if list_:
            existing = destination.settings.get(setting, [])
            value.extend(existing)
        else:
            existing = destination.settings.get(setting, {})
            value.update(existing)
        set_cache(key_prefix + '.' + setting, value, ttl)
        destination.settings[setting] = value
