#include <stdbool.h>
#include "cdc_ecm.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>
#if RF230BB
#include "rf230bb.h"
#endif

#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];
static U16 usb_ecm_packet_filter = 0;

#define PACKET_TYPE_PROMISCUOUS		(1<<0)
#define PACKET_TYPE_ALL_MULTICAST	(1<<1)
#define PACKET_TYPE_DIRECTED		(1<<2)
#define PACKET_TYPE_BROADCAST		(1<<3)
#define PACKET_TYPE_MULTICAST		(1<<4)

#define Usb_write_word(x)	Usb_write_byte((x)&0xFF),Usb_write_byte((x>>8)&0xFF)
#define Usb_write_long(x)	Usb_write_word((x)&0xFFFF),Usb_write_word((x>>16)&0xFFFF)

#define Usb_read_word()	((U16)Usb_read_byte()+((U16)Usb_read_byte()<<8))

void
cdc_ecm_set_ethernet_packet_filter(void) {
	usb_ecm_packet_filter = Usb_read_word();

  	Usb_ack_receive_setup();
	Usb_send_control_in();
	usb_endpoint_wait_for_read_control_enabled();
	
	PRINTF_P(PSTR("cdc_ecm: Received SET_ETHERNET_PACKET_FILTER: (0x%04X) "),usb_ecm_packet_filter);
	if(usb_ecm_packet_filter & PACKET_TYPE_PROMISCUOUS) {
		PRINTF_P(PSTR("PROMISCUOUS "));
		USB_ETH_HOOK_SET_PROMISCIOUS_MODE(true);
	} else {
		USB_ETH_HOOK_SET_PROMISCIOUS_MODE(false);
	}

	if(usb_ecm_packet_filter & PACKET_TYPE_ALL_MULTICAST)
		PRINTF_P(PSTR("ALL_MULTICAST "));
	if(usb_ecm_packet_filter & PACKET_TYPE_DIRECTED)
		PRINTF_P(PSTR("DIRECTED "));
	if(usb_ecm_packet_filter & PACKET_TYPE_BROADCAST)
		PRINTF_P(PSTR("BROADCAST "));
	if(usb_ecm_packet_filter & PACKET_TYPE_MULTICAST)
		PRINTF_P(PSTR("MULTICAST "));
		
	PRINTF_P(PSTR("\n"));
}


#define CDC_NOTIFY_NETWORK_CONNECTION	(0x00)
#define CDC_NOTIFY_CONNECTION_SPEED_CHANGE	(0x2A)

void
cdc_ecm_notify_network_connection(uint8_t value) {
#if CDC_ECM_USES_INTERRUPT_ENDPOINT
	Usb_select_endpoint(INT_EP);

	if(!Is_usb_endpoint_enabled()) {
		//PRINTF_P(PSTR("cdc_ecm: cdc_ecm_notify_network_connection: endpoint not enabled\n"));
		return;
	}

	if(usb_endpoint_wait_for_IN_ready()!=0) {
		//PRINTF_P(PSTR("cdc_ecm: cdc_ecm_notify_network_connection: Timeout waiting for interrupt endpoint to be available\n"));
		return;
	}

	Usb_send_control_in();

	Usb_write_byte(0x51); // 10100001b
	Usb_write_byte(CDC_NOTIFY_NETWORK_CONNECTION);
	Usb_write_byte(value);
	Usb_write_byte(0x00);
	Usb_write_word(ECM_INTERFACE0_NB);
	Usb_write_word(0x0000);

	Usb_send_in();
	PRINTF_P(PSTR("cdc_ecm: CDC_NOTIFY_NETWORK_CONNECTION %d\n"),value);
#endif
}

#define CDC_ECM_DATA_ENDPOINT_SIZE		64

void cdc_ecm_configure_endpoints() {
#if CDC_ECM_USES_INTERRUPT_ENDPOINT
	usb_configure_endpoint(INT_EP,      \
						 TYPE_INTERRUPT,     \
						 DIRECTION_IN,  \
						 SIZE_8,       \
						 TWO_BANKS,     \
						 NYET_ENABLED);
#endif

	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);
#if CDC_ECM_USES_INTERRUPT_ENDPOINT
	Usb_reset_endpoint(INT_EP);
#endif
	Usb_reset_endpoint(TX_EP);
	Usb_reset_endpoint(RX_EP);
	usb_eth_is_active = 1;
}


void
cdc_ecm_notify_connection_speed_change(uint32_t upstream,uint32_t downstream) {
#if CDC_ECM_USES_INTERRUPT_ENDPOINT
	Usb_select_endpoint(INT_EP);

	if(!Is_usb_endpoint_enabled())
		return;

	if(usb_endpoint_wait_for_IN_ready()!=0)
		return;

	Usb_send_control_in();

	Usb_write_byte(0x51); // 10100001b
	Usb_write_byte(CDC_NOTIFY_CONNECTION_SPEED_CHANGE);
	Usb_write_word(0x0000);
	Usb_write_word(ECM_INTERFACE0_NB);
	Usb_write_word(0x0008);

	Usb_send_in();

	if(usb_endpoint_wait_for_write_enabled()!=0)
		return;

	Usb_write_long(upstream);
	Usb_write_long(downstream);
	Usb_send_in();

	PRINTF_P(PSTR("cdc_ecm: CDC_NOTIFY_CONNECTION_SPEED_CHANGE UP:%d DOWN:%d\n"),upstream,downstream);
#endif
}

void cdc_ecm_set_active(uint8_t value) {
	if(value!=usb_eth_is_active) {
		Led3_on();

		usb_eth_is_active = value;
		cdc_ecm_notify_network_connection(value);
		if(value) {
			cdc_ecm_notify_connection_speed_change(250000,250000);
		} else {
			cdc_ecm_notify_connection_speed_change(0,0);
		}
	}
}

uint8_t
cdc_ecm_process(void) {
	static uint8_t doInit = 1;
	
	Usb_select_endpoint(RX_EP);

	if(!Is_usb_endpoint_enabled()) {
		return 0;
	}

	if (doInit) {
#ifdef USB_ETH_HOOK_INIT
		USB_ETH_HOOK_INIT();
#endif
		cdc_ecm_notify_network_connection(1);
		cdc_ecm_notify_connection_speed_change(250000,250000);
		doInit = 0;
		if(usb_ecm_packet_filter & PACKET_TYPE_PROMISCUOUS) {
#if RF230BB
			rf230_set_promiscuous_mode(true);
#else		
			radio_set_trx_state(RX_ON);
#endif
		}

		// Select again, just to make sure.
		Usb_select_endpoint(RX_EP);
	}

	if(!usb_eth_is_active) {
		// If we aren't active, just eat the packets.
		if(Is_usb_read_enabled()) {
			Usb_ack_receive_out();
		}
		return 0;
	}

	//Connected!
	Led0_on();

	if(Is_usb_read_enabled()) {
		uint16_t bytecounter;
		uint16_t bytes_received = 0;
		U8 * buffer = uip_buf;

		if(!usb_eth_ready_for_next_packet()) {
			// Since we aren't ready for a packet yet,
			// just return.
			goto bail;
		}

#ifdef USB_ETH_HOOK_RX_START
		USB_ETH_HOOK_RX_START();
#endif

		while((bytecounter=Usb_byte_counter_8())==CDC_ECM_DATA_ENDPOINT_SIZE) {
			while((bytes_received<USB_ETH_MTU) && (bytecounter--)) {
				*buffer++ = Usb_read_byte();
				bytes_received++;
			}
			bytes_received+=bytecounter+1;

			//ACK previous data
			Usb_ack_receive_out();

			//Wait for new data
			if(usb_endpoint_wait_for_read_enabled()!=0) {
				USB_ETH_HOOK_RX_ERROR("Timeout: read enabled");
				goto bail;
			}
		}
		bytecounter = Usb_byte_counter_8();
		while((bytes_received<USB_ETH_MTU) && (bytecounter--)) {
			*buffer++ = Usb_read_byte();
			bytes_received++;
		}
		bytes_received+=bytecounter+1;
		
		//Ack final data packet
		Usb_ack_receive_out();

		//PRINTF_P(PSTR("cdc_ecm: Got packet %d bytes long\n"),bytes_received);

#ifdef USB_ETH_HOOK_RX_END
		USB_ETH_HOOK_RX_END();
#endif
		
		//Send data over RF or to local stack
		if(bytes_received<=USB_ETH_MTU) {

			USB_ETH_HOOK_HANDLE_INBOUND_PACKET(uip_buf,bytes_received);
		} else {
			USB_ETH_HOOK_RX_ERROR("Oversized packet");
		}
	}
bail:
	return 1;
}

uint8_t
ecm_send(uint8_t * senddata, uint16_t sendlen, uint8_t led) {
	U8 byte_in_packet = 0;

	//Send Data
	Usb_select_endpoint(TX_EP);

	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 packet
	while(sendlen) {
		Usb_write_byte(*senddata);
		senddata++;
		sendlen--;
		byte_in_packet++;
		
		//If endpoint is full, send data in
		//And then wait for data to transfer
		if (!Is_usb_write_enabled()) {
			Usb_ack_in_ready();

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

	}

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

#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;
}