# Elisa - Home multimedia server
# Copyright (C) 2007-2008 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 Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" in the root directory of this distribution package
# for details on that license.

__maintainer__ = 'Alessandro Decina <alessandro@fluendo.com>'

import gobject
gobject.threads_init()
import gst

from elisa.core.tests.elisa_test_case import ElisaTestCase
from elisa.core.tests.component_test_case import ComponentTestCase
from elisa.core.media_uri import MediaUri

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

from twisted.trial.unittest import SkipTest

from elisa.plugins.gstreamer_plugin.gst_metadata import \
        GstMetadataPipeline, MetadataClientProcessLauncher, TimeoutError, \
        UriError

class TestSrc(gst.Bin):
    def __init__(self, source):
        gst.Bin.__init__(self)

        identity = gst.element_factory_make('identity', 'identity')
        self.add(source, identity)

        srcpad = gst.GhostPad('src', identity.get_pad('src'))
        srcpad.set_active(True)
        self.add_pad(srcpad)

        source.link(identity)
gobject.type_register(TestSrc)

class TestMetadataAudioPipeline(GstMetadataPipeline):
    def _plug_src(self, uri):
        src = gst.element_factory_make('audiotestsrc')
        src = TestSrc(src)

        return src

class TestMetadataErrorPipeline(GstMetadataPipeline):
    def _plug_src(self, uri):
        src = gst.element_factory_make('audiotestsrc')
        src = TestSrc(src)
        src.get_by_name('identity').props.error_after = 1

        return src

class TestMetadataTimeoutPipeline(GstMetadataPipeline):
    def _plug_src(self, uri):
        src = gst.element_factory_make('audiotestsrc')
        src = TestSrc(src)
        src.get_by_name('identity').props.drop_probability = 1

        return src

class TestMetadataVideoPipeline(GstMetadataPipeline):
    def _build_pipeline(self):
        GstMetadataPipeline._build_pipeline(self)
        self._thumbnail = False

    def _plug_src(self, uri):
        src = gst.element_factory_make('videotestsrc')
        src = TestSrc(src)
        
        return src

    def _have_thumbnail(self):
        # toggle _have_thumbnail so that we go to PAUSED and then stop on the
        # first frame
        self._thumbnail = not self._thumbnail
        return not self._thumbnail

class EncodedImageSrc(gst.Bin):
    def __init__(self):
        gst.Bin.__init__(self)

        src = gst.element_factory_make('videotestsrc')
        src.props.num_buffers = 1
        enc = gst.element_factory_make('pngenc', 'testpngenc')
        enc.props.snapshot = False
        self.add(src, enc)

        srcpad = gst.GhostPad('src', enc.get_pad('src'))
        srcpad.set_active(True)
        self.add_pad(srcpad)

        src.link(enc)
gobject.type_register(EncodedImageSrc)
#gst.element_register(EncodedImageSrc.__gtype__, 'encodedimgsrc')

class TestMetadataImagePipeline(GstMetadataPipeline):
    def _build_pipeline(self):
        GstMetadataPipeline._build_pipeline(self)
        self._thumbnail = False

    def _plug_src(self, uri):
        src = TestSrc(EncodedImageSrc())
        
        return src

    def _have_thumbnail(self):
        # toggle _have_thumbnail so that we go to PAUSED and then stop on the
        # first frame
        self._thumbnail = not self._thumbnail
        return not self._thumbnail

class TestGstMetadataPipelineCommon(object):
    """Whitebox tests for GstMetadataPipeline"""
    def setUpClass(self):
        self.audio_pipeline = self.build_audio_pipeline()
        self.video_pipeline = self.build_video_pipeline()
        self.image_pipeline = self.build_image_pipeline()
        self.error_pipeline = self.build_error_pipeline()
        self.timeout_pipeline = self.build_timeout_pipeline()
        self.audio_pipeline.initialize()
        self.video_pipeline.initialize()
        self.image_pipeline.initialize()
        self.error_pipeline.initialize()
        self.timeout_pipeline.initialize()

    def tearDownClass(self):
        self.audio_pipeline.clean()
        self.video_pipeline.clean()
        self.image_pipeline.clean()
        self.error_pipeline.clean()
        self.timeout_pipeline.clean()

    def test_get_audio_media_type(self):
        def get_metadata_done(result):
            self.assertEqual(result['file_type'], 'audio')

        media_type_dict = {'uri': MediaUri('file://not_used_for_the_test'),
                'file_type': None, 'mime_type': None}
        dfr = self.audio_pipeline.get_metadata(media_type_dict)
        dfr.addCallback(get_metadata_done)

        return dfr

    def test_get_video_media_type(self):
        media_type_dict = {'uri': MediaUri('file://not_used_for_the_test'),
                'file_type': None, 'mime_type': None}
        dfr = self.video_pipeline.get_metadata(media_type_dict)

        def get_metadata_done(result):
            self.assertEqual(result['file_type'], 'video')

        dfr.addCallback(get_metadata_done)

        return dfr
    
    def test_get_image_media_type(self):
        media_type_dict = {'uri': MediaUri('file://not_used_for_the_test'),
                'file_type': None, 'mime_type': None}
        dfr = self.image_pipeline.get_metadata(media_type_dict)

        def get_metadata_done(result):
            self.assertEqual(result['file_type'], 'image')

        dfr.addCallback(get_metadata_done)

        return dfr

    def _eat_buffers_probe_cb(self, buffer, data):
        return False

    def test_metadata_timeout(self):
        def get_metadata_result(result):
            self.failUnless(isinstance(result, failure.Failure))
            result.trap(TimeoutError)

        media_type_dict = {'uri': MediaUri('file://not_used_for_the_test'),
                'file_type': None, 'media_type': None}
        dfr = self.timeout_pipeline.get_metadata(media_type_dict)
        dfr.addBoth(get_metadata_result)
        
        return dfr

    def test_error(self):
        def get_metadata_result(result):
            self.failUnless(isinstance(result, failure.Failure)) 
        
        media_type_dict = {'uri': MediaUri('file://not_used_for_the_test'),
                'file_type': None, 'media_type': None}
        dfr = self.error_pipeline.get_metadata(media_type_dict)
        dfr.addBoth(get_metadata_result)
        
        return dfr

class TestGstMetadataPipelineNoReuse(ElisaTestCase,
        TestGstMetadataPipelineCommon):
   
    if gst.pygst_version < (0, 10, 9):
        skip = "breaks with gst-python < 0.10.9"    
 
    def build_audio_pipeline(self):
        pipeline = TestMetadataAudioPipeline()
        pipeline.reuse_elements = False
        
        return pipeline
    
    def build_video_pipeline(self):
        pipeline = TestMetadataVideoPipeline()
        pipeline.reuse_elements = False
        
        return pipeline
    
    def build_image_pipeline(self):
        pipeline = TestMetadataImagePipeline()
        pipeline.reuse_elements = False
        
        return pipeline
    
    def build_error_pipeline(self):
        pipeline = TestMetadataErrorPipeline()
        pipeline.reuse_elements = False
        
        return pipeline
    
    def build_timeout_pipeline(self):
        pipeline = TestMetadataTimeoutPipeline()
        pipeline.reuse_elements = False
        
        return pipeline

class TestGstMetadataPipelineReuse(ElisaTestCase,
        TestGstMetadataPipelineCommon):
    if gst.pygst_version < (0, 10, 9):
        skip = "breaks with gst-python < 0.10.9"    

    def build_audio_pipeline(self):
        pipeline = TestMetadataAudioPipeline()
        pipeline.reuse_elements = True
        
        return pipeline
    
    def build_video_pipeline(self):
        pipeline = TestMetadataVideoPipeline()
        pipeline.reuse_elements = True
        
        return pipeline
    
    def build_image_pipeline(self):
        pipeline = TestMetadataImagePipeline()
        pipeline.reuse_elements = True
        
        return pipeline
    
    def build_error_pipeline(self):
        pipeline = TestMetadataErrorPipeline()
        pipeline.reuse_elements = True
        
        return pipeline
    
    def build_timeout_pipeline(self):
        pipeline = TestMetadataTimeoutPipeline()
        pipeline.reuse_elements = True
        
        return pipeline

class TestGstMetadataBase(object):
    def test_able_to_handle(self):
        # no uri
        metadata = {'foo': None}
        self.failIf(self.component.able_to_handle(metadata))

        # no uri and supported field
        metadata = {'song': None}
        self.failIf(self.component.able_to_handle(metadata))

        # unsupported uri scheme
        metadata = {'uri': MediaUri('gopher://')}
        self.failIf(self.component.able_to_handle(metadata))

        # unsupported uri scheme with supported empty field
        metadata = {'uri': MediaUri('gopher://'), 'song': None}
        self.failIf(self.component.able_to_handle(metadata))

        # supported uri but no empty field
        metadata = {'uri': MediaUri('file://ciao'), 'artist': 'fabrizio de andre'}
        self.failIf(self.component.able_to_handle(metadata))

        # supported uri but no supported field empty
        metadata = {'uri': MediaUri('file://ciao'), 'artist': 'fabrizio de andre',
                'foo': None}
        self.failIf(self.component.able_to_handle(metadata))
        
        # supported uri and supported field empty
        metadata = {'uri': MediaUri('file://ciao'), 'song': None}
        self.failUnless(self.component.able_to_handle(metadata))
    
    def test_metadata_already_failed(self):
        if gst.pygst_version < (0, 10, 9):
            raise SkipTest("breaks with gst-python < 0.10.9")
 
        def create_test():
            media_type_dict = {'uri': MediaUri('file://not_used_for_the_test'),
                'file_type': None, 'media_type': None}
            dfr = self.component.get_metadata(media_type_dict)
            return dfr

        def get_metadata_failed_result(result):
            self.failUnless(isinstance(result, failure.Failure))
            result.trap(UriError)
            
        def get_metadata_result(result):
            self.failUnless(isinstance(result, failure.Failure))
            result.trap(Exception)
            # okay. do it again :D
            dfr = create_test()
            dfr.addBoth(get_metadata_failed_result)
            return dfr

        dfr = create_test()
        dfr.addBoth(get_metadata_result)
        
        return dfr

class TestGstMetadata(ComponentTestCase, TestGstMetadataBase):
    component_class = \
            'elisa.plugins.gstreamer_plugin.gst_metadata:GstMetadata' 

class TestGstMetadataClient(ComponentTestCase, TestGstMetadataBase):
    component_class = \
            'elisa.plugins.gstreamer_plugin.gst_metadata:GstMetadataClient'

class TestMetadataClientProcessLauncher(ElisaTestCase):
    def setUp(self):
        self.launcher = MetadataClientProcessLauncher()

    def test_start_stop(self):
        def get_component_done_after_stop(result):
            self.failUnless(isinstance(result, failure.Failure))

        dfr = self.launcher.startProcess()
        dfr.addCallback(lambda result: self.launcher.stopProcess())
        dfr.addCallback(lambda result: self.launcher.get_component())
        dfr.addBoth(get_component_done_after_stop)

        return dfr
        

    def test_restart(self):
        """Start the metadata_runner process, kill it and check that a
        get_component() call still succeeds
        """
        def second_get_component_done(result, test_dfr):
            dfr = self.launcher.stopProcess()
            dfr.chainDeferred(test_dfr)

        def continue_after_kill(test_dfr):
            dfr = self.launcher.get_component()
            dfr.addCallback(second_get_component_done, test_dfr)
            
        def get_component_done(component):
            self.launcher.process.signalProcess('KILL')
            # continue later so the signal is dispatched
            dfr = defer.Deferred()
            reactor.callLater(1, continue_after_kill, dfr)

            return dfr

        dfr = self.launcher.startProcess()
        dfr.addCallback(get_component_done)

        return dfr

    def test_give_up(self):
        """Start the process, set max_retries to 0 and kill the process. Check
        that the launcher gives up after max_retries
        """
        def get_component_result(result, test_dfr):
            self.failUnless(isinstance(result, failure.Failure))
            test_dfr.callback(None)

        def continue_after_kill(test_dfr):
            dfr = self.launcher.get_component()
            dfr.addBoth(get_component_result, test_dfr)
        
            return dfr

        def get_component_done(component):
            self.launcher.max_retries = 0
            self.launcher.process.signalProcess('KILL')
            dfr = defer.Deferred()
            reactor.callLater(1, continue_after_kill, dfr)
            
            return dfr 

        dfr = self.launcher.startProcess()
        dfr.addCallback(get_component_done)

        return dfr

