#ifndef MICROBIT_RADIO_H
#define MICROBIT_RADIO_H

#include "mbed.h"
#include "PacketBuffer.h"

/**
 * Provides a simple broadcast radio abstraction, built upon the raw nrf51822 RADIO module.
 *
 * The nrf51822 RADIO module supports a number of proprietary modes of operation in addition to the typical BLE usage.
 * This class uses one of these modes to enable simple, point to multipoint communication directly between micro:bits.
 *
 * TODO: The protocols implemented here do not currently perform any significant form of energy management,
 * which means that they will consume far more energy than their BLE equivalent. Later versions of the protocol
 * should look to address this through energy efficient broadcast techniques / sleep scheduling. In particular, the GLOSSY
 * approach to efficienct rebroadcast and network synchronisation would likely provide an effective future step.
 *
 * TODO: Meshing should also be considered - again a GLOSSY approach may be effective here, and highly complementary to
 * the master/slave arachitecture of BLE.
 *
 * TODO: This implementation only operates whilst the BLE stack is disabled. The nrf51822 provides a timeslot API to allow
 * BLE to cohabit with other protocols. Future work to allow this colocation would be benefical, and would also allow for the
 * creation of wireless BLE bridges.
 *
 * NOTE: This API does not contain any form of encryption, authentication or authorization. It's purpose is solely for use as a
 * teaching aid to demonstrate how simple communications operates, and to provide a sandpit through which learning can take place.
 * For serious applications, BLE should be considered a substantially more secure alternative.
 */

// Status Flags
#define MICROBIT_RADIO_STATUS_INITIALISED       0x0001

// Default configuration values
#define MICROBIT_RADIO_BASE_ADDRESS             0x75626974
#define MICROBIT_RADIO_DEFAULT_GROUP            0
#define MICROBIT_RADIO_DEFAULT_TX_POWER         6
#define MICROBIT_RADIO_DEFAULT_FREQUENCY        7
#define MICROBIT_RADIO_MAX_PACKET_SIZE          32
#define MICROBIT_RADIO_HEADER_SIZE              4
#define MICROBIT_RADIO_MAXIMUM_RX_BUFFERS       4

// Known Protocol Numbers
#define MICROBIT_RADIO_PROTOCOL_DATAGRAM        1       // A simple, single frame datagram. a little like UDP but with smaller packets. :-)
#define MICROBIT_RADIO_PROTOCOL_EVENTBUS        2       // Transparent propogation of events from one micro:bit to another.

// Events
#define MICROBIT_RADIO_EVT_DATAGRAM             1       // Event to signal that a new datagram has been received.

struct FrameBuffer
{
    uint8_t         length;                             // The length of the remaining bytes in the packet. includes protocol/version/group fields, excluding the length field itself.
    uint8_t         version;                            // Protocol version code.
    uint8_t         group;                              // ID of the group to which this packet belongs.
    uint8_t         protocol;                           // Inner protocol number c.f. those issued by IANA for IP protocols

    uint8_t         payload[MICROBIT_RADIO_MAX_PACKET_SIZE];    // User / higher layer protocol data
    FrameBuffer     *next;                              // Linkage, to allow this and other protocols to queue packets pending processing.
    uint8_t         rssi;                               // Received signal strength of this frame.
};

#include "MicroBitRadioDatagram.h"
#include "MicroBitRadioEvent.h"

class MicroBitRadio : MicroBitComponent
{
    uint8_t                 group;      // The radio group to which this micro:bit belongs.
    uint8_t                 queueDepth; // The number of packets in the receiver queue.
    uint8_t                 rssi;
    FrameBuffer             *rxQueue;   // A linear list of incoming packets, queued awaiting processing.
    FrameBuffer             *rxBuf;     // A pointer to the buffer being actively used by the RADIO hardware.

    public:
    MicroBitRadioDatagram   datagram;   // A simple datagram service.
    MicroBitRadioEvent      event;      // A simple event handling service.
    static MicroBitRadio *instance;     // A singleton reference, used purely by the interrupt service routine.

    /**
     * Constructor.
     *
     * Initialise the MicroBitRadio. Note that this class is demand activated, so most resources are only
     * committed if send/recv or event registrations calls are made.
     */
    MicroBitRadio(uint16_t id);

    /**
     * Change the output power level of the transmitter to the given value.
     *
     * @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest.
     * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range.
     *
     */
    int setTransmitPower(int power);

    /**
     * Change the transmission and reception band of the radio to the given channel
     *
     * @param band a frequency band in the range 0 - 100. Each step is 1MHz wide, based at 2400MHz.
     * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range,
     * or MICROBIT_NOT_SUPPORTED if the BLE stack is running.
     *
     */
    int setFrequencyBand(int band);

    /**
     * Retrieve a pointer to the currently allocated recieve buffer. This is the area of memory
     * actively being used by the radio hardware to store incoming data.
     *
     * @return a pointer to the current receive buffer
     */
    FrameBuffer * getRxBuf();

    /**
     * Attempt to queue a buffer received by the radio hardware, if sufficient space is available.
     *
     * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if a replacement receiver buffer
     * could not be allocated (either by policy or memory exhaustion).
     */
    int queueRxBuf();

    /**
     * Sets the RSSI for the most recent packet.
     *
     * @param rssi the new rssi value
     */
    int setRSSI(uint8_t rssi);

    /**
     * Retrieves the current RSSI for the most recent packet.
     */
    int getRSSI();

    /**
     * Initialises the radio for use as a multipoint sender/receiver
     * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if SoftDevice is enabled.
     */
    int enable();

    /**
     * Disables the radio for use as a multipoint sender/receiver.
     * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if SoftDevice is enabled.
     */
    int disable();

    /**
     * Sets the radio to listen to packets sent with the given group id.
     *
     * @param group The group to join. A micro:bit can only listen to one group ID at any time.
     * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the BLE stack is running.
     */
    int setGroup(uint8_t group);

    /**
     * A background, low priority callback that is triggered whenever the processor is idle.
     * Here, we empty our queue of received packets, and pass them onto higher level protocol handlers.
     *
     * We provide optimised handling of well known, simple protocols and events on the MicroBitMessageBus
     * to provide extensibility to other protocols that may be written in the future.
     */
    virtual void idleTick();

    /**
     * Determines the number of packets ready to be processed.
     * @return The number of packets in the receive buffer.
     */
    int dataReady();

    /**
     * Retrieves the next packet from the receive buffer.
     * If a data packet is available, then it will be returned immediately to
     * the caller. This call will also dequeue the buffer.
     *
     * NOTE: Once recv() has been called, it is the callers resposibility to
     * delete the buffer when appropriate.
     *
     * @return The buffer containing the the packet. If no data is available, NULL is returned.
     */
    FrameBuffer* recv();

    /**
     * Transmits the given buffer onto the broadcast radio.
     * The call will wait until the transmission of the packet has completed before returning.
     *
     * @param data The packet contents to transmit.
     * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the BLE stack is running.
     */
    int send(FrameBuffer *buffer);
};

#endif
