from machine import CAN
from micropython import const
import time
import sys

# instance0 transitions through various modes, instance1
# listens for various messages (or not)
#
# Note this test assumes no other CAN nodes are connected apart from the test
# instances (or if they are connected they must be in silent mode.)
#
# TODO: This test needs to eventually support the case where modes aren't supported
# on a controller, maybe by printing fake output if the mode switch fails?

# MODE_NORMAL, MODE_SLEEP, MODE_LOOPBACK, MODE_SILENT, MODE_SILENT_LOOPBACK
can = CAN(1, 500_000, mode=CAN.MODE_NORMAL)

# While instance0 is in Silent mode, instance1 sends a message with this ID
# that will be retried for 100ms (as instance0 won't ACK). So don't print every one.
_SILENT_RX_ID = const(0x53)
silent_rx_count = 0


def irq_print(can):
    global silent_rx_count
    while flags := can.irq().flags():
        if flags & can.IRQ_RX:
            can_id, data, _flags, errors = can.recv()
            if can_id != _SILENT_RX_ID:
                print("recv", hex(can_id), bytes(data))
            else:
                silent_rx_count += 1
        elif flags & can.IRQ_TX:  # note: only enabled on instance1 to avoid race conditions
            print("send", "failed" if flags & can.IRQ_TX_FAILED else "ok")


def reinit_with_mode(mode):
    can.deinit()
    can.init(bitrate=500_000, mode=mode)
    can.irq(irq_print, trigger=can.IRQ_RX, hard=False)
    can.set_filters(None)  # receive all


def instance0():
    multitest.next()
    multitest.wait("instance1 ready")

    reinit_with_mode(can.MODE_NORMAL)
    print("Normal", "MODE_NORMAL" in str(can))
    can.send(0x50, b"Normal")
    time.sleep_ms(100)

    # Skipping MODE_SLEEP as means different things on different hardware

    reinit_with_mode(can.MODE_LOOPBACK)
    print("Loopback", "MODE_LOOPBACK" in str(can))

    # This message should go out to the bus, but will also be received by instance0 itself
    can.send(0x51, b"Loopback")
    time.sleep_ms(100)

    reinit_with_mode(can.MODE_SILENT)
    print("Silent", "MODE_SLIENT" in str(can))

    # This message shouldn't go out onto the bus
    idx = can.send(0x52, b"Silent")
    multitest.broadcast("silent")
    multitest.wait("silent done")
    # we should have received the message from instance1 many times, as instance0 won't have ACKed it
    # create a dummy "OK" for MIMXRT, since it on receives ACKed messages in SILENT mode.
    if ("mimxrt" in sys.platform) or ("alif" in sys.platform):
        print("silent_rx_count True")
    else:
        print("silent_rx_count", silent_rx_count > 5)
    # Cancel the sent message only if it was accpeted for sending.
    if idx is not None:
        can.cancel_send(idx)

    reinit_with_mode(can.MODE_SILENT_LOOPBACK)
    print("Silent Loopback", "MODE_SILENT_LOOPBACK" in str(can))

    # This message should be received by instance0 only
    idx = can.send(0x54, b"SiLoop")
    time.sleep_ms(50)

    reinit_with_mode(can.MODE_NORMAL)
    print("Normal again", "MODE_NORMAL" in str(can))
    can.send(0x55, b"Normal2")  # should be received by instance1 only, again
    multitest.broadcast("normal done")


# Receiver
def instance1():
    can.irq(irq_print, trigger=can.IRQ_RX | can.IRQ_TX, hard=False)
    can.set_filters(None)  # receive all
    multitest.next()

    multitest.broadcast("instance1 ready")

    # The IRQ does most of the work on this instance

    multitest.wait("silent")
    # Sending this message back, it should fail to send as Silent mode won't ACK it
    idx = can.send(0x53, b"Silent2")
    time.sleep_ms(20)
    can.cancel_send(idx)
    # mimxrt does not give TX failed interrupts on cancel_send.
    if "mimxrt" in sys.platform:
        print("send failed")
    multitest.broadcast("silent done")

    multitest.wait("normal done")
