class test_KATCPClientResource_IntegratedTimewarp(TimewarpAsyncTestCase):
    def setUp(self):
        super(test_KATCPClientResource_IntegratedTimewarp, self).setUp()
        self.server = DeviceTestServer('', 0)
        start_thread_with_cleanup(self, self.server)
        self.host, self.port = self.server.bind_address
        self.default_resource_spec = dict(
            name='thething',
            address=self.server.bind_address,
            controlled=True)

    @tornado.gen.coroutine
    def _get_DUT_and_sync(self, resource_spec):
        DUT = resource_client.KATCPClientResource(self.default_resource_spec)
        DUT.start()
        yield DUT.until_state('synced')
        raise tornado.gen.Return(DUT)

    @tornado.testing.gen_test
    def test_disconnect(self):
        # Test that a device disconnect / reconnect is correctly handled
        DUT = yield self._get_DUT_and_sync(self.default_resource_spec)
        initial_reqs = set(DUT.req)
        initial_sensors = set(DUT.sensor)
        self.server.stop()
        self.server.join(timeout=1)
        yield DUT.until_state('disconnected')

        # Test that requests fail
        rep = yield DUT.req.watchdog()
        self.assertFalse(rep.succeeded)

        # Restart device so that we can reconnect
        self.server.start()
        # timewarp beyond reconect delay
        self.set_ioloop_time(self.ioloop_time + 1)
        yield DUT.until_state('syncing')
        yield DUT.until_state('synced')
        # check that sensors / requests are unchanged
        self.assertEqual(set(DUT.req), initial_reqs)
        self.assertEqual(set(DUT.sensor), initial_sensors)

        # Now disconnect and change the device, to check that it is properly resynced.
        self.server.stop()
        self.server.join(timeout=1)
        yield DUT.until_state('disconnected')

        # Add a new request to the server
        def request_sparkling_new(self, req, msg):
            """A new command."""
            return Message.reply(msg.name, "ok", "bling1", "bling2")
        self.server._request_handlers['sparkling-new'] = request_sparkling_new
        # Check that the request does not exist currently
        self.assertNotIn('sparkling_new', initial_reqs)

        # Add a new sensor to the server
        sensor = DeviceTestSensor(DeviceTestSensor.INTEGER, "another.int",
                                  "An Integer.",
                                  "count", [-5, 5], timestamp=self.io_loop.time(),
                                  status=DeviceTestSensor.NOMINAL, value=3)
        self.server.add_sensor(sensor)
        # Check that the sensor does not exist currently
        escaped_new_sensor = resource.escape_name(sensor.name)
        self.assertNotIn(resource.escape_name(sensor.name), initial_sensors)

        # Restart device so that we can reconnect
        self.server.start()
        # timewarp beyond reconect delay
        self.set_ioloop_time(self.ioloop_time + 1)
        yield DUT.until_state('syncing')
        yield DUT.until_state('synced')
        # check that sensors / requests are correctly updated
        self.assertEqual(set(DUT.req), initial_reqs | set(['sparkling_new']))
        self.assertEqual(set(DUT.sensor), initial_sensors | set([escaped_new_sensor]))

    @tornado.testing.gen_test(timeout=1000)
    def test_set_sensor_sampling(self):
        self.server.stop()
        self.server.join()
        DUT = resource_client.KATCPClientResource(self.default_resource_spec)
        DUT.start()
        yield tornado.gen.moment
        test_strategy = ('period', '2.5')
        yield DUT.set_sensor_strategy('an_int', test_strategy)
        # Double-check that the sensor does not yet exist
        self.assertNotIn('an_int', DUT.sensor)
        self.server.start()
        self.server.wait_running(timeout=1)
        advancer = TimewarpAsyncTestCaseTimeAdvancer(self, quantum=0.55)
        advancer.start()
        yield DUT.until_synced()
        self.assertEqual(DUT.sensor.an_int.sampling_strategy, test_strategy)

        # Now call set_sensor_strategy with a different strategy and check that it is
        # applied to the real sensor
        new_test_strategy = ('event',)
        yield DUT.set_sensor_strategy('an_int', new_test_strategy)
        self.assertEqual(DUT.sensor.an_int.sampling_strategy, new_test_strategy)

    @tornado.testing.gen_test(timeout=1000)
    def test_set_sensor_listener(self):
        self.server.stop()
        self.server.join()
        resource_spec = self.default_resource_spec
        DUT = resource_client.KATCPClientResource(resource_spec)
        DUT.start()
        yield tornado.gen.moment
        test_listener1 = lambda *x : None
        test_listener2 = lambda *y : None
        DUT.set_sensor_listener('an_int', test_listener1)
        # Double-check that the sensor does not yet exist
        self.assertNotIn('an_int', DUT.sensor)
        self.server.start()
        self.server.wait_running(timeout=1)
        advancer = TimewarpAsyncTestCaseTimeAdvancer(self, quantum=0.55)
        advancer.start()
        yield DUT.until_synced()
        self.assertTrue(DUT.sensor.an_int.is_listener, test_listener1)

        # Now call set_sensor_lister with a different listener and check that it is
        # also subscribed
        DUT.set_sensor_listener('an_int', test_listener2)
        self.assertTrue(DUT.sensor.an_int.is_listener, test_listener2)