#!/usr/bin/env python3

"""
This example configures CSP on the host machine and establishes a CSP route to
a Polaris receiver via KISS. Polaris specific bindings are then demonstrated.

Refer to client/polaris-ais/python/bindings/polaris_ais_client_python.c and
client/polaris-ais/python/bindings/polaris_adsb_client_python.c for a complete
list of available polaris bindings and the function docstrings for more
information about a specific function.

Prerequisites:

Python bindings shall be enabled when building ``satctl`` and installed on the
system or in a virtual environment. See the ``README`` file for details.

The routing table on the DUT shall be configured to respond to node 5 via KISS,
the routing table below routes traffic to node 5 via KISS, and traffic to all
other nodes via CAN.

    [polaris] prop list sys
    Property        Type  Stat  Flag  Value
    ...
    csp.routes      str   B     -     0/0 CAN, 5/5 KISS
    ...

The store-mirror function call waits indefinitely, or until ^C is pressed, if
no AIS|ADS-B packets are received. AIS|ADS-B packets must therefore be
available on the RF interface for the store-mirror call to succeed.
"""

import time
import datetime
import argparse

import csp


def example(args):
    if args.dut == 'ais':
        from satlab import polaris_ais_client
        polaris_addr = polaris_ais_client.DEFAULT_ADDRESS  # 20
        client = polaris_ais_client.PolarisAIS()
    elif args.dut == 'adsb':
        from satlab import polaris_adsb_client
        polaris_addr = polaris_adsb_client.DEFAULT_ADDRESS  # 23
        client = polaris_adsb_client.PolarisADSB()
    else:
        raise NotImplementedError(f"DUT {args.dut} not supported")

    host_csp_addr = 5

    # store mirror uses port 30, this is outside the default range allowed incoming CSP ports.
    csp.init(host_csp_addr, port_max_bind=30)

    csp.kiss_init(args.kiss_device, args.kiss_rate)
    csp.route_start_task()
    # Route all packets to polaris via KISS
    csp.rtable_set(polaris_addr, 5, 'KISS')

    # Set time on Polaris to own time
    sec = time.time()
    csp.cmp_clock_set(polaris_addr, int(sec), round((sec - int(sec)) * 1e9))
    now = csp.cmp_clock_get(polaris_addr)
    print(f'Time set to {datetime.datetime.fromtimestamp(now[0] + now[1] / 1e9)} UTC')

    if args.dut == 'ais':
        # Sample a chunk of raw data, number of samples are fixed for the raw channel
        raw_data = client.sample(source=polaris_ais_client.SAMPLE_SOURCE_RAW)
        assert len(raw_data) == 2**15
        print(f'Length of raw AIS data: {len(raw_data)}')

        # Sample 5000 samples from AIS channel 88B (AIS2)
        samples = 5000
        channel_2_data = client.sample(source=polaris_ais_client.SAMPLE_SOURCE_AIS2_CH88B, samples=samples)
        assert len(channel_2_data) == samples * 4  # 16 bit I and Q == 4 bytes per sample
        print(f'Length of data sampled from AIS channel 2: {len(channel_2_data)}')

    else:
        # Sample a chunk of raw data, number of samples are fixed for the raw channel
        raw_data = client.sample()
        assert len(raw_data) == 2**15
        print(f'Length of raw ADS-B data: {len(raw_data)}')

    """
    When enabling store-mirror, a callback function capable of handling received
    frames must be specified. The functions 'frame_cb_x' below are simple callback functions
    which prints some of the contents of the received frames.
    It is possible to pass a second, user defined, parameter to a callback function.
    The example below use a dict to store the number of packets received as well as a
    list of timestamps of the received packets.
    """
    def frame_cb_ais(frame, cb_ctx):
        dt = datetime.datetime.fromtimestamp(frame.time / 1e9)
        print(f"time {dt}")
        print(f"type {frame.type}")
        print(f"error {frame.error}")
        print(f"seq {frame.seq}")
        print(f"channel {frame.channel}")
        print(f"scale {frame.scale}")
        print(f"mean {frame.mean}")
        print(f"length {frame.length}")
        print(f"data {frame.data}")

        cb_ctx['packets'] += 1
        cb_ctx['timestamps'].append(frame.time / 1e9)

        # Returning a negative value from the callback will stop the store-mirror
        if cb_ctx['packets'] >= 3:
            return -1

        # Store mirror will continue as long as 0 or None is returned from the callback

    def frame_cb_adsb(frame, cb_ctx):
        dt = datetime.datetime.fromtimestamp(frame.time / 1e9)
        print(f"time {dt}")
        print(f"type {frame.type}")
        print(f"error {frame.error}")
        print(f"seq {frame.seq}")
        print(f"rssi {frame.rssi / 100.0} dBFS")
        print(f"flags {frame.flags}")
        print(f"data {frame.data}")

        cb_ctx['packets'] += 1
        cb_ctx['timestamps'].append(frame.time / 1e9)

        # Returning a negative value from the callback will stop the store-mirror
        if cb_ctx['packets'] >= 3:
            return -1

        # Store mirror will continue as long as 0 or None is returned from the callback
        return 0

    cb_ctx = {'packets': 0,
              'timestamps': []
              }

    # The store_mirror call will run forever (or until the callback returns a negative value),
    # but it can also be stopped by pressing ctrl+c
    print("Starting store-mirror...")
    if args.dut == 'ais':
        client.store_mirror_recv(frame_cb_ais, cbarg=cb_ctx)
    else:
        client.store_mirror_recv(frame_cb_adsb, cbarg=cb_ctx)

    print(f"Packet timestamps: {[datetime.datetime.fromtimestamp(x).isoformat() for x in cb_ctx['timestamps']]}")


def main():
    # Parse arguments
    parser = argparse.ArgumentParser()

    parser.add_argument('--dut', help='Target DUT', type=str, choices=['ais', 'adsb'], required=True)
    parser.add_argument('--kiss-device', help='Path to KISS device on host PC', type=str, default='/dev/ttyUSB1')
    parser.add_argument('--kiss-rate', help='Data rate of the KISS device, must match sys.uart.rate on DUT',
                        type=int, default=3000000)

    args = parser.parse_args()

    example(args)


if __name__ == '__main__':
    main()
