"""
@file wrtd-config.py
@copyright: Copyright (c) 2019 CERN (home.cern)

SPDX-License-Identifier: LGPL-3.0-or-later
"""

import time
import argparse
import re

from PyWrtd import *

def __tstamp_normalise(tstamp):
    ret = tstamp
    while (ret['ns'] >= 1000000000):
        ret['ns']      -= 1000000000
        ret['seconds'] += 1
    while (ret['ns'] < 0):
        ret['ns']      += 1000000000
        ret['seconds'] -= 1
    return ret

def __tstamp_to_ns(tstamp):
    return tstamp['seconds'] * 1e9 + tstamp['ns'] + tstamp['frac'] * 2**-32

def __tstamp_to_str(tstamp):
    if list(tstamp.values()) == [0, 0, 0]:
        return 'Never'
    else:
        ret = __tstamp_normalise(tstamp)
        return '{0}.{1:03d}.{2:03d}.{3:03d}+{4:03d}'.format(
            time.strftime('%F,%T', time.gmtime(ret['seconds'])),
            int(ret['ns'] / 1e6),
            int(ret['ns'] / 1e3) % 1000,
            int(ret['ns'] / 1e1) % 1000,
            ret['frac'] >> (32 - 9))

def cmd_sys_info(wrtd, args):
    print('')
    cmd_sys_time(wrtd, args)
    print('')
    count = wrtd.get_attr_int32(wrtd.WRTD_GLOBAL_REP_CAP_ID,
                                wrtd.WRTD_ATTR_FW_COUNT)
    print ('{0} Application{1} detected{2}'.format(count,
                                                   '' if (count == 1) else 's',
                                                   '.' if (count == 0) else ':'))
    for i in range(count):
        if args.verbose:
            print ('')
        fw_name = wrtd.get_fw_name(i)
        print ('# {0}'.format(fw_name))
        if args.verbose:
            print("  + Version Info")
            print("    - Name..............: {0}".format(fw_name))
            print("    - FW version........: {0}.{1}".format(
                wrtd.get_attr_int32(fw_name, wrtd.WRTD_ATTR_FW_MAJOR_VERSION),
                wrtd.get_attr_int32(fw_name, wrtd.WRTD_ATTR_FW_MINOR_VERSION)))
            print("    - WRTD version......: {0}.{1}".format(
                wrtd.get_attr_int32(fw_name, wrtd.WRTD_ATTR_FW_MAJOR_VERSION_REQUIRED),
                wrtd.get_attr_int32(fw_name, wrtd.WRTD_ATTR_FW_MINOR_VERSION_REQUIRED)))
            print("  + Capabilities")
            print("    - Local RX Channels.: {0}".format(
                wrtd.get_attr_int32(fw_name, wrtd.WRTD_ATTR_FW_LOCAL_INPUTS)))
            print("    - Local TX Channels.: {0}".format(
                wrtd.get_attr_int32(fw_name, wrtd.WRTD_ATTR_FW_LOCAL_OUTPUTS)))
            caps = wrtd.get_attr_int32(fw_name, wrtd.WRTD_ATTR_FW_CAPABILITIES)
            print("    - Network RX........: {0}".format(caps & 0x1 != 0))
            print("    - Network TX........: {0}".format(caps & 0x2 != 0))

    print('')
    cmd_list_alarms(wrtd, args)
    print('')
    cmd_list_rules(wrtd, args)
    print('')

def cmd_sys_time(wrtd, args):
    print('Current time = {0}'.format(__tstamp_to_str(
        wrtd.get_attr_tstamp(wrtd.WRTD_GLOBAL_REP_CAP_ID,
                             wrtd.WRTD_ATTR_SYS_TIME))))

def cmd_set_log(wrtd, args):
    wrtd.set_attr_bool(wrtd.WRTD_GLOBAL_REP_CAP_ID,
                       wrtd.WRTD_ATTR_EVENT_LOG_ENABLED,
                       args.log == "on")

def cmd_clear_log(wrtd, args):
    wrtd.clear_event_log_entries()

def cmd_list_rules(wrtd, args):
    count = wrtd.get_attr_int32(wrtd.WRTD_GLOBAL_REP_CAP_ID,
                                wrtd.WRTD_ATTR_RULE_COUNT)
    print ('{0} Rule{1} defined{2}'.format(count,
                                           '' if (count == 1) else 's',
                                           '.' if (count == 0) else ':'))
    for i in range(count):
        args.name = wrtd.get_rule_name(i)
        if args.verbose:
            print ('')
        print ('# {0}'.format(args.name))
        if args.verbose:
            cmd_rule_info(wrtd, args)

def cmd_add_rule(wrtd, args):
    wrtd.add_rule(args.name)

def cmd_set_rule(wrtd, args):
    time = {'seconds': 0, 'ns': args.delay, 'frac': 0}

    delay = __tstamp_normalise(time)
    wrtd.set_attr_tstamp(args.name, wrtd.WRTD_ATTR_RULE_DELAY,
                         seconds = delay['seconds'],
                         ns      = delay['ns'],
                         frac    = delay['frac'])

    wrtd.set_attr_string(args.name, wrtd.WRTD_ATTR_RULE_SOURCE, args.source)
    wrtd.set_attr_string(args.name, wrtd.WRTD_ATTR_RULE_DESTINATION, args.destination)

def cmd_remove_rule(wrtd, args):
    wrtd.remove_rule(args.name)

def cmd_remove_all_rules(wrtd, args):
    wrtd.remove_all_rules()

def cmd_enable_rule(wrtd, args):
    wrtd.set_attr_bool(args.name, wrtd.WRTD_ATTR_RULE_ENABLED, True)

def cmd_disable_rule(wrtd, args):
    wrtd.set_attr_bool(args.name, wrtd.WRTD_ATTR_RULE_ENABLED, False)

def cmd_disable_all_rules(wrtd, args):
    wrtd.disable_all_rules()

def cmd_rule_info(wrtd, args):
    print("  + Configuration")
    print("    - Name..............: {0}".format(args.name))
    print("    - Source............: {0}".format(
        wrtd.get_attr_string(args.name, wrtd.WRTD_ATTR_RULE_SOURCE)))
    print("    - Destination.......: {0}".format(
        wrtd.get_attr_string(args.name, wrtd.WRTD_ATTR_RULE_DESTINATION)))
    print("    - Enabled...........: {0}".format(
        wrtd.get_attr_bool(args.name, wrtd.WRTD_ATTR_RULE_ENABLED)))
    print("    - Send Late.........: {0}".format(
        wrtd.get_attr_bool(args.name, wrtd.WRTD_ATTR_RULE_SEND_LATE)))
    print("    - Repeat Count......: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_RULE_REPEAT_COUNT)))
    print("    - Delay.............: {0:.3f}ns".format(__tstamp_to_ns(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_RULE_DELAY))))
    print("    - Holdoff...........: {0:.3f}ns".format(__tstamp_to_ns(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_RULE_HOLDOFF))))
    print("    - Resync Period.....: {0:.3f}ns".format(__tstamp_to_ns(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_RULE_RESYNC_PERIOD))))
    print("    - Resync Factor.....: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_RULE_RESYNC_FACTOR)))
    print("  + Statistics")
    print("    - RX Events.........: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_STAT_RULE_RX_EVENTS)))
    print("    - Last RX...........: {0}".format(__tstamp_to_str(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_STAT_RULE_RX_LAST))))
    print("    - TX Events.........: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_STAT_RULE_TX_EVENTS)))
    print("    - Last TX...........: {0}".format(__tstamp_to_str(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_STAT_RULE_TX_LAST))))
    print("    - Latency (min)....: {0:.3f}ns".format(__tstamp_to_ns(
        wrtd.get_attr_tstamp(args.name,wrtd.WRTD_ATTR_STAT_RULE_RX_LATENCY_MIN))))
    print("    - Latency (avg)....: {0:.3f}ns".format(__tstamp_to_ns(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_STAT_RULE_RX_LATENCY_AVG))))
    print("    - Latency (max)....: {0:.3f}ns".format(__tstamp_to_ns(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_STAT_RULE_RX_LATENCY_MAX))))
    print("    - Missed (late).....: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_STAT_RULE_MISSED_LATE)))
    print("    - Missed (holdoff)..: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_STAT_RULE_MISSED_HOLDOFF)))
    print("    - Missed (no sync)..: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_STAT_RULE_MISSED_NOSYNC)))
    print("    - Missed (overflow).: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_STAT_RULE_MISSED_OVERFLOW)))
    print("    - Last Missed.......: {0}".format(__tstamp_to_str(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_STAT_RULE_MISSED_LAST))))

def cmd_reset_rule_stats(wrtd, args):
    wrtd.reset_rule_stats(args.name)

def cmd_list_alarms(wrtd, args):
    count = wrtd.get_attr_int32(wrtd.WRTD_GLOBAL_REP_CAP_ID,
                                wrtd.WRTD_ATTR_ALARM_COUNT)
    print ('{0} Alarm{1} defined{2}'.format(count,
                                            '' if (count == 1) else 's',
                                            '.' if (count == 0) else ':'))
    for i in range(count):
        args.name = wrtd.get_alarm_name(i)
        if args.verbose:
            print ('')
        print ('# {0}'.format(args.name))
        if args.verbose:
            cmd_alarm_info(wrtd, args)

def cmd_add_alarm(wrtd, args):
    wrtd.add_alarm(args.name)

def cmd_set_alarm(wrtd, args):
    time = wrtd.get_attr_tstamp(wrtd.WRTD_GLOBAL_REP_CAP_ID, wrtd.WRTD_ATTR_SYS_TIME)

    time['ns'] += args.delay
    delay = __tstamp_normalise(time)
    wrtd.set_attr_tstamp(args.name, wrtd.WRTD_ATTR_ALARM_TIME,
                         seconds = delay['seconds'],
                         ns      = delay['ns'],
                         frac    = delay['frac'])

    time['ns'] -= args.setup
    setup = __tstamp_normalise(time)
    wrtd.set_attr_tstamp(args.name, wrtd.WRTD_ATTR_ALARM_SETUP_TIME,
                         seconds = setup['seconds'],
                         ns      = setup['ns'],
                         frac    = setup['frac'])

    time['seconds'] = 0
    time['ns']      = args.period
    time['frac']    = 0

    period = __tstamp_normalise(time)
    wrtd.set_attr_tstamp(args.name, wrtd.WRTD_ATTR_ALARM_PERIOD,
                         seconds = setup['seconds'],
                         ns      = setup['ns'],
                         frac    = setup['frac'])

    wrtd.set_attr_int32(args.name, wrtd.WRTD_ATTR_ALARM_REPEAT_COUNT, args.count)

    if (args.enable):
        cmd_enable_alarm(wrtd, args)

def cmd_remove_alarm(wrtd, args):
    wrtd.remove_alarm(args.name)

def cmd_remove_all_alarms(wrtd, args):
    wrtd.remove_all_alarms()

def cmd_enable_alarm(wrtd, args):
    wrtd.set_attr_bool(args.name, wrtd.WRTD_ATTR_ALARM_ENABLED, True)

def cmd_disable_alarm(wrtd, args):
    wrtd.set_attr_bool(args.name, wrtd.WRTD_ATTR_ALARM_ENABLED, False)

def cmd_disable_all_alarms(wrtd, args):
    wrtd.disable_all_alarms()

def cmd_alarm_info(wrtd, args):
    print("  + Configuration")
    print("    - Name..............: {0}".format(args.name))
    print("    - Enabled...........: {0}".format(
        wrtd.get_attr_bool(args.name, wrtd.WRTD_ATTR_ALARM_ENABLED)))
    print("    - Trigger Time......: {0}".format(__tstamp_to_str(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_ALARM_TIME))))
    print("    - Setup Time........: {0}".format(__tstamp_to_str(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_ALARM_SETUP_TIME))))
    print("    - Period............: {0:.3f}ns".format(__tstamp_to_ns(
        wrtd.get_attr_tstamp(args.name, wrtd.WRTD_ATTR_ALARM_PERIOD))))
    print("    - Repeat Count......: {0}".format(
        wrtd.get_attr_int32(args.name, wrtd.WRTD_ATTR_ALARM_REPEAT_COUNT)))

def time_interval(string):
    m = re.match('\A([0-9]+)([nums]?)\Z', string)
    if m == None:
        msg = "%r is not a valid time interval specification" % string
        raise argparse.ArgumentTypeError(msg)
    mult = {
        '' : 1e0,
        'n': 1e0,
        'u': 1e3,
        'm': 1e6,
        's': 1e9,
    }
    return int(m.group(1)) * int(mult[m.group(2)])

def time_interval_help():
    return """Default unit is ns, but an 'n','u','m' or 's' can be appended \
    to the value to set it explicitly to nano, micro, milli or full seconds"""

def main():

    parser = argparse.ArgumentParser(description='WRTD node configuration tool')
    parser.add_argument('-D', '--dev-id', dest='dev', required=True, type=int,
                        help='MockTurtle device ID (integer) to open')
    subparsers = parser.add_subparsers(title='Available commands',
                                       dest='command', metavar='<command>',
                                       help='(Use "<command> -h" to get more details)')
    subparsers.required = True;

    # sys-info
    cmd_parser = subparsers.add_parser('sys-info', help='Show system information')
    cmd_parser.add_argument('-v', '--verbose', action='store_true',
                            help='Show more details')
    cmd_parser.set_defaults(func=cmd_sys_info)

    # sys-time
    cmd_parser = subparsers.add_parser('sys-time', help='Show current system time')
    cmd_parser.set_defaults(func=cmd_sys_time)

    # set-log
    cmd_parser = subparsers.add_parser('set-log', help='Enable/Disable logging')
    cmd_parser.add_argument('log', choices=['on', 'off'], help='Enable/Disable logging')
    cmd_parser.set_defaults(func=cmd_set_log)

    # clear-log
    cmd_parser = subparsers.add_parser('clear-log', help='Clear pending log entries')
    cmd_parser.set_defaults(func=cmd_clear_log)

    # list-rules
    cmd_parser = subparsers.add_parser('list-rules', help='List all defined Rules')
    cmd_parser.add_argument('-v', '--verbose', action='store_true',
                            help='Show details about each Rule')
    cmd_parser.set_defaults(func=cmd_list_rules)

    # add-rule
    cmd_parser = subparsers.add_parser('add-rule', help='Define a new Rule')
    cmd_parser.add_argument('name', help='The name of the new Rule')
    cmd_parser.set_defaults(func=cmd_add_rule)

    # set-rule
    cmd_parser = subparsers.add_parser('set-rule', help='Configure a Rule')
    cmd_parser.add_argument('name', help='The name of the Rule to configure')
    cmd_parser.add_argument('-d', '--delay', type=time_interval, default = 0,
                            help='Set the delay for this Rule. ' + time_interval_help())
    cmd_parser.add_argument('source', help='The source Event ID for this Rule.')
    cmd_parser.add_argument('destination', help='The destination Event ID for this Rule.')
    cmd_parser.set_defaults(func=cmd_set_rule)

    # remove-rule
    cmd_parser = subparsers.add_parser('remove-rule', help='Delete a Rule')
    cmd_parser.add_argument('name', help='The name of the Rule to delete')
    cmd_parser.set_defaults(func=cmd_remove_rule)

    # remove-all-rules
    cmd_parser = subparsers.add_parser('remove-all-rules', help='Delete all Rules')
    cmd_parser.set_defaults(func=cmd_remove_all_rules)

    # enable-rule
    cmd_parser = subparsers.add_parser('enable-rule', help='Enable a Rule')
    cmd_parser.add_argument('name', help='The name of the Rule to enable')
    cmd_parser.set_defaults(func=cmd_enable_rule)

    # disable-rule
    cmd_parser = subparsers.add_parser('disable-rule', help='Disable a Rule')
    cmd_parser.add_argument('name', help='The name of the Rule to disable')
    cmd_parser.set_defaults(func=cmd_disable_rule)

    # disable-all-rules
    cmd_parser = subparsers.add_parser('disable-all-rules', help='Disable all Rules')
    cmd_parser.set_defaults(func=cmd_remove_all_rules)

    # rule-info
    cmd_parser = subparsers.add_parser('rule-info', help='Display information about a Rule')
    cmd_parser.add_argument('name', help='The name of the Rule to display its information')
    cmd_parser.set_defaults(func=cmd_rule_info)

    # reset-rule-stats
    cmd_parser = subparsers.add_parser('reset-rule-stats', help='Reset all statistics of a Rule')
    cmd_parser.add_argument('name', help='The name of the Rule to reset its statistics')
    cmd_parser.set_defaults(func=cmd_reset_rule_stats)

    # list-alarms
    cmd_parser = subparsers.add_parser('list-alarms', help='List all defined Alarms')
    cmd_parser.add_argument('-v', '--verbose', action='store_true',
                            help='Show details about each Alarm')
    cmd_parser.set_defaults(func=cmd_list_alarms)


    # add-alarm
    cmd_parser = subparsers.add_parser('add-alarm', help='Define a new Alarm')
    cmd_parser.add_argument('name', help='The name of the new Alarm')
    cmd_parser.set_defaults(func=cmd_add_alarm)

    # set-alarm
    cmd_parser = subparsers.add_parser('set-alarm', help='Configure an Alarm')
    cmd_parser.add_argument('name', help='The name of the Alarm to configure')
    cmd_parser.add_argument('-d', '--delay', type=time_interval, required = True,
                            help='Set the delay for this Alarm wrt now. ' + time_interval_help())
    cmd_parser.add_argument('-s', '--setup', type=time_interval, default = 0,
                            help='Set the setup time for this Alarm. ' + time_interval_help())
    cmd_parser.add_argument('-p', '--period', type=time_interval, default = 0,
                            help='Set the period for this Alarm. ' + time_interval_help())
    cmd_parser.add_argument('-c', '--count', type=int, default = 0,
                            help='Set the repeat count for this Alarm')
    cmd_parser.add_argument('-e', '--enable', action='store_true',
                            help='Also enable the Alarm after configuring it.')
    cmd_parser.set_defaults(func=cmd_set_alarm)

    # remove-alarm
    cmd_parser = subparsers.add_parser('remove-alarm', help='Delete an Alarm')
    cmd_parser.add_argument('name', help='The name of the Alarm to delete')
    cmd_parser.set_defaults(func=cmd_remove_alarm)

    # remove-all-alarms
    cmd_parser = subparsers.add_parser('remove-all-alarms', help='Delete all Alarms')
    cmd_parser.set_defaults(func=cmd_remove_all_alarms)

    # enable-alarm
    cmd_parser = subparsers.add_parser('enable-alarm', help='Enable an Alarm')
    cmd_parser.add_argument('name', help='The name of the Alarm to enable')
    cmd_parser.set_defaults(func=cmd_enable_alarm)

    # disable-alarm
    cmd_parser = subparsers.add_parser('disable-alarm', help='Disable an Alarm')
    cmd_parser.add_argument('name', help='The name of the Alarm to disable')
    cmd_parser.set_defaults(func=cmd_disable_alarm)

    # disable-all-alarms
    cmd_parser = subparsers.add_parser('disable-all-alarms', help='Disable all Alarms')
    cmd_parser.set_defaults(func=cmd_remove_all_alarms)

    # alarm-info
    cmd_parser = subparsers.add_parser('alarm-info', help='Display information about an Alarm')
    cmd_parser.add_argument('name', help='The name of the Alarm to display its information')
    cmd_parser.set_defaults(func=cmd_alarm_info)

    args = parser.parse_args()

    dev = 'MT' + str(args.dev)

    wrtd = PyWrtd(dev)

    args.func(wrtd, args)

if __name__ == "__main__":
    main()
