class TestUnitInstrumentProtocol(IonUnitTestCase):
    """
    Test cases for instrument protocol class. Functions in this class provide
    instrument protocol unit tests and provide a tutorial on use of
    the protocol interface.
    """ 
    def setUp(self):
        """
        """
        self.callback_result = None
        
        def protocol_callback(self, arg):
            callback_result = arg
            
        self.protocol = InstrumentProtocol(protocol_callback)
    
    def test_extraction(self):
        sample_line = "SATPAR0229,10.01,2206748544,234"
        result = self.protocol._extract_sample(SatlanticPARDataParticle,
                                               SAMPLE_REGEX,
                                               sample_line,
                                               publish=False)
    
        self.assertTrue(result['parsed'])
        self.assertTrue(result['raw'])

        # Test the format of the result in the individual driver tests. Here,
        # just tests that the result is there.
        
    def test_publish_raw(self):
        """
        Tests to see if raw data is appropriately published back out to
        the InstrumentAgent via the event callback.
        """
        # build a packet
        # have it published by the protocol (force state if needed)
        # delay?
        # catch it in the  callback
        # confirm it came back
        # compare response to original packet
        
        self.assertTrue(False)
    
    def test_publish_parsed_data(self):
        """
        Tests to see if parsed data is appropriately published back to the
        InstrumentAgent via the event callback.
        """
        # similar to above
        self.assertTrue(False)

    def test_publish_engineering_data(self):
        """
        Tests to see if engineering data is appropriately published back to the
        InstrumentAgent via the event callback.
        """
        # similar to above
        self.assertTrue(False)    
class TestUnitInstrumentProtocol(MiUnitTestCase):
    """
    Test cases for instrument protocol class. Functions in this class provide
    instrument protocol unit tests and provide a tutorial on use of
    the protocol interface.
    """ 
    def setUp(self):
        """
        """
        self.callback_result = None
        self._trigger_count = 0
        self._events = []

        self.protocol = InstrumentProtocol(self.event_callback)

    def event_callback(self, event, value=None):
        log.debug("Test event callback: %s" % event)
        self._events.append(event)
        self._trigger_count += 1

    def _scheduler_callback(self):
        """
        Callback to test the scheduler
        """
        self._trigger_count += 1

    def assert_scheduled_event_triggered(self, event_count=1):
        count = 0
        for i in range(0, 40):
            count = self._trigger_count
            log.debug("check for triggered event, count %d" % self._trigger_count)
            if(count >= event_count): break
            time.sleep(0.3)

        self.assertGreater(count, 0)


    def test_extraction(self):
        sample_line = "SATPAR0229,10.01,2206748544,234\r\n"
        ntptime = ntplib.system_to_ntp_time(time.time())
        result = self.protocol._extract_sample(SatlanticPARDataParticle,
                                               SAMPLE_REGEX,
                                               sample_line,
                                               ntptime,
                                               publish=False)

        log.debug("R: %s" % result)
        self.assertEqual(result['stream_name'], SatlanticPARDataParticle(None, None).data_particle_type())

        # Test the format of the result in the individual driver tests. Here,
        # just tests that the result is there.

    @unittest.skip('Not Written')
    def test_publish_raw(self):
        """
        Tests to see if raw data is appropriately published back out to
        the InstrumentAgent via the event callback.
        """
        # build a packet
        # have it published by the protocol (force state if needed)
        # delay?
        # catch it in the  callback
        # confirm it came back
        # compare response to original packet
        
        self.assertTrue(False)

    @unittest.skip('Not Written')
    def test_publish_parsed_data(self):
        """
        Tests to see if parsed data is appropriately published back to the
        InstrumentAgent via the event callback.
        """
        # similar to above
        self.assertTrue(False)

    @unittest.skip('Not Written')
    def test_publish_engineering_data(self):
        """
        Tests to see if engineering data is appropriately published back to the
        InstrumentAgent via the event callback.
        """
        # similar to above
        self.assertTrue(False)
        
    def test_get_running_config(self):
        """
        Checks to see that one can successfully get the running config from an
        instrument protocol.
        """
        # set some values
        log.debug("First param_dict: %s", self.protocol._param_dict.get_config())
        self.protocol._param_dict.add("foo", r'foo=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=True,
                             default_value=10)
        self.protocol._param_dict.set_default("foo") # test hack to set w/o fetch
        self.protocol._param_dict.add("bar", r'bar=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=False,
                             default_value=15)
        self.protocol._param_dict.set_default("bar")
                
        self.assertEquals(self.protocol._param_dict.get("foo"), 10)
        self.assertEquals(self.protocol._param_dict.get("bar"), 15)
        result = self.protocol.get_cached_config()
        self.assertEquals(result['foo'], 10)
        self.assertEquals(result['bar'], 15)

        self.protocol._param_dict.update("bar=20")
        result = self.protocol.get_cached_config()
        self.assertEquals(result['foo'], 10)
        self.assertEquals(result['bar'], 20)
        self.assertEquals(self.protocol._param_dict.get("bar"), 20)
        
        # get and check the running config
        result = self.protocol.get_cached_config()
        self.assertTrue(isinstance(result, dict))
        self.assertEquals(result['foo'], 10)
        self.assertEquals(result['bar'], 20)

    def test_init_values(self):
        """
        Test getting and setting the initialization value for a parameter
        """
        # set an additional value for test
        self.protocol._param_dict.add("foo", r'foo=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=True,
                             startup_param=True,
                             default_value=10)
        self.protocol._param_dict.add("bar", r'bar=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=False,
                             startup_param=True,
                             default_value=0)
        self.protocol._param_dict.add("baz", r'baz=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=True,
                             default_value=20)
        self.protocol._param_dict.add("bat", r'bat=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             startup_param=False,
                             default_value=20)
        self.protocol._param_dict.add("qux", r'qux=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             startup_param=True)
        self.protocol._param_dict.add("rok", r'rok=(.*)',
                             lambda match : int(match.group(1)),
                             lambda x : str(x))
        self.protocol._param_dict.update("qux=6666")
        
        # mark init params
        self.assertRaises(InstrumentParameterException,
                          self.protocol.set_init_params, [])
        self.protocol.set_init_params({DriverConfigKey.PARAMETERS: {"foo": 1111, "baz":2222}})
        
        # get new startup config
        self.assertRaises(InstrumentProtocolException, self.protocol.get_startup_config)
        self.protocol.set_init_params({DriverConfigKey.PARAMETERS: {"foo": 1111, "baz":2222, "bat": 11, "qux": 22}})
        result = self.protocol.get_startup_config()
        
        self.assertEquals(len(result), 5)
        self.assertEquals(result["foo"], 1111) # init param
        self.assertEquals(result["bar"], 0)    # init param with default value
        self.assertEquals(result["baz"], 2222) # non-init param, but value specified
        self.assertEquals(result["bat"], 11)   # set param
        self.assertEquals(result["qux"], 22)   # set param
        self.assertIsNone(result.get("rok"))   # defined in paramdict, no config

    def test_apply_startup_params(self):
        """
        Test that the apply startup parameters method exists and throws
        a "not implemented" exception for the base class
        """
        self.assertRaises(NotImplementedException,
                          self.protocol.apply_startup_params)

    def test_scheduler(self):
        """
        Test to see that the scheduler can add and remove jobs properly
        Jobs are just queued for adding unit we call initialize_scheduler
        then the jobs are actually created.
        """
        dt = datetime.datetime.now() + datetime.timedelta(0,1)
        job_name = 'test_job'
        startup_config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.ABSOLUTE,
                        DriverSchedulerConfigKey.DATE: dt
                    }
                }
            }
        }

        self.protocol.set_init_params(startup_config)

        # Verify we are initialized properly
        self.assertIsNone(self.protocol._scheduler)
        self.assertEqual(self.protocol._scheduler_config, {})
        self.assertEqual(self.protocol._scheduler_callback, {})

        # Verify the the scheduler is created
        self.protocol.initialize_scheduler()
        self.assertIsInstance(self.protocol._scheduler, DriverScheduler)
        self.assertEqual(self.protocol._scheduler_config, {})
        self.assertEqual(self.protocol._scheduler_callback, {})

        # Now lets see some magic happen.  Lets add our schedulers.  Generally
        # This would be done as part of the protocol init, but it can happen
        # anytime.  If the scheduler has already been initialized the
        # job will be started right away
        self.protocol._add_scheduler(job_name, self._scheduler_callback)
        self.assertEqual(0, self._trigger_count)
        self.assert_scheduled_event_triggered()

    def test_scheduler_event(self):
        """
        Test if we can add and trigger jobs using events instead of callbacks
        We will create two event triggers, foo and bar.  They should come in
        that order.
        """
        self.protocol._protocol_fsm = Mock()
        #self.protocol._fsm.on_event = Mock()

        dt = datetime.datetime.now() + datetime.timedelta(0,1)
        foo_scheduler = 'foo'
        bar_scheduler = 'bar'
        startup_config = {
            DriverConfigKey.SCHEDULER: {
                foo_scheduler: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: 1
                    }
                },
                bar_scheduler: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: 2
                    }
                }
            }
        }

        self.protocol.set_init_params(startup_config)

        # Verify we are initialized properly
        self.assertIsNone(self.protocol._scheduler)
        self.assertEqual(self.protocol._scheduler_config, {})
        self.assertEqual(self.protocol._scheduler_callback, {})

        # Verify the the scheduler is created
        self.protocol.initialize_scheduler()
        self.assertIsInstance(self.protocol._scheduler, DriverScheduler)
        self.assertEqual(self.protocol._scheduler_config, {})
        self.assertEqual(self.protocol._scheduler_callback, {})

        # Now lets see some magic happen.  Lets add our schedulers.  Generally
        # This would be done as part of the protocol init, but it can happen
        # anytime.  If the scheduler has already been initialized the
        # job will be started right away
        foo_event='foo'
        bar_event='bar'
        self.protocol._add_scheduler_event(foo_scheduler, foo_event)
        self.protocol._add_scheduler_event(bar_scheduler, bar_event)

        self.assertEqual(0, self._trigger_count)