from __future__ import absolute_import
from __future__ import with_statement

from celery.worker.hub import (
    DummyLock,
    BoundedSemaphore,
    Hub,
)

from mock import Mock, call, patch

from celery.tests.utils import Case


class File(object):

    def __init__(self, fd):
        self.fd = fd

    def fileno(self):
        return self.fd

    def __eq__(self, other):
        if isinstance(other, File):
            return self.fd == other.fd
        return NotImplemented


class test_DummyLock(Case):

    def test_context(self):
        mutex = DummyLock()
        with mutex:
            pass


class test_BoundedSemaphore(Case):

    def test_acquire_release(self):
        x = BoundedSemaphore(2)

        c1 = Mock()
        x.acquire(c1, 1)
        self.assertEqual(x.value, 1)
        c1.assert_called_with(1)

        c2 = Mock()
        x.acquire(c2, 2)
        self.assertEqual(x.value, 0)
        c2.assert_called_with(2)

        c3 = Mock()
        x.acquire(c3, 3)
        self.assertEqual(x.value, 0)
        self.assertFalse(c3.called)

        x.release()
        self.assertEqual(x.value, 1)
        c3.assert_called_with(3)

    def test_bounded(self):
        x = BoundedSemaphore(2)
        for i in xrange(100):
            x.release()
        self.assertEqual(x.value, 2)

    def test_grow_shrink(self):
        x = BoundedSemaphore(1)
        self.assertEqual(x.initial_value, 1)
        cb1 = Mock()
        x.acquire(cb1, 1)
        cb1.assert_called_with(1)
        self.assertEqual(x.value, 0)

        cb2 = Mock()
        x.acquire(cb2, 2)
        self.assertFalse(cb2.called)
        self.assertEqual(x.value, 0)

        cb3 = Mock()
        x.acquire(cb3, 3)
        self.assertFalse(cb3.called)

        x.grow(2)
        cb2.assert_called_with(2)
        cb3.assert_called_with(3)
        self.assertEqual(x.value, 3)
        self.assertEqual(x.initial_value, 3)

        self.assertFalse(x._waiting)
        x.grow(3)
        for i in xrange(x.initial_value):
            self.assertTrue(x.acquire(Mock()))
        self.assertFalse(x.acquire(Mock()))
        x.clear()

        x.shrink(3)
        for i in xrange(x.initial_value):
            self.assertTrue(x.acquire(Mock()))
        self.assertFalse(x.acquire(Mock()))
        self.assertEqual(x.value, 0)

        for i in xrange(100):
            x.release()
        self.assertEqual(x.value, x.initial_value)

    def test_clear(self):
        x = BoundedSemaphore(10)
        for i in xrange(11):
            x.acquire(Mock())
        self.assertTrue(x._waiting)
        self.assertEqual(x.value, 0)

        x.clear()
        self.assertFalse(x._waiting)
        self.assertEqual(x.value, x.initial_value)


class test_Hub(Case):

    @patch('kombu.utils.eventio.poll')
    def test_start_stop(self, poll):
        hub = Hub()
        hub.start()
        poll.assert_called_with()

        hub.stop()
        hub.poller.close.assert_called_with()

    def test_init(self):
        hub = Hub()
        cb1 = Mock()
        cb2 = Mock()
        hub.on_init.extend([cb1, cb2])

        hub.init()
        cb1.assert_called_with(hub)
        cb2.assert_called_with(hub)

    def test_fire_timers(self):
        hub = Hub()
        hub.timer = Mock()
        hub.timer._queue = []
        self.assertEqual(hub.fire_timers(min_delay=42.324,
                                         max_delay=32.321), 32.321)

        hub.timer._queue = [1]
        hub.scheduler = Mock()
        hub.scheduler.next.return_value = 3.743, None
        self.assertEqual(hub.fire_timers(), 3.743)

        e1, e2, e3 = Mock(), Mock(), Mock()
        entries = [e1, e2, e3]

        def se():
            if entries:
                return None, entries.pop()
            return 3.982, None
        hub.scheduler.next = Mock()
        hub.scheduler.next.side_effect = se

        self.assertEqual(hub.fire_timers(max_timers=10), 3.982)
        hub.timer.apply_entry.assert_has_calls(map(call, [e3, e2, e1]))

        entries[:] = [Mock() for _ in xrange(11)]
        keep = list(entries)
        self.assertEqual(hub.fire_timers(max_timers=10, min_delay=1.13), 1.13)
        hub.timer.apply_entry.assert_has_calls(
            map(call, reversed(keep[1:])),
        )
        self.assertEqual(hub.fire_timers(max_timers=10), 3.982)
        hub.timer.apply_entry.assert_has_calls(call(keep[0]))

    def test_update_readers(self):
        hub = Hub()
        P = hub.poller = Mock()

        read_A = Mock()
        read_B = Mock()
        hub.update_readers({10: read_A, File(11): read_B})

        P.register.assert_has_calls([
            call(10, hub.READ | hub.ERR),
            call(File(11), hub.READ | hub.ERR),
        ], any_order=True)

        self.assertIs(hub.readers[10], read_A)
        self.assertIs(hub.readers[11], read_B)

        hub.remove(10)
        self.assertNotIn(10, hub.readers)
        hub.remove(File(11))
        self.assertNotIn(11, hub.readers)
        P.unregister.assert_has_calls([
            call(10), call(File(11)),
        ])

    def test_can_remove_unknown_fds(self):
        hub = Hub()
        hub.poller = Mock()
        hub.remove(30)
        hub.remove(File(301))

    def test_remove__unregister_raises(self):
        hub = Hub()
        hub.poller = Mock()
        hub.poller.unregister.side_effect = OSError()

        hub.remove(313)

    def test_update_writers(self):
        hub = Hub()
        P = hub.poller = Mock()

        write_A = Mock()
        write_B = Mock()
        hub.update_writers({20: write_A, File(21): write_B})

        P.register.assert_has_calls([
            call(20, hub.WRITE),
            call(File(21), hub.WRITE),
        ], any_order=True)

        self.assertIs(hub.writers[20], write_A)
        self.assertIs(hub.writers[21], write_B)

        hub.remove(20)
        self.assertNotIn(20, hub.writers)
        hub.remove(File(21))
        self.assertNotIn(21, hub.writers)
        P.unregister.assert_has_calls([
            call(20), call(File(21)),
        ])

    def test_enter__exit(self):
        hub = Hub()
        P = hub.poller = Mock()
        hub.init = Mock()

        on_close = Mock()
        hub.on_close.append(on_close)

        with hub:
            hub.init.assert_called_with()

            read_A = Mock()
            read_B = Mock()
            hub.update_readers({10: read_A, File(11): read_B})
            write_A = Mock()
            write_B = Mock()
            hub.update_writers({20: write_A, File(21): write_B})
            self.assertTrue(hub.readers)
            self.assertTrue(hub.writers)
        self.assertFalse(hub.readers)
        self.assertFalse(hub.writers)

        P.unregister.assert_has_calls([
            call(10), call(11), call(20), call(21),
        ], any_order=True)

        on_close.assert_called_with(hub)

    def test_scheduler_property(self):
        hub = Hub(timer=[1, 2, 3])
        self.assertEqual(list(hub.scheduler), [1, 2, 3])
