#include "cdc_eem.h"
#include "contiki.h"
#include "usb_drv.h"
#include "usb_descriptors.h"
#include "usb_specific_request.h"
#include "rndis/rndis_task.h"
#include "rndis/rndis_protocol.h"
#include "uip.h"
#include "sicslow_ethernet.h"
#include <stdio.h>

#include <avr/pgmspace.h>
#include <util/delay.h>

#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
#define PRINTF printf
#define PRINTF_P printf_P

extern uint8_t usb_eth_data_buffer[64];


//_____ M A C R O S ________________________________________________________


#define EEMCMD_ECHO                    0x00 ///bmEEMCmd Echo
#define EEMCMD_ECHO_RESPONSE           0x01 ///bmEEMCmd Echo Response
#define EEMCMD_SUSPEND_HINT            0x02 ///bmEEMCmd Suspend Hint
#define EEMCMD_RESPONSE_HINT           0x03 ///bmEEMCmd Response Hint
#define EEMCMD_RESPONSE_COMPLETE_HINT  0x04 ///bmEEMCmd Response Complete Hint
#define EEMCMD_TICKLE                  0x05 ///bmEEMCmd Tickle


void cdc_eem_configure_endpoints() {
	usb_configure_endpoint(TX_EP,         \
					  TYPE_BULK,     \
					  DIRECTION_IN,  \
					  SIZE_64,       \
					  TWO_BANKS,     \
					  NYET_ENABLED);

	usb_configure_endpoint(RX_EP,          \
						  TYPE_BULK,     \
					  DIRECTION_OUT, \
					  SIZE_64,       \
					  TWO_BANKS,     \
					  NYET_ENABLED);
	Usb_reset_endpoint(TX_EP);
	Usb_reset_endpoint(RX_EP);
}

void cdc_eem_process(void) {
	uint16_t datalength;
	uint8_t bytecounter, headercounter;
	uint16_t i;

#ifdef USB_ETH_HOOK_INIT
	static uint8_t doInit = 1;
	if (doInit) {
		USB_ETH_HOOK_INIT();
		doInit = 0;
	}
#endif

	//Connected!
	Led0_on();

	Usb_select_endpoint(RX_EP);

	//If we have data and a free buffer
	if(Is_usb_receive_out() && (uip_len == 0)) {

		//Read how much (endpoint only stores up to 64 bytes anyway)
		bytecounter = Usb_byte_counter_8();

		//EEM uses 2 bytes as a header
		headercounter = 2;

		uint8_t fail = 0;

		//Hmm.. what's going on here?
		 if (bytecounter < headercounter) {
			 Usb_ack_receive_out();
					//TODO CO done = 1;
		 }

		 //Read EEM Header
		 i = 0;
		 while (headercounter) {
			usb_eth_data_buffer[i] = Usb_read_byte();
			bytecounter--;
			headercounter--;
			i++;
		 }

		//Order is LSB/MSB, so MSN is in usb_eth_data_buffer[1]
		//Bit 15 indicates command packet when set
		if (usb_eth_data_buffer[1] & 0x80) {
			//not a data payload
			datalength = 0;
		} else {
			//'0' indicates data packet
			//Length is lower 14 bits
			datalength = usb_eth_data_buffer[0] | ((usb_eth_data_buffer[1] & 0x3F) << 8);
		}

		/* EEM Command Packet */
		if ((datalength == 0) && (fail == 0))
		{
			uint8_t command;
			uint16_t echoLength;

			//Strip command off
			command = usb_eth_data_buffer[1] & 0x38;
			command = command >> 3;

			//Decode command type
			switch (command)
			{
				/* Echo Request */
				case EEMCMD_ECHO:

					//Get echo length
					echoLength  = (usb_eth_data_buffer[1] & 0x07) << 8; //MSB
					echoLength |= usb_eth_data_buffer[0];               //LSB

					//TODO: everything. oops.

					break;

				/* Everything else: Whatever. */
				case EEMCMD_ECHO_RESPONSE:
				case EEMCMD_SUSPEND_HINT:
				case EEMCMD_RESPONSE_HINT:
				case EEMCMD_RESPONSE_COMPLETE_HINT:
				case EEMCMD_TICKLE:
					break;

				default: break;
			}
		}
		/* EEM Data Packet */
		else if (datalength && (fail == 0))
		{
			//Looks like we've got a live one
#ifdef USB_ETH_HOOK_RX_START
			USB_ETH_HOOK_RX_START();
#endif

			uint16_t bytes_received = 0;
			uint16_t dataleft = datalength;
			U8 * buffer = uip_buf;

			while(dataleft)
			{
				*buffer++ = Usb_read_byte();

				dataleft--;
				bytecounter--;
				bytes_received++;

				//Check if endpoint is done but we are expecting more data
				if ((bytecounter == 0) && (dataleft))
				{
					//ACK previous data
					Usb_ack_receive_out();

					//Wait for new data
					while (!Is_usb_receive_out());

					//Get new data
					bytecounter = Usb_byte_counter_8();

					//ZLP?
					if (bytecounter == 0)
					{
						//Incomplete!!
						break;
					}
				}
			}

			//Ack final data packet
			Usb_ack_receive_out();

#ifdef USB_ETH_HOOK_RX_END
			USB_ETH_HOOK_RX_END();
#endif

			//Packet has CRC, nobody wants that garbage
			datalength -= 4;

			//Send data over RF or to local stack
			if(datalength <= USB_ETH_MTU) {
				USB_ETH_HOOK_HANDLE_INBOUND_PACKET(uip_buf,datalength);
			} else {
				USB_ETH_HOOK_RX_ERROR("Oversized packet");
			}
		} //if (datalength)
	}  //if(Is_usb_receive_out() && (uip_len == 0))
}


/**
 \brief Send a single ethernet frame using EEM
 */
uint8_t eem_send(uint8_t * senddata, uint16_t sendlen, uint8_t led)
{
	//Make a header
	uint8_t header[2];

	//Fake CRC! Add 4 to length for CRC
	sendlen += 4;
	header[0] = (sendlen >> 8) & 0x3f;
	header[1] = sendlen & 0xff;

	//We send CRC seperatly..
	sendlen -= 4;

	//Send Data
	Usb_select_endpoint(TX_EP);
	//Usb_send_in();

	//Wait for ready
	if(usb_endpoint_wait_for_write_enabled()!=0) {
		USB_ETH_HOOK_TX_ERROR("Timeout: write enabled");
		return 0;
	}

#ifdef USB_ETH_HOOK_TX_START
	USB_ETH_HOOK_TX_START();
#endif

	//Send header (LSB then MSB)
	Usb_write_byte(header[1]);
	Usb_write_byte(header[0]);

	//Send packet
	while(sendlen) {
		Usb_write_byte(*senddata);
		senddata++;
		sendlen--;

		//If endpoint is full, send data in
		//And then wait for data to transfer
		if (!Is_usb_write_enabled()) {
			Usb_send_in();

			if(usb_endpoint_wait_for_write_enabled()!=0) {
				USB_ETH_HOOK_TX_ERROR("Timeout: write enabled");
				return 0;
			}
		}

	}

	//CRC = 0xdeadbeef
	//Linux kernel 2.6.31 needs 0xdeadbeef in wrong order,
	//like this: uint8_t crc[4] = {0xef, 0xbe, 0xad, 0xde};
	//This is fixed in 2.6.32 to the correct order (0xde, 0xad, 0xbe, 0xef)
	uint8_t crc[4] = {0xde, 0xad, 0xbe, 0xef};

	sendlen = 4;
	uint8_t i = 0;

	//Send fake CRC
	while(sendlen) {
		Usb_write_byte(crc[i]);
		i++;
		sendlen--;

		//If endpoint is full, send data in
		//And then wait for data to transfer
		if (!Is_usb_write_enabled()) {
			Usb_send_in();
			if(usb_endpoint_wait_for_write_enabled()!=0) {
				USB_ETH_HOOK_TX_ERROR("Timeout: write enabled");
				return 0;
			}
		}
	}

	//Send last data in - also handles sending a ZLP if needed
	Usb_send_in();

#ifdef USB_ETH_HOOK_TX_END
	USB_ETH_HOOK_TX_END();
#endif

    //Wait for ready
	if(usb_endpoint_wait_for_IN_ready()!=0) {
		USB_ETH_HOOK_TX_ERROR("Timeout: IN ready");
		return 0;
	}

	return 1;
}