# SPDX-FileCopyrightText: 2022 CERN (home.cern)
# SPDX-License-Identifier: TODO

import pytest

from WrtdTestHelper import *

@pytest.fixture()
def fmc_adc():
    fmc_adc_list = [FmcAdc100m(pytest.fmc_adc1), FmcAdc100m(pytest.fmc_adc2)]
    yield fmc_adc_list
    for dev in fmc_adc_list:
        dev.acq_stop(0)
        dev.disable_triggers()

@pytest.fixture()
def adc():
    return WrtdTestNode(pytest.adc_node, 'wrtd-adc-x2')

@pytest.fixture()
def tdc():
    return WrtdTestNode(pytest.tdc_node, 'wrtd-tdc-x2')

@pytest.fixture()
def fdelay():
    return WrtdTestNode(pytest.fd_node, 'wrtd-fd-x2')

class TestWrtdRefDesigns(object):
    """
    Collection of tests for the WRTD reference designs
    """

    def test_node_count(self):
        node_count = PyWrtd.get_node_count()
        assert node_count == 3

    @pytest.mark.parametrize('node', ['adc', 'tdc', 'fdelay'])
    def test_fw_name(self, node, request, adc, tdc, fdelay):
        wrtd = request.getfixturevalue(node)
        assert wrtd.get_fw_name(0) == wrtd.expected_fw_name

    @pytest.mark.parametrize('tx_node, rx_nodes',
                             [('adc', ['fdelay']),
                              ('tdc', ['adc', 'fdelay'])],
                             ids=['adc->fdelay', 'tdc->adc/fdelay'])
    def test_alarm_log_and_net(self, tx_node, rx_nodes, request, adc, tdc, fdelay):
        """
        Schedule an alarm in tx_node, send an event and check the logs to
        see that the alarm triggered, the event was sent out and that it was
        received by all rx_nodes
        """
        transmitter = request.getfixturevalue(tx_node)

        transmitter.add_alarm('alarm1')

        transmitter.add_rule('rule1')

        transmitter.set_attr_tstamp('rule1', transmitter.WRTD_ATTR_RULE_DELAY, ns = 500000)
        transmitter.set_attr_string('rule1', transmitter.WRTD_ATTR_RULE_SOURCE, 'alarm1')
        transmitter.set_attr_string('rule1', transmitter.WRTD_ATTR_RULE_DESTINATION, 'net0')

        transmitter.set_attr_bool('rule1', transmitter.WRTD_ATTR_RULE_ENABLED, True)

        ts_alarm = transmitter.get_attr_tstamp(transmitter.WRTD_GLOBAL_REP_CAP_ID,
                                               transmitter.WRTD_ATTR_SYS_TIME)
        ts_alarm['ns'] += 10000000
        transmitter.set_attr_tstamp('alarm1', transmitter.WRTD_ATTR_ALARM_TIME,
                                    seconds = ts_alarm['seconds'],
                                    ns      = ts_alarm['ns'],
                                    frac    = ts_alarm['frac'])

        transmitter.set_attr_bool('alarm1', transmitter.WRTD_ATTR_ALARM_ENABLED, True)

        transmitter.check_log_entry({'id': 'alarm1', 'log_type': 'GENERATED'})

        transmitter.check_log_entry({'id': 'net0', 'log_type': 'NETWORK', 'log_reason': 'TX'})

        for rx_node in rx_nodes:
            receiver = request.getfixturevalue(rx_node)
            receiver.check_log_entry({'id': 'net0', 'log_type': 'NETWORK', 'log_reason': 'RX'})

    @pytest.mark.parametrize('channel', [1, 2, 3, 4], ids=lambda i: f'ch{i}')
    @pytest.mark.parametrize('fmc', [0, 1], ids=lambda i: f'fmc{i+1}')
    def test_fd_out_tdc_in(self, tdc, fdelay, fmc, channel):
        """
        Schedule an alarm on fdelay, generate pulse on output, check that
        it goes out and that it is received by the tdc. FD out 4 is wired
        to both TDC in 4 and 5.
        """

        fdelay.add_alarm('alarm1')

        fd_channel = f'LC-O{4*fmc+channel}'

        fdelay.add_rule_enabled('rule1', 'alarm1', fd_channel, 500000)

        ts_alarm = fdelay.get_attr_tstamp(fdelay.WRTD_GLOBAL_REP_CAP_ID,
                                          fdelay.WRTD_ATTR_SYS_TIME)
        ts_alarm['ns'] += 10000000
        fdelay.set_attr_tstamp('alarm1', fdelay.WRTD_ATTR_ALARM_TIME,
                               seconds = ts_alarm['seconds'],
                               ns      = ts_alarm['ns'],
                               frac    = ts_alarm['frac'])

        fdelay.set_attr_bool('alarm1', fdelay.WRTD_ATTR_ALARM_ENABLED, True)

        fdelay.check_log_entry({'id': 'alarm1', 'log_type': 'GENERATED'})
        fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED', 'log_reason': 'START'})
        fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED', 'log_reason': 'DONE'})

        tdc_channel = f'LC-I{5*fmc+channel}'
        tdc.check_log_entry({'id': tdc_channel, 'log_type': 'GENERATED'})
        if channel == 4:
            tdc_channel = f'LC-I{5*(fmc+1)}'
            tdc.check_log_entry({'id': tdc_channel, 'log_type': 'GENERATED'})

    @pytest.mark.parametrize('fmc', [0, 1], ids=lambda i: f'fmc{i+1}')
    def test_fd_tdc_snake_adc_trigin(self, tdc, fdelay, adc, fmc, fmc_adc):
        """
        Schedule an alarm on fdelay, generate pulse on FD output 1, receive
        on TDC input 1, generate event, add rule to generate pulse on FD
        output 2, etc. TDC input 4 should cause one last pulse on FD output 1
        and TDC input 5 should trigger the ADC over the network.
        """

        for channel in [1, 2, 3, 4, 5]:
            tdc_channel = f'LC-I{5*fmc+channel}'
            rule_name = f'rule.{tdc_channel}'
            tdc.add_rule_enabled(rule_name, tdc_channel, f'net{tdc_channel}', 500000, 1)
            if channel < 5:
                if channel == 4:
                    fd_channel = f'LC-O{4*fmc+1}'
                else:
                    fd_channel = f'LC-O{4*fmc+channel+1}'
                rule_name = f'rule.{fd_channel}'
                fdelay.add_rule_enabled(rule_name, f'net{tdc_channel}', fd_channel)
            else:
                adc_channel = f'LC-O{fmc+1}'
                rule_name = f'rule.{adc_channel}'
                adc.add_rule_enabled(rule_name, f'net{tdc_channel}', adc_channel)

        fmc_adc[fmc].start_acquisition(pre=1, post=2)

        fdelay.add_alarm('alarm1')

        fd_channel = f'LC-O{4*fmc+1}'

        fdelay.add_rule_enabled('rule-alarm', 'alarm1', fd_channel, 500000)

        ts_alarm = fdelay.get_attr_tstamp(fdelay.WRTD_GLOBAL_REP_CAP_ID,
                                          fdelay.WRTD_ATTR_SYS_TIME)
        ts_alarm['ns'] += 10000000
        fdelay.set_attr_tstamp('alarm1', fdelay.WRTD_ATTR_ALARM_TIME,
                               seconds = ts_alarm['seconds'],
                               ns      = ts_alarm['ns'],
                               frac    = ts_alarm['frac'])

        fdelay.set_attr_bool('alarm1', fdelay.WRTD_ATTR_ALARM_ENABLED, True)

        fdelay.check_log_entry({'id': 'alarm1', 'log_type': 'GENERATED'})

        fd_channel = f'LC-O{4*fmc+1}'
        fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED', 'log_reason': 'START'})
        fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED', 'log_reason': 'DONE'})

        for channel in [1, 2, 3, 4]:
            tdc_channel = f'LC-I{5*fmc+channel}'
            tdc.check_log_entry({'id': tdc_channel, 'log_type': 'GENERATED'})

            event_name = f'net{tdc_channel}'
            tdc.check_log_entry({'id': event_name, 'log_type': 'NETWORK', 'log_reason': 'TX'})
            fdelay.check_log_entry({'id': event_name, 'log_type': 'NETWORK', 'log_reason': 'RX'})
            adc.check_log_entry({'id': event_name, 'log_type': 'NETWORK', 'log_reason': 'RX'})

            if channel == 4:
                fd_channel = f'LC-O{4*fmc+1}'
            else:
                fd_channel = f'LC-O{4*fmc+channel+1}'

            fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED',
                                    'log_reason': 'START'})

            # two events will arrive from TDC in 4 and 5 almost simultaneously
            if channel == 4:
                event_name  =f'netLC-I{5*(fmc+1)}'
                fdelay.check_log_entry({'id': event_name, 'log_type': 'NETWORK',
                                        'log_reason': 'RX'})
                adc.check_log_entry({'id': event_name, 'log_type': 'NETWORK', 'log_reason': 'RX'})

            fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED', 'log_reason': 'DONE'})

        adc_channel = f'LC-O{fmc+1}'
        adc.check_log_entry({'id': adc_channel, 'log_type': 'CONSUMED', 'log_reason': 'START'})
        adc.check_log_entry({'id': adc_channel, 'log_type': 'CONSUMED', 'log_reason': 'DONE'})

        assert fmc_adc[fmc].acquisition_complete(), "acquisition timeout"

    @pytest.mark.parametrize('fmc', [0, 1], ids=lambda i: f'fmc{i+1}')
    def test_fd_adc_snake(self, fdelay, adc, fmc, fmc_adc):
        """
        Schedule an alarm on fdelay, generate pulse on FD output 1, receive
        on ADC channel 1, trigger and generate event, add rule to generate pulse
        on FD output 2, etc.
        """

        for channel in [1, 2, 3, 4]:
            adc_channel = f'LC-I{5*fmc+channel}'
            rule_name = f'rule.{adc_channel}'
            adc.add_rule_enabled(rule_name, adc_channel, f'net{adc_channel}', 500000, 1)
            if channel < 4:
                fd_channel = f'LC-O{4*fmc+channel+1}'
                rule_name = f'rule.{fd_channel}'
                fdelay.add_rule_enabled(rule_name, f'net{adc_channel}', fd_channel)
            fmc_adc[fmc].enable_int_trigger(channel-1)

        fmc_adc[fmc].start_acquisition(pre=1, post=2, nshot=4)

        fdelay.add_alarm('alarm1')

        fd_channel = f'LC-O{4*fmc+1}'

        fdelay.add_rule_enabled('rule-alarm', 'alarm1', fd_channel, 500000)

        ts_alarm = fdelay.get_attr_tstamp(fdelay.WRTD_GLOBAL_REP_CAP_ID,
                                          fdelay.WRTD_ATTR_SYS_TIME)
        ts_alarm['ns'] += 10000000
        fdelay.set_attr_tstamp('alarm1', fdelay.WRTD_ATTR_ALARM_TIME,
                               seconds = ts_alarm['seconds'],
                               ns      = ts_alarm['ns'],
                               frac    = ts_alarm['frac'])

        fdelay.set_attr_bool('alarm1', fdelay.WRTD_ATTR_ALARM_ENABLED, True)

        fdelay.check_log_entry({'id': 'alarm1', 'log_type': 'GENERATED'})

        fd_channel = f'LC-O{4*fmc+1}'
        fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED', 'log_reason': 'START'})
        fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED', 'log_reason': 'DONE'})

        for channel in [1, 2, 3, 4]:
            adc_channel = f'LC-I{5*fmc+channel}'
            adc.check_log_entry({'id': adc_channel, 'log_type': 'GENERATED'})

            event_name = f'net{adc_channel}'
            adc.check_log_entry({'id': event_name, 'log_type': 'NETWORK', 'log_reason': 'TX'})
            fdelay.check_log_entry({'id': event_name, 'log_type': 'NETWORK', 'log_reason': 'RX'})
            if channel < 4:
                fd_channel = f'LC-O{4*fmc+channel+1}'
                fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED',
                                        'log_reason': 'START'})
                fdelay.check_log_entry({'id': fd_channel, 'log_type': 'CONSUMED',
                                        'log_reason': 'DONE'})

        assert fmc_adc[fmc].acquisition_complete(), "acquisition timeout"
