/*
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#pragma once

#if HAL_RCINPUT_WITH_AP_RADIO

/*
  AP_Radio implementation for Cypress 2.4GHz radio.

  With thanks to the SuperBitRF project
  See http://wiki.paparazziuav.org/wiki/SuperbitRF

  This implementation uses the DSMX protocol on a CYRF6936
 */

#include "AP_Radio_backend.h"
#include "hal.h"
#include "telem_structure.h"

class AP_Radio_cypress : public AP_Radio_backend
{
public:
    AP_Radio_cypress(AP_Radio &radio);

    // init - initialise radio
    bool init(void) override;

    // rest radio
    bool reset(void) override;

    // send a packet
    bool send(const uint8_t *pkt, uint16_t len) override;

    // start bind process as a receiver
    void start_recv_bind(void) override;

    // return time in microseconds of last received R/C packet
    uint32_t last_recv_us(void) override;

    // return number of input channels
    uint8_t num_channels(void) override;

    // return current PWM of a channel
    uint16_t read(uint8_t chan) override;

    // handle a data96 mavlink packet for fw upload
    void handle_data_packet(mavlink_channel_t chan, const mavlink_data96_t &m) override;

    // update status
    void update(void) override;

    // get TX fw version
    uint32_t get_tx_version(void) override
    {
        // pack date into 16 bits for vendor_id in AUTOPILOT_VERSION
        return (uint16_t(dsm.tx_firmware_year)<<12) + (uint16_t(dsm.tx_firmware_month)<<8) + dsm.tx_firmware_day;
    }

    // get radio statistics structure
    const AP_Radio::stats &get_stats(void) override;

    // set the 2.4GHz wifi channel used by companion computer, so it can be avoided
    void set_wifi_channel(uint8_t channel) override
    {
        t_status.wifi_chan = channel;
    }

private:
    AP_HAL::OwnPtr<AP_HAL::SPIDevice> dev;
    static AP_Radio_cypress *radio_singleton;

    void radio_init(void);

    void dump_registers(uint8_t n);

    void force_initial_state(void);
    void set_channel(uint8_t channel);
    uint8_t read_status_debounced(uint8_t adr);

    uint8_t read_register(uint8_t reg);
    void write_register(uint8_t reg, uint8_t value);
    void write_multiple(uint8_t reg, uint8_t n, const uint8_t *data);

    enum {
        STATE_RECV,
        STATE_BIND,
        STATE_AUTOBIND,
        STATE_SEND_TELEM,
        STATE_SEND_TELEM_WAIT,
        STATE_SEND_FCC
    } state;

    struct config {
        uint8_t reg;
        uint8_t value;
    };
    static const uint8_t pn_codes[5][9][8];
    static const uint8_t pn_bind[];
    static const config cyrf_config[];
    static const config cyrf_bind_config[];
    static const config cyrf_transfer_config[];

    virtual_timer_t timeout_vt;
    static thread_t *_irq_handler_ctx;
    void radio_set_config(const struct config *config, uint8_t size);

    void start_receive(void);

    // main IRQ handler
    void irq_handler(void);

    // IRQ handler for packet receive
    void irq_handler_recv(uint8_t rx_status);

    // handle timeout IRQ
    void irq_timeout(void);

    // trampoline functions to take us from static IRQ function to class functions

    static void irq_handler_thd(void* arg);
    static void trigger_irq_radio_event(void);
    static void trigger_timeout_event(void *arg);

    static const uint8_t max_channels = 16;

    uint32_t last_debug_print_ms;

    void print_debug_info(void);

    AP_Radio::stats stats;
    AP_Radio::stats last_stats;

    enum dsm_protocol {
        DSM_NONE   = 0,      // not bound yet
        DSM_DSM2_1 = 0x01,   // The original DSM2 protocol with 1 packet of data
        DSM_DSM2_2 = 0x02,   // The original DSM2 protocol with 2 packets of data
        DSM_DSMX_1 = 0xA2,   // The original DSMX protocol with 1 packet of data
        DSM_DSMX_2 = 0xB2,   // The original DSMX protocol with 2 packets of data
    };

    enum dsm2_sync {
        DSM2_SYNC_A,
        DSM2_SYNC_B,
        DSM2_OK
    };

    // semaphore between ISR and main thread
    HAL_Semaphore sem;

    // dsm config data and status
    struct {
        uint8_t channels[23];
        enum dsm_protocol protocol;
        uint8_t mfg_id[4];
        uint8_t current_channel;
        uint8_t current_rf_channel;
        uint16_t crc_seed;
        uint8_t sop_col;
        uint8_t data_col;
        uint8_t last_sop_code[8];
        uint8_t last_data_code[16];

        uint32_t receive_start_us;
        uint32_t receive_timeout_msec;

        uint32_t last_recv_us;
        uint32_t last_parse_us;
        uint32_t last_recv_chan;
        uint32_t last_chan_change_us;
        uint16_t num_channels;
        uint16_t pwm_channels[max_channels];
        bool need_bind_save;
        enum dsm2_sync sync;
        uint32_t crc_errors;
        float rssi;
        bool last_discrc;
        uint8_t last_transmit_power;
        uint32_t send_irq_count;
        uint32_t send_count;
        uint16_t pkt_time1 = 3000;
        uint16_t pkt_time2 = 7000;
        uint8_t tx_firmware_year;
        uint8_t tx_firmware_month;
        uint8_t tx_firmware_day;
        int8_t forced_channel = -1;
        uint8_t tx_rssi;
        uint8_t tx_pps;
        uint32_t last_autobind_send;
        bool have_tx_pps;
        uint32_t telem_send_count;
        uint8_t tx_bl_version;
    } dsm;

    struct {
        mavlink_channel_t chan;
        bool need_ack;
        uint8_t counter;
        uint8_t sequence;
        uint32_t offset;
        uint32_t length;
        uint32_t acked;
        uint8_t len;
        enum telem_type fw_type;
        uint8_t pending_data[92];
    } fwupload;

    // bind structure saved to storage
    static const uint16_t bind_magic = 0x43F6;
    struct PACKED bind_info {
        uint16_t magic;
        uint8_t  mfg_id[4];
        enum dsm_protocol protocol;
    };

    struct telem_status t_status;

    // DSM specific functions
    void dsm_set_channel(uint8_t channel, bool is_dsm2, uint8_t sop_col, uint8_t data_col, uint16_t crc_seed);

    // generate DSMX channels
    void dsm_generate_channels_dsmx(uint8_t mfg_id[4], uint8_t channels[23]);

    // setup for DSMX transfers
    void dsm_setup_transfer_dsmx(void);

    // choose channel to receive on
    void dsm_choose_channel(void);

    // map for mode1/mode2
    void map_stick_mode(uint16_t *channels);

    // parse DSM channels from a packet
    bool parse_dsm_channels(const uint8_t *data);

    // process an incoming packet
    void process_packet(const uint8_t *pkt, uint8_t len);

    // process an incoming bind packet
    void process_bind(const uint8_t *pkt, uint8_t len);

    // load bind info from storage
    void load_bind_info(void);

    // save bind info to storage
    void save_bind_info(void);

    bool is_DSM2(void);

    // send a 16 byte packet
    void transmit16(const uint8_t data[16]);

    void send_telem_packet(void);
    void irq_handler_send(uint8_t tx_status);

    void send_FCC_test_packet(void);

    // check sending of fw upload ack
    void check_fw_ack(void);

    // re-sync DSM2
    void dsm2_start_sync(void);

    // check for double binding
    void check_double_bind(void);

    // setup a timeout handler
    void setup_timeout(uint32_t timeout_ms);
};

#endif