#include "pxt.h"

using namespace pxt;

#define MAX_FIELD_NAME_LENGTH 12

//% color=270 weight=34
namespace radio {

    // -------------------------------------------------------------------------
    // Radio
    // -------------------------------------------------------------------------    
    bool radioEnabled = false;
    bool transmitSerialNumber = false;
    
    PacketBuffer packet;
    
    int radioEnable() {
        int r = uBit.radio.enable();
        if (r != MICROBIT_OK) {
            uBit.panic(43);
            return r;
        }
        if (!radioEnabled) {
            uBit.radio.setGroup(pxt::programHash());
            radioEnabled = true;
        }
        return r;
    }

    void broadcastMessage(int message) {
        if (radioEnable() != MICROBIT_OK) return;
        uBit.radio.event.eventReceived(MicroBitEvent(MES_BROADCAST_GENERAL_ID, message, CREATE_ONLY));
    }
        
    void onBroadcastMessageReceived(int message, Action f) {
        if (radioEnable() != MICROBIT_OK) return;
        registerWithDal(MES_BROADCAST_GENERAL_ID, message, f);
    }

    /**
     * Broadcasts a number over radio to any connected micro:bit in the group.
     */    
    //% help=radio/send-number
    //% weight=60
    //% blockId=radio_datagram_send block="send number %value" blockGap=8
    void sendNumber(int value) { 
        if (radioEnable() != MICROBIT_OK) return;        
        uint32_t t = system_timer_current_time();
        uint32_t sn = transmitSerialNumber ? microbit_serial_number() : 0;
        uint32_t buf[] = { (uint32_t)value, t, sn };        
        uBit.radio.datagram.send((uint8_t*)buf, 3*sizeof(uint32_t));
    }
    
    /**
    * Broadcasts a name / value pair along with the device serial number
    * and running time to any connected BBC micro:bit in the group.
    * @param name the field name (max 12 characters), eg: "data"
    * @param value the numberic value
    */
    //% help=radio/send-value
    //% weight=59
    //% blockId=radio_datagram_send_value block="send|value %name|= %value" blockGap=8
    void sendValue(StringData* name, int value) {
        if (radioEnable() != MICROBIT_OK) return;

        ManagedString n(name);     
        uint32_t t = system_timer_current_time();           
        uint32_t sn = transmitSerialNumber ? microbit_serial_number() : 0;
        uint8_t buf[32];
        uint32_t* buf32 = (uint32_t*)buf;
        memset(buf, 32, 0);
        buf32[0] = value;                      // 4 bytes: value
        buf32[1] = t; // 4 bytes: running time
        buf32[2] = sn; // 4 bytes: serial number
        uint8_t len = min(MAX_FIELD_NAME_LENGTH, n.length());          // 1 byte: string length
        if (len > 0) {
            buf[12] = len;                          // 
            memcpy(buf + 13, n.toCharArray(), len); // 13-25: field name            
        }
        uBit.radio.datagram.send(buf, 13 + len);
    }
    
    /**
     * Broadcasts a number over radio to any connected micro:bit in the group.
     */    
    //% help=radio/send-string
    //% weight=58
    //% blockId=radio_datagram_send_string block="send string %msg"
    void sendString(StringData* msg) {
        if (radioEnable() != MICROBIT_OK) return;        
        
        ManagedString s(msg);
        if (s.length() > MICROBIT_RADIO_MAX_PACKET_SIZE)
            s = s.substring(0, MICROBIT_RADIO_MAX_PACKET_SIZE);
        
        uBit.radio.datagram.send(s);
    }
            
    /**
    * Reads a value sent with `stream value` and writes it
    * to the serial stream as JSON
    */
    //% help=radio/write-value-to-serial
    //% weight=3
    //% blockId=radio_write_value_serial block="write value to serial"
    void writeValueToSerial() {
        if (radioEnable() != MICROBIT_OK) return;
        PacketBuffer p = uBit.radio.datagram.recv();
        int length = p.length();
        uint8_t* bytes = p.getBytes();
        int value;
        
        uBit.serial.send("{");
        if (length >= 4) {
            memcpy(&value, bytes, 4);
            uBit.serial.send("v:"); uBit.serial.send(value);
            if(length >= 8) {
                memcpy(&value, bytes + 4, 4);
                uBit.serial.send(",t:"); uBit.serial.send(value);                
                if (length >= 12) {
                    memcpy(&value, bytes + 8, 4);
                    uBit.serial.send(",s:"); uBit.serial.send(value);                                    
                    if (length >= 13) {
                        char name[MAX_FIELD_NAME_LENGTH+1];
                        uint8_t len = min(MAX_FIELD_NAME_LENGTH, bytes[12]);
                        memcpy(name, bytes + 13, len);
                        name[len] = 0;
                        uBit.serial.send(",n:\""); uBit.serial.send(name); uBit.serial.send("\"");                                    
                    }
                }
            }
        }
        uBit.serial.send("}\r\n");
    }

    /**
     * Registers code to run when a packet is received over radio.
     */
    //% help=radio/on-data-received
    //% weight=50
    //% blockId=radio_datagram_received_event block="on data received" blockGap=8
    void onDataReceived(Action body) {
        if (radioEnable() != MICROBIT_OK) return;
        registerWithDal(MICROBIT_ID_RADIO, MICROBIT_RADIO_EVT_DATAGRAM, body);    
    }

    /**
     * Reads a number at a given index, between ``0`` and ``3``, from the packet received by ``receive number``. Not supported in simulator.
     * @param index index of the number to read from 0 to 3. 1 eg
     */
    //% help=radio/received-number-at
    //% weight=45 debug=true
    //% blockId=radio_datagram_received_number_at block="receive number|at %VALUE" blockGap=8
    int receivedNumberAt(int index) {
        if (radioEnable() != MICROBIT_OK) return 0;
        if (0 <= index && index < packet.length() / 4) {
            // packet.getBytes() is not aligned
            int r;
            memcpy(&r, packet.getBytes() + index * 4, 4);
            return r;
        }
        return 0;
    }

    /**
     * Reads the next packet as a number from the radio queue.
     */
    //% help=radio/receive-number
    //% weight=46
    //% blockId=radio_datagram_receive block="receive number" blockGap=8
    int receiveNumber()
    {
        if (radioEnable() != MICROBIT_OK) return 0;
        packet = uBit.radio.datagram.recv();
        return receivedNumberAt(0);
    }
    
    /**
    * Reads the next packet as a string and returns it.
    */
    //% blockId=radio_datagram_receive_string block="receive string" blockGap=8
    //% weight=44
    //% help=radio/receive-string
    StringData* receiveString() {
        if (radioEnable() != MICROBIT_OK) return ManagedString().leakData();
        packet = uBit.radio.datagram.recv();
        return ManagedString(packet).leakData();        
    }
    
    /**
     * Gets the received signal strength indicator (RSSI) from the packet received by ``receive number``. Not supported in simulator.
     * namespace=radio
     */
    //% help=radio/received-signal-strength
    //% weight=40
    //% blockId=radio_datagram_rssi block="received signal strength"
    int receivedSignalStrength() {
        if (radioEnable() != MICROBIT_OK) return 0;
        return packet.getRSSI();
    }

    /**
     * Sets the group id for radio communications. A micro:bit can only listen to one group ID at any time.
     * @ param id the group id between ``0`` and ``255``, 1 eg
     */
    //% help=radio/set-group
    //% weight=10
    //% blockId=radio_set_group block="set group %ID"
    void setGroup(int id) {
        if (radioEnable() != MICROBIT_OK) return;
        uBit.radio.setGroup(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, 7 eg
    */
    //% help=radio/set-transmit-power
    //% weight=9
    //% blockId=radio_set_transmit_power block="set transmit power %power"
    void setTransmitPower(int power) {
        if (radioEnable() != MICROBIT_OK) return;
        uBit.radio.setTransmitPower(power);
    }
    
    /**
    * Set the radio to transmit the serial number in each message.
    */
    //% help=radio/set-transmit-serial-number
    //% weight=8
    //% block=radio_set_transmit_serial_number block="set tranmist serial number %transmit"
    void setTransmitSerialNumber(bool transmit) {
        transmitSerialNumber = transmit;
    }
}