/*
 This project 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.

 Multiprotocol 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 Multiprotocol.  If not, see <http://www.gnu.org/licenses/>.
 */
// compatible with MJX Bugs 3 Mini and Bugs 3H

#if defined(BUGSMINI_NRF24L01_INO)

#include "iface_xn297.h"

#define BUGSMINI_INITIAL_WAIT    500
#define BUGSMINI_PACKET_INTERVAL 6840
#define BUGSMINI_WRITE_WAIT      2000
#define BUGSMINI_TX_PAYLOAD_SIZE 24
#define BUGSMINI_RX_PAYLOAD_SIZE 16
#define BUGSMINI_NUM_RF_CHANNELS 15
#define BUGSMINI_ADDRESS_SIZE    5

static uint8_t BUGSMINI_txid[3];
static uint8_t BUGSMINI_txhash;

enum {
    BUGSMINI_BIND1,
    BUGSMINI_BIND2,
    BUGSMINI_DATA1,
    BUGSMINI_DATA2
};

#define BUGSMINI_CH_SW_ARM		CH5_SW
#define BUGSMINI_CH_SW_ANGLE	CH6_SW
#define BUGSMINI_CH_SW_FLIP		CH7_SW
#define BUGSMINI_CH_SW_PICTURE	CH8_SW
#define BUGSMINI_CH_SW_VIDEO	CH9_SW
#define BUGSMINI_CH_SW_LED		CH10_SW
#define BUGSMINI_CH_SW_ALTHOLD	CH11_SW

// flags packet[12]
#define BUGSMINI_FLAG_FLIP    0x08    // automatic flip
#define BUGSMINI_FLAG_MODE    0x04    // low/high speed select (set is high speed)
#define BUGSMINI_FLAG_VIDEO   0x02    // toggle video
#define BUGSMINI_FLAG_PICTURE 0x01    // toggle picture

// flags packet[13]
#define BUGSMINI_FLAG_LED     0x80    // enable LEDs
#define BUGSMINI_FLAG_ARM     0x40    // arm (toggle to turn on motors)
#define BUGSMINI_FLAG_DISARM  0x20    // disarm (toggle to turn off motors)
#define BUGSMINI_FLAG_ANGLE   0x02    // angle/acro mode (set is angle mode)
#define BUGSMINI_FLAG_ALTHOLD 0x04    // angle/altitude hold mode (set is altitude mode)

static void __attribute__((unused)) BUGSMINI_RF_init()
{
	XN297_Configure(XN297_CRCEN, XN297_SCRAMBLED, XN297_1M);
	//XN297_HoppingCalib(BUGSMINI_NUM_RF_CHANNELS*2);
}

static void __attribute__((unused)) BUGSMINI_check_arming()
{
	uint8_t arm_channel = BUGSMINI_CH_SW_ARM;

	if (arm_channel != arm_channel_previous)
	{
		arm_channel_previous = arm_channel;
		if (arm_channel)
		{
			armed = 1;
			arm_flags ^= BUGSMINI_FLAG_ARM;
		}
		else
		{
			armed = 0;
			arm_flags ^= BUGSMINI_FLAG_DISARM;
		}
	}
}

static void __attribute__((unused)) BUGSMINI_send_packet()
{
	BUGSMINI_check_arming();  // sets globals arm_flags and armed

	uint16_t aileron  = convert_channel_16b_limit(AILERON,500,0);
	uint16_t elevator = convert_channel_16b_limit(ELEVATOR,0,500);
	uint16_t throttle = armed ? convert_channel_16b_limit(THROTTLE,0,500) : 0;
	uint16_t rudder   = convert_channel_16b_limit(RUDDER,500,0);

	packet[1] = BUGSMINI_txid[0];
	packet[2] = BUGSMINI_txid[1];
	packet[3] = BUGSMINI_txid[2];
	if(IS_BIND_IN_PROGRESS)
	{
		packet[4] = 0x00;
		packet[5] = 0x7d;
		packet[6] = 0x7d;
		packet[7] = 0x7d;
		packet[8] = 0x20;
		packet[9] = 0x20;
		packet[10]= 0x20;
		packet[11]= 0x40;
		packet[12]^= 0x40;	 // alternating freq hopping flag
		packet[13]= 0x60;
		packet[14]= 0x00;
		packet[15]= 0x00;
	}
	else
	{
		packet[4] = throttle >> 1;
		packet[5] = rudder >> 1;
		packet[6] = elevator >> 1;
		packet[7] = aileron >> 1;
		packet[8] = (((aileron / 5) >> 1) + 7)   // dynamic trim 0x07 - 0x39
					| (aileron << 7);
		packet[9] = (((elevator / 5) >> 1) + 7)  // dynamic trim 0x07 - 0x39
					| (elevator << 7);
		packet[10]= (((rudder / 5) >> 1) + 7)    // dynamic trim 0x07 - 0x39
					| (rudder << 7);
		packet[11]= 0x40 | (throttle << 7);
		packet[12]= 0x80 | ((packet[12] ^ 0x40) & 0x40)
			| BUGSMINI_FLAG_MODE
			| GET_FLAG(BUGSMINI_CH_SW_PICTURE, BUGSMINI_FLAG_PICTURE)
			| GET_FLAG(BUGSMINI_CH_SW_VIDEO, BUGSMINI_FLAG_VIDEO);
		if(armed)
			packet[12] |= GET_FLAG(BUGSMINI_CH_SW_FLIP, BUGSMINI_FLAG_FLIP);
		packet[13] = arm_flags
			| GET_FLAG(BUGSMINI_CH_SW_LED, BUGSMINI_FLAG_LED)
			| GET_FLAG(BUGSMINI_CH_SW_ALTHOLD, BUGSMINI_FLAG_ALTHOLD)
			| GET_FLAG(BUGSMINI_CH_SW_ANGLE, BUGSMINI_FLAG_ANGLE);
			// BUGS3H althold -> BUGSMINI_FLAG_ALTHOLD|BUGSMINI_FLAG_ANGLE , angle -> 0
		packet[14] = 0;
		packet[15] = 0; // a lot of 0x53 and some 0x52 on bugs 3H
	}
	uint8_t checksum = 0x6d;
	for(uint8_t i=1; i < BUGSMINI_TX_PAYLOAD_SIZE; i++)
	checksum ^= packet[i];
	packet[0] = checksum;

	if(!(packet[12]&0x40))
	{
		hopping_frequency_no++;
		if(hopping_frequency_no >= BUGSMINI_NUM_RF_CHANNELS)
			hopping_frequency_no = 0;
		XN297_Hopping(IS_BIND_IN_PROGRESS ? hopping_frequency_no+BUGSMINI_NUM_RF_CHANNELS : hopping_frequency_no);
	}

	// Send
	XN297_SetPower();
	XN297_SetTxRxMode(TXRX_OFF);
	XN297_SetTxRxMode(TX_EN);
	XN297_WritePayload(packet, BUGSMINI_TX_PAYLOAD_SIZE);
}

// compute final address for the rxid received during bind
// thanks to Pascal for the function!
const uint8_t PROGMEM BUGSMINI_end []= {
	0x2d,0x9e ,0x95,0xa4 ,0x9c,0x5c ,0xb4,0xa6 ,0xa9,0xce ,0x56,0x2b ,0x3e,0x73 ,0xb8,0x95 ,0x6a,0x82,
	0x94,0x37 ,0x3d,0x5a ,0x4b,0xb2 ,0x69,0x49 ,0xc2,0x24 ,0x6b,0x3d ,0x23,0xc6 ,0x9e,0xa3 ,0xa4,0x98,
	0x5c,0x9e ,0xa6,0x52 ,0xce,0x76 ,0x2b,0x4b ,0x73,0x3a };
static void __attribute__((unused)) BUGSMINI_make_address()
{
    uint8_t start, length, index;

	//read rxid
	uint8_t base_adr=BUGSMINI_EEPROM_OFFSET+(RX_num&0x0F)*2;
    uint8_t rxid_high = eeprom_read_byte((EE_ADDR)(base_adr+0));
    uint8_t rxid_low  = eeprom_read_byte((EE_ADDR)(base_adr+1));
    
    if(rxid_high==0x00 || rxid_high==0xFF)
        rx_tx_addr[0]=0x52;
    else
        rx_tx_addr[0]=rxid_high;
    
    rx_tx_addr[1]=BUGSMINI_txhash;
    
    if(rxid_low==0x00 || rxid_low==0xFF)
        rx_tx_addr[2]=0x66;
    else
        rx_tx_addr[2]=rxid_low;
    
    for(uint8_t end_idx=0;end_idx<23;end_idx++)
    {
        //calculate sequence start
        if(end_idx<=7)
            start=end_idx;
        else
            start=(end_idx-7)*16+7;
        //calculate sequence length
        if(end_idx>6)
        {
            if(end_idx>15)
                length=(23-end_idx)<<1;
            else
                length=16;
        }
        else
            length=(end_idx+1)<<1;
        //calculate first index
        index=start-rxid_high;
        //scan for a possible match using the current end
        for(uint8_t i=0;i<length;i++)
        {
            if(index==rxid_low)
            { //match found
                rx_tx_addr[3]=pgm_read_byte_near( &BUGSMINI_end[end_idx<<1] );
                rx_tx_addr[4]=pgm_read_byte_near( &BUGSMINI_end[(end_idx<<1)+1] );
                return;
            }
            index+=i&1?7:8;	//increment index
        }
    }
    // Something wrong happened if we arrive here....
}

#if defined(BUGS_HUB_TELEMETRY)
static void __attribute__((unused)) BUGSMINI_update_telemetry()
{
	uint8_t checksum = 0x6d;
	for(uint8_t i=1; i<12; i++)
		checksum += packet_in[i];
	if(packet_in[0] == checksum)
	{
		RX_RSSI = packet_in[3];
		if(sub_protocol==BUGS3H)
		{
			if(packet_in[11] & 0x40)
				v_lipo1 = 0x40; // Warning
			else if(packet_in[11] & 0x80)
				v_lipo1 = 0x20; // Critical
			else
				v_lipo1 = 0x80; // Ok
		}
		else
		{
			if(packet_in[11] & 0x80)
				v_lipo1 = 0x80; // Ok
			else if(packet_in[11] & 0x40)
				v_lipo1 = 0x40; // Warning
			else
				v_lipo1 = 0x20; // Critical
		}
		telemetry_link=1;
	}
}
#endif

uint16_t BUGSMINI_callback()
{
	uint8_t base_adr;
	switch(phase)
	{
		case BUGSMINI_BIND1:
			if( XN297_IsRX() )
			{ // RX fifo data ready
				XN297_ReadPayload(packet, BUGSMINI_RX_PAYLOAD_SIZE);	// Not checking the CRC??
				base_adr=BUGSMINI_EEPROM_OFFSET+(RX_num&0x0F)*2;
				eeprom_write_byte((EE_ADDR)(base_adr+0),packet[1]);		// Save rxid in EEPROM
				eeprom_write_byte((EE_ADDR)(base_adr+1),packet[2]);		// Save rxid in EEPROM
				BUGSMINI_make_address();
				XN297_SetTXAddr(rx_tx_addr, 5);
				XN297_SetRXAddr(rx_tx_addr, BUGSMINI_RX_PAYLOAD_SIZE);
				phase = BUGSMINI_DATA1;
				BIND_DONE;
				break;
			}
			BUGSMINI_send_packet();
			phase = BUGSMINI_BIND2;
			return BUGSMINI_WRITE_WAIT;
		case BUGSMINI_BIND2:
			// switch to RX mode
			XN297_SetTxRxMode(TXRX_OFF);
			XN297_SetTxRxMode(RX_EN);
			phase = BUGSMINI_BIND1;
			return BUGSMINI_PACKET_INTERVAL - BUGSMINI_WRITE_WAIT;
		case BUGSMINI_DATA1:
			#ifdef MULTI_SYNC
				telemetry_set_input_sync(BUGSMINI_PACKET_INTERVAL);
			#endif
			#if defined(BUGS_HUB_TELEMETRY)
				if( XN297_IsRX() )
				{
					XN297_ReadPayload(packet_in, BUGSMINI_RX_PAYLOAD_SIZE);	// Not checking the CRC??
					BUGSMINI_update_telemetry();
				}
			#endif
			BUGSMINI_send_packet();
	#if not defined(BUGS_HUB_TELEMETRY)
			break;
	#else
			phase = BUGSMINI_DATA2;
			return BUGSMINI_WRITE_WAIT;
		case BUGSMINI_DATA2:
			// switch to RX mode
			XN297_SetTxRxMode(TXRX_OFF);
			XN297_SetTxRxMode(RX_EN);
			phase = BUGSMINI_DATA1;
			return BUGSMINI_PACKET_INTERVAL - BUGSMINI_WRITE_WAIT;
	#endif
	}
	return BUGSMINI_PACKET_INTERVAL;
}

#define BUGSMINI_NUM_TX_RF_MAPS 4
// haven't figured out BUGSMINI_txid<-->rf channel mapping yet
const uint8_t PROGMEM BUGSMINI_RF_chans[BUGSMINI_NUM_TX_RF_MAPS][BUGSMINI_NUM_RF_CHANNELS] = {
	{0x22,0x2f,0x3a,0x14,0x20,0x2d,0x38,0x18,0x26,0x32,0x11,0x1d,0x29,0x35,0x17},
	{0x3d,0x34,0x2b,0x22,0x19,0x40,0x37,0x2e,0x25,0x1c,0x3a,0x31,0x28,0x1f,0x16},
	{0x12,0x20,0x2f,0x1a,0x28,0x38,0x14,0x23,0x32,0x1c,0x2c,0x3b,0x17,0x26,0x34},
	{0x13,0x25,0x37,0x1F,0x31,0x17,0x28,0x3A,0x1C,0x2E,0x22,0x33,0x19,0x2B,0x3D} };
const uint8_t PROGMEM BUGSMINI_bind_chans[BUGSMINI_NUM_RF_CHANNELS] = {
	0x1A,0x23,0x2C,0x35,0x3E,0x17,0x20,0x29,0x32,0x3B,0x14,0x1D,0x26,0x2F,0x38}; // bugs 3 mini bind channels
const uint8_t PROGMEM BUGSMINI_tx_id[BUGSMINI_NUM_TX_RF_MAPS][3] = {
	{0xA8,0xE6,0x32},
	{0xdd,0xab,0xfd},
	{0x90,0x9e,0x4a},
	{0x20,0x28,0xBA} };
const uint8_t PROGMEM BUGSMINI_tx_hash[BUGSMINI_NUM_TX_RF_MAPS] = { // 2nd byte of final address
	0x6c,0x9e,0x3d,0xb3};
	
static void __attribute__((unused)) BUGSMINI_initialize_txid()
{
	// load hopping_frequency with tx channels in low part and bind channels in high part
	for(uint8_t i=0; i<BUGSMINI_NUM_RF_CHANNELS;i++)
	{
		hopping_frequency[i]=pgm_read_byte_near( &BUGSMINI_RF_chans[rx_tx_addr[3]%BUGSMINI_NUM_TX_RF_MAPS][i] );
		hopping_frequency[i+BUGSMINI_NUM_RF_CHANNELS]=pgm_read_byte_near( &BUGSMINI_bind_chans[i] );
	}
	// load txid
	for(uint8_t i=0; i<sizeof(BUGSMINI_txid);i++)
		BUGSMINI_txid[i]=pgm_read_byte_near( &BUGSMINI_tx_id[rx_tx_addr[3]%BUGSMINI_NUM_TX_RF_MAPS][i] );
	//load tx_hash
	BUGSMINI_txhash = pgm_read_byte_near( &BUGSMINI_tx_hash[rx_tx_addr[3]%BUGSMINI_NUM_TX_RF_MAPS] );
}

void BUGSMINI_init()
{
	BUGSMINI_initialize_txid();
	BUGSMINI_RF_init();
	memset(packet, (uint8_t)0, BUGSMINI_TX_PAYLOAD_SIZE);
	if(IS_BIND_IN_PROGRESS)
	{
		XN297_SetTXAddr((const uint8_t*)"mjxRC", 5);
		XN297_SetRXAddr((const uint8_t*)"mjxRC", BUGSMINI_RX_PAYLOAD_SIZE);
		phase = BUGSMINI_BIND1;
	}
	else
	{
		BUGSMINI_make_address();
		XN297_SetTXAddr(rx_tx_addr, 5);
		XN297_SetRXAddr(rx_tx_addr, BUGSMINI_RX_PAYLOAD_SIZE);
		phase = BUGSMINI_DATA1;
	}
	armed = 0;
	arm_flags = BUGSMINI_FLAG_DISARM;    // initial value from captures
	arm_channel_previous = BUGSMINI_CH_SW_ARM;
}

#endif
