# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Author: Olivier Tilloy <olivier@fluendo.com>

"""
Caching and serialization facilities.
"""

try:
    from hashlib import md5
except ImportError:
    # hashlib is new in Python 2.5
    from md5 import md5

import os
import cPickle

from elisa.core import default_config
from elisa.core.utils import defer
from elisa.core.utils.internet import get_page


def get_cache_path(cache_dir):
    """
    Return the absolute path to a specific cache directory.

    The cache directory is located in the local moovida directory and will be
    created if it doesn't exist yet.

    @param cache_dir: the name of the cache directory
    @type cache_dir:  C{unicode}

    @return: the absolute path to the cache directory
    @rtype:  C{unicode}
    """
    cache = os.path.join(default_config.CONFIG_DIR, cache_dir)
    if not os.path.exists(cache):
        os.makedirs(cache, 0755)
    return cache


def get_pictures_cache_path():
    """
    @return: the absolute path to the pictures cache directory
    @rtype:  C{unicode}
    """
    return get_cache_path(u'pictures_cache')


def get_cached_file_path(uri, cache_path, extension=None):
    """
    Return the full path to a file cached on disk corresponding to the URI.
    This does not check whether the cached file actually exists.

    This implementation is very loosely based on the freedesktop thumbnail
    specification (http://jens.triq.net/thumbnail-spec/).

    @param uri:        the URI to a resource to cache
    @type uri:         L{elisa.core.media_uri.MediaUri}
    @param cache_path: the full path to the cache directory
    @type cache_path:  C{unicode}
    @param extension:  the file extension the cached file should have
                       (default C{None}, use the URI's path extension)
    @type extension:   C{unicode}

    @return: the full path to the file cached on disk
    @rtype:  C{unicode}
    """
    if uri.scheme == 'file':
        # The file is local, it does not make sense to cache it
        return os.path.normpath(uri.path)

    digest = md5(unicode(uri)).hexdigest()
    if extension is None:
        extension = uri.extension
    if extension:
        cached_file = u'%s.%s' % (digest, extension)
    else:
        cached_file = unicode(digest)

    return os.path.join(cache_path, cached_file)


def get_cached_image_path(image_uri):
    """
    Specialized implementation of get_cached_file_path for images.

    @param image_uri: the URI to an image resource to cache
    @type image_uri:  L{elisa.core.media_uri.MediaUri}

    @return: the full path to the image cached on disk
    @rtype:  C{unicode}
    """
    return get_cached_file_path(image_uri, get_pictures_cache_path())


def cache_to_file(data, cache_file_path):
    """
    Write some data to a cache file on disk for faster subsequent access.

    If the cache file already exists, it is overwritten.

    @param data:            the binary data to cache
    @type data:             C{str}
    @param cache_file_path: the full path to the cache file
    @type cache_file_path:  C{unicode}

    @return: the full path to the cache file (C{cache_file_path})
    @rtype:  C{unicode}
    """
    # TODO: make this asynchronous
    fd = open(cache_file_path, 'wb')
    try:
        fd.write(data)
    finally:
        fd.close()
    return cache_file_path


def _get(uri):
    # This function is meant to be monkey-patched in unit tests.
    return get_page(uri)


def get_and_cache_to_file(uri, cache_path):
    """
    Get a distant resource over HTTP if needed and cache it.

    @param uri:        the URI of the resource to retrieve and cache
    @type uri:         L{elisa.core.media_uri.MediaUri}
    @param cache_path: the full path to the cache directory
    @type cache_path:  C{unicode}

    @return: a deferred fired with the full path to the cached file when done
    @rtype:  L{elisa.core.utils.defer.Deferred}
    """
    # FIXME: when the distant resource needs to be retrieved, the deferred
    # returned is not cancellable.
    cached_file_path = get_cached_file_path(uri, cache_path)
    if os.path.exists(cached_file_path):
        return defer.succeed(cached_file_path)

    dfr = _get(str(uri))
    dfr.addCallback(cache_to_file, cached_file_path)
    return dfr


def serialize(obj, dump_file_path):
    """
    Serialize an object to a file on disk.

    @param obj:            the object to serialize
    @type obj:             C{object}
    @param dump_file_path: the full path to the file to dump the object to
    @type dump_file_path:  C{unicode}
    """
    fd = open(dump_file_path, 'wb')
    try:
        cPickle.dump(obj, fd)
    finally:
        fd.close()


def deserialize(dump_file_path):
    """
    De-serialize a object from a file on disk.

    @param dump_file_path: the full path to the file the object was dumped to
    @type dump_file_path:  C{unicode}

    @return: the de-serialized object
    @rtype:  C{object}

    @raise C{IOError}: if the file cannot be read
    """
    fd = open(dump_file_path, 'rb')
    try:
        obj = cPickle.load(fd)
    finally:
        fd.close()
    return obj
