#ifndef LAKS_USB_USB_NRF_H
#define LAKS_USB_USB_NRF_H

#include "generic.h"
#include "usb_nrf_def.h"

#include <string.h>

class USB_NRF : public USB_generic {
	private:
		USB_NRF_t& usb;
		
		uint32_t ep_in_busy;
		
		uint8_t buf_in[8][64];
		uint8_t buf_out[8][64];
		
	protected:
		virtual void hw_set_address(uint8_t addr) {
			usb_rblog.log("SetAddress: %d", addr);
			
			// Do nothing, handled in hardware.
		}
		
		virtual void hw_conf_ep(uint8_t ep, EPType type, uint32_t size) {
			usb_rblog.log("Configuring EP%02x: size=%d", ep, size);
			
			uint8_t in = ep & 0x80;
			ep &= 0x7f;
			
			if(in || ep == 0) {
				usb.reg_in[ep].PTR = (uint32_t)&buf_in[ep];
				usb.reg.EPINEN |= 1 << ep;
			}
			
			if(!in) {
				usb.reg_out[ep].PTR = (uint32_t)&buf_out[ep];
				
				usb.reg.SIZE_EPOUT[ep] = 0;
				usb.reg.EPOUTEN |= 1 << ep;
			}
		}
		
		virtual void hw_set_stall(uint8_t ep) {
			if(ep == 0) {
				usb.tasks.EP0STALL = 1;
			}
		}
	
	public:
		USB_NRF(USB_NRF_t& usb_periph, desc_t dev, desc_t conf) : USB_generic(dev, conf), usb(usb_periph) {}
		
		void init() {
			*(volatile uint32_t*)0x4006ec00 = 0x00009375;
			*(volatile uint32_t*)0x4006ed14 = 0x00000003;
			*(volatile uint32_t*)0x4006ec00 = 0x00009375;
			
			usb.reg.ENABLE = 1;
			
			while(!(usb.reg.EVENTCAUSE & (1 << 11)));
			
			usb.reg.EVENTCAUSE = 1 << 11;
			
			*(volatile uint32_t*)0x4006ec00 = 0x00009375;
			*(volatile uint32_t*)0x4006ed14 = 0x00000000;
			*(volatile uint32_t*)0x4006ec00 = 0x00009375;
			
			usb.reg.USBPULLUP = 1;
		}
		
		void process() {
			if(usb.events.USBRESET) {
				usb.events.USBRESET = 0;
				
				usb_rblog.log("USB Reset");
				
				handle_reset();
				return;
			}
			
			if(usb.events.EP0SETUP) {
				usb.events.EP0SETUP = 0;
				
				uint8_t setupbuf[8] = {
					(uint8_t)usb.reg.BMREQUESTTYPE,
					(uint8_t)usb.reg.BREQUEST,
					(uint8_t)usb.reg.WVALUEL,
					(uint8_t)usb.reg.WVALUEH,
					(uint8_t)usb.reg.WINDEXL,
					(uint8_t)usb.reg.WINDEXH,
					(uint8_t)usb.reg.WLENGTHL,
					(uint8_t)usb.reg.WLENGTHH,
				};
				
				handle_setup((uint32_t*)&setupbuf);
			}
			
			if(usb.events.EP0DATADONE) {
				// TODO: Support multi-packet data stages.
				usb.events.EP0DATADONE = 0;
				
				usb_rblog.log("Control data IN done, ACKing status stage.");
				
				usb.tasks.EP0STATUS = 1;
			}
			
			/*
			for(uint32_t ep = 0; ep <= 7; ep++) {
				if(usb.events.ENDEPIN[ep]) {
					usb.events.ENDEPIN[ep] = 0;
					
					ep_in_busy &= ~(1 << ep);
				}
			}
			*/
			
			if(usb.reg.EPDATASTATUS) {
				for(uint32_t ep = 1; ep <= 7; ep++) {
					if((usb.reg.EPDATASTATUS & ((1 << 16) << ep)) == 0) {
						continue;
					}
					
					usb_rblog.log("EPDATA, starting DMA on ep %d", ep);
					
					usb.reg.EPDATASTATUS = (1 << 16) << ep;
					
					usb.reg_out[ep].MAXCNT = usb.reg.SIZE_EPOUT[ep];
					usb.tasks.STARTEPOUT[ep] = 1;
				}
			}
			
			for(uint32_t ep = 0; ep <= 7; ep++) {
				if(usb.events.ENDEPOUT[ep]) {
					usb.events.ENDEPOUT[ep] = 0;
					
					handle_out(ep, usb.reg_out[ep].AMOUNT);
				}
			}
		}
		
		virtual bool ep_ready(uint32_t ep) {
			//if(usb.events.ENDEPIN[ep]) {
			//	usb.events.ENDEPIN[ep] = 0;
			//	ep_in_busy &= ~(1 << ep);
			//}
			
			if(usb.reg.EPDATASTATUS & (1 << ep)) {
				usb.reg.EPDATASTATUS = 1 << ep;
				
				ep_in_busy &= ~(1 << ep);
			}
			
			return (ep_in_busy & (1 << ep)) == 0;
		}
		
		virtual void write(uint32_t ep, uint32_t* bufp, uint32_t len) {
			usb_rblog.log("Writing, ep=%d, len=%d", ep, len);
			
			if(ep == 0 && len == 0 && (usb.reg.BMREQUESTTYPE & 0x80) == 0) {
				usb_rblog.log("EP0 status ACK");
				usb.tasks.EP0STATUS = 1;
				return;
			}
			
			memcpy(&buf_in[ep], bufp, len);
			
			usb.reg_in[ep].MAXCNT = len;
			
			usb.tasks.STARTEPIN[ep] = 1;
			
			ep_in_busy |= 1 << ep;
		}
		
		virtual uint32_t read(uint32_t ep, uint32_t* bufp, uint32_t len) {
			usb_rblog.log("Reading, ep=%d, len=%d", ep, len);
			
			if(len > usb.reg_out[ep].AMOUNT) {
				len = usb.reg_out[ep].AMOUNT;
			}
			
			memcpy(bufp, &buf_out[ep], len);
			
			return len;
		}
};

#endif