# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4
#
# gst-python - Python bindings for GStreamer
# Copyright (C) 2002 David I. Lehn
# Copyright (C) 2004 Johan Dahlin
# Copyright (C) 2005 Edward Hervey
# Copyright (C) 2005 Thomas Vander Stichele
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA

from common import gobject, gst, unittest, TestCase, pygobject_2_13

import sys
import time

# see
# http://www.sicem.biz/personal/lgs/docs/gobject-python/gobject-tutorial.html
class MyBin(gst.Bin):
    _state_changed = False

    def __init__(self, name):
        # we need to call GObject's init to be able to do self.do_*
        gobject.GObject.__init__(self)
        # since we can't chain up to our parent's __init__, we set the
        # name manually 
        self.set_property('name', name)

    def do_change_state(self, state_change):
        if state_change == gst.STATE_CHANGE_PAUSED_TO_PLAYING:
            self._state_changed = True
        # FIXME: it seems a vmethod increases the refcount without unreffing
        # print self.__gstrefcount__
        # print self.__grefcount__

        # chain up to parent
        return gst.Bin.do_change_state(self, state_change)

# we need to register the type for PyGTK < 2.8
gobject.type_register(MyBin)

# FIXME: fix leak in vmethods before removing overriding fixture
class BinSubclassTest(TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def testStateChange(self):
        bin = MyBin("mybin")
        self.assertEquals(bin.__gstrefcount__, 1)
        self.assertEquals(sys.getrefcount(bin), pygobject_2_13 and 2 or 3)

        self.assertEquals(bin.get_name(), "mybin")
        self.assertEquals(bin.__gstrefcount__, 1)

        # test get_state with no timeout
        (ret, state, pending) = bin.get_state()
        self.failIfEqual(ret, gst.STATE_CHANGE_FAILURE)
        self.assertEquals(bin.__gstrefcount__, 1)

        # set to playing
        bin.set_state(gst.STATE_PLAYING)
        self.failUnless(bin._state_changed)

        # test get_state with no timeout
        (ret, state, pending) = bin.get_state()
        self.failIfEqual(ret, gst.STATE_CHANGE_FAILURE)

        if ret == gst.STATE_CHANGE_SUCCESS:
            self.assertEquals(state, gst.STATE_PLAYING)
            self.assertEquals(pending, gst.STATE_VOID_PENDING)

        # test get_state with a timeout
        (ret, state, pending) = bin.get_state(1)
        self.failIfEqual(ret, gst.STATE_CHANGE_FAILURE)

        if ret == gst.STATE_CHANGE_SUCCESS:
            self.assertEquals(state, gst.STATE_PLAYING)
            self.assertEquals(pending, gst.STATE_VOID_PENDING)

        (ret, state, pending) = bin.get_state(timeout=gst.SECOND)

        # back to NULL
        bin.set_state(gst.STATE_NULL)

class BinAddRemove(TestCase):
    def setUp(self):
        TestCase.setUp(self)
        self.bin = gst.Bin('bin')

    def tearDown(self):
        del self.bin
        TestCase.tearDown(self)

    def testError(self):
        gst.info("creating fakesrc")
        src = gst.element_factory_make('fakesrc', 'name')
        gst.info("creating fakesink")
        sink = gst.element_factory_make('fakesink', 'name')
        gst.info("adding src:%d to bin" % src.__gstrefcount__)
        self.assertEqual(src.__gstrefcount__, 1)
        self.bin.add(src)
        self.assertEqual(src.__gstrefcount__, 2)
        gst.info("added src:%d" % src.__gstrefcount__)
        self.assertRaises(gst.AddError, self.bin.add, sink)
        self.assertRaises(gst.AddError, self.bin.add, src)
        self.assertRaises(gst.RemoveError, self.bin.remove, sink)
        gst.info("removing src")
        self.bin.remove(src)
        gst.info("removed")
        self.assertRaises(gst.RemoveError, self.bin.remove, src)
        
    def testMany(self):
        src = gst.element_factory_make('fakesrc')
        sink = gst.element_factory_make('fakesink')
        self.bin.add(src, sink)
        self.assertRaises(gst.AddError, self.bin.add, src, sink)
        self.bin.remove(src, sink)
        self.assertRaises(gst.RemoveError, self.bin.remove, src, sink)

class Preroll(TestCase):
    def setUp(self):
        TestCase.setUp(self)
        self.bin = gst.Bin('bin')

    def tearDown(self):
        # FIXME: wait for state change thread to settle down
        while self.bin.__gstrefcount__ > 1:
            time.sleep(0.1)
        self.assertEquals(self.bin.__gstrefcount__, 1)
        del self.bin
        TestCase.tearDown(self)

    def testFake(self):
        src = gst.element_factory_make('fakesrc')
        sink = gst.element_factory_make('fakesink')
        self.bin.add(src)

        # bin will go to paused, src pad task will start and error out
        self.bin.set_state(gst.STATE_PAUSED)
        ret = self.bin.get_state()
        self.assertEquals(ret[0], gst.STATE_CHANGE_SUCCESS)
        self.assertEquals(ret[1], gst.STATE_PAUSED)
        self.assertEquals(ret[2], gst.STATE_VOID_PENDING)

        # adding the sink will cause the bin to go in preroll mode
        gst.debug('adding sink and setting to PAUSED, should cause preroll')
        self.bin.add(sink)
        sink.set_state(gst.STATE_PAUSED)
        ret = self.bin.get_state(timeout=0)
        self.assertEquals(ret[0], gst.STATE_CHANGE_ASYNC)
        self.assertEquals(ret[1], gst.STATE_PAUSED)
        self.assertEquals(ret[2], gst.STATE_PAUSED)

        # to actually complete preroll, we need to link and re-enable fakesrc
        src.set_state(gst.STATE_READY)
        src.link(sink)
        src.set_state(gst.STATE_PAUSED)
        ret = self.bin.get_state()
        self.assertEquals(ret[0], gst.STATE_CHANGE_SUCCESS)
        self.assertEquals(ret[1], gst.STATE_PAUSED)
        self.assertEquals(ret[2], gst.STATE_VOID_PENDING)

        self.bin.set_state(gst.STATE_NULL)
        self.bin.get_state()
 
class ConstructorTest(TestCase):
    def testGood(self):
        bin = gst.Bin()
        bin = gst.Bin(None)
        bin = gst.Bin('')
        bin = gst.Bin('myname')
        
    def testBad(self):
        # these are now valid. pygobject will take care of converting
        # the arguments to a string.
        #self.assertRaises(TypeError, gst.Bin, 0)
        #self.assertRaises(TypeError, gst.Bin, gst.Bin())
        pass
        
if __name__ == "__main__":
    unittest.main()