# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-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.
#
# Authors: Alessandro Decina <alessandro@fluendo.com>

import gobject
gobject.threads_init()
import gst
import os
import platform

from twisted.internet import defer, reactor
from twisted.python import failure

from elisa.core.components.metadata_provider import MetadataProvider, \
        MetadataError
from elisa.core.log import Loggable
from elisa.core.media_uri import unquote
from elisa.core.component import InitializeFailure

from elisa.plugins.gstreamer.request import Request, RequestQueue
from elisa.plugins.gstreamer.pipeline import GstMetadataPipeline, \
        supported_metadata_keys, media_type_keys, thumbnail_keys, \
        supported_keys, get_thumbnail_location

supported_schemes = ['file', 'http']

if platform.system() == 'windows':
    import win32process

class UriError(MetadataError):
    pass

def able_to_handle(supported_schemes, supported_keys, metadata):
    uri = metadata.get('uri')
    if not uri or uri.scheme not in supported_schemes:
        return False

    keys = set(metadata.keys())
    if uri.scheme == 'file' and os.path.isdir(uri.path) and \
            keys != media_type_keys:
        return False

    request_keys = supported_keys.intersection(metadata.keys())
    request_empty_keys = \
            [key for key in request_keys if metadata[key] is None]

    if request_empty_keys:
        return True

    return False 

class GstMetadata(MetadataProvider):
    def __init__(self, next_interval=0.00):
        super(GstMetadata, self).__init__()
        self._pipeline = GstMetadataPipeline()
        self._uri_cache = {}
        self._requests = RequestQueue()
        self._running = False
        self._process_next_call = None
        self._process_next_interval = next_interval
        self._failed_uris = {}

    def clean(self):
        if self._process_next_call is not None:
            self._process_next_call.cancel()
            self._process_next_call = None
        self._pipeline.clean()

        return super(GstMetadata, self).clean()

    def initialize(self, retself=True):
        try:
            self._pipeline.initialize()
        except Exception, exc:
            msg = "Could not initialize the Pipeline: %s" % exc
            raise InitializeFailure(msg)

        if retself:
            return defer.succeed(self)

        return defer.succeed(None)

    def _reset(self):
        # timestamps used for logging purposes
        pass

    def get_rank(self):
        return 10

    def able_to_handle(self, metadata):
        return able_to_handle(supported_schemes,
                supported_keys, metadata)

    def get_metadata(self, metadata):
        blacklist_value = self._failed_uris.get(unicode(metadata['uri']))

        keys = set(metadata.keys())
        
        if keys == thumbnail_keys:
            if blacklist_value == 'thumbnail-failed':
                return defer.fail(UriError())

            location = get_thumbnail_location(metadata['uri'])
            if os.path.exists(location):
                stat = os.stat(location)
                if stat.st_size != 0:
                    self.debug('thumbnail cached %s' % location)
                    metadata['thumbnail'] = location
                    return defer.succeed(metadata)

        if keys == media_type_keys and blacklist_value == 'media-type-failed':
            return defer.fail(UriError())

        dfr = self._requests.enqueue(metadata)
        if not self._running:
            self._running = True
            self._process_next()

        return dfr

    def _cancel_request(self, request_id):
        request.cancelled = True

    def set_process_interval(self, value):
        self._process_next_interval = value

    def _process_next(self):
        if self._process_next_call is not None:
            return

        self._process_next_call = \
                reactor.callLater(self._process_next_interval,
                        self._process_next_real)

    def _process_next_real(self):
        self._process_next_call = None

        try:
            request = self._requests.dequeue()
        except IndexError:
            self._running = False
            self.debug('metadata queue empty')

            return

        self.debug('getting metadata %s, queue length %d' %
                (request.metadata, len(self._requests)))

        metadata_defer = None
        uri = request.metadata['uri']
        # FIXME uri.path should probably be unquoted inside MediaUri
        if uri.scheme == 'file' and os.path.isdir(unquote(uri.path)):
            request.metadata['file_type'] = 'directory'
            metadata_defer = defer.succeed(request.metadata)
        else:
            try:
                metadata_defer = self._pipeline.get_metadata(request.metadata)
            except:
                error = failure.Failure()
                metadata_defer = defer.fail(error)

        metadata_defer.addCallbacks(self._done, self._failed,
                callbackArgs=(request,), errbackArgs=(request,))

    def _done(self, metadata, request):
        self._process_next()
        # NOTE: this can't be called from the streaming thread
        self._reset()

        request.defer.callback(metadata)

    def _failed(self, failure, request):
        self._process_next()
        self._reset()

        keys = set(request.metadata)
        if keys == thumbnail_keys:
            blacklist_value = 'thumbnail-failed'
        else:
            blacklist_value = 'media-type-failed'
        
        self._failed_uris[unicode(request.metadata['uri'])] = blacklist_value

        request.defer.errback(failure)

class GstMetadataFast(MetadataProvider):
    def __init__(self):
        super(GstMetadataFast, self).__init__()
        self.media_type_provider = GstMetadata(0.05)
        self.metadata_provider = GstMetadata()

    def initialize(self):
        dfr1 = self.media_type_provider.initialize()
        dfr2 = self.metadata_provider.initialize()
    
        dfr = defer.DeferredList([dfr1, dfr2], fireOnOneErrback=True)
        dfr.addCallback(lambda result: self)

        return dfr

    def clean(self):
        dfr1 = self.media_type_provider.clean()
        dfr2 = self.metadata_provider.clean()
        
        dfr = defer.DeferredList([dfr1, dfr2], fireOnOneErrback=True)

        def parent_clean(result):
            return super(GstMetadataFast, self).clean()

        dfr.addCallbacks(parent_clean)

        return dfr

    def get_rank(self):
        return 10

    def able_to_handle(self, metadata):
        return able_to_handle(supported_schemes,
                supported_keys, metadata)
                
    def set_process_interval(self, value):
        return self.metadata_provider.set_process_interval(value)

    def get_metadata(self, metadata):
        req_keys = set(metadata.keys())
        if req_keys == media_type_keys:
            return self.media_type_provider.get_metadata(metadata)
        
        return self.metadata_provider.get_metadata(metadata)
