/**
 * USB Audio class interface 
 */

#include "conf_usb.h"
#include "usb_protocol.h"
#include "usb_protocol_audio.h"
#include "udd.h"
#include "udc.h"
#include "udi_audio.h"
#include <string.h>

uint32_t run_error = 0;

// Functions required / called by UDC
// Interface 1, audio control (setup only, no data endpoints)
bool udi_audio_control_enable(void);
void udi_audio_control_disable(void);
bool udi_audio_control_setup(void);
uint8_t udi_audio_control_getsetting(void);
void udi_audio_control_sof_notify(void);
UDC_DESC_STORAGE udi_api_t udi_api_audio_control = {
	.enable = udi_audio_control_enable,
	.disable = udi_audio_control_disable,
	.setup = udi_audio_control_setup,
	.getsetting = udi_audio_control_getsetting,
    .sof_notify = udi_audio_control_sof_notify
};

// Interface 2, audio streaming (single IN isochronous endpoint)
bool udi_audio_streaming_enable(void);
void udi_audio_streaming_disable(void);
bool udi_audio_streaming_setup(void);
uint8_t udi_audio_streaming_getsetting(void);
void udi_audio_streaming_sof_notify(void);
UDC_DESC_STORAGE udi_api_t udi_api_audio_streaming = {
    .enable = udi_audio_streaming_enable,
    .disable = udi_audio_streaming_disable,
    .setup = udi_audio_streaming_setup,
    .getsetting = udi_audio_streaming_getsetting,
    .sof_notify = udi_audio_streaming_sof_notify
};

//void udi_audio_streaming_start(void) ;
//void udi_audio_fill_buffer(uint16_t tone_freq);
void udi_audio_streaming_complete (udd_ep_status_t status,
		iram_size_t nb_transfered, udd_ep_id_t ep);

// Status of audio streaming interface
uint8_t udi_audio_streaming_alt_setting = 0;

// Status of streaming
uint8_t udi_audio_streaming_state = 0;

// Place in microphone buffer to pull data from
uint16_t udi_audio_streaming_offset = 0;

// Data buffer for sending to the PC
#define AUDIO_NB_SAMPLES_1MS	(UDI_AUDIO_FREQ / 1000UL)
#define AUDIO_BUFFER_SIZE_1MS (AUDIO_NB_SAMPLES_1MS*2UL) // 16kHz, so 16 samples per ms, and two bytes per sample
//#define AUDIO_BUFFER_SIZE (AUDIO_BUFFER_SIZE_1MS*20) // 20 ms of data
COMPILER_WORD_ALIGNED
uint8_t udi_streaming_buffer[USB_AUDIO_NB_CHANNELS*(AUDIO_BUFFER_SIZE_1MS+1UL)]; 
uint16_t udi_streaming_buffer_place = 0;

extern int16_t main_audio_buffer_difference; 

/** Buffer timing fiddling
 * When the source (microphone) is getting samples faster than the sink (USB streaming),
 * then the buffer difference increases. Vice versa, if the source is slower, the
 * buffer difference decreases. We don't want this difference going too high or too low
 * because then weird clipping sounds start occurring. To fix the difference, one 
 * sample will be added or subtracted from each audio packet until the buffer
 * difference gets back to the "ideal" difference. 
 */
typedef enum {
	STATE_NORMAL,
	STATE_SKIPPING,
	STATE_DOUBLING,
	STATE_STARTUP
	}udi_audio_buffer_fix_state_t;
	
static udi_audio_buffer_fix_state_t buf_fix_state;

#define MAX_BUFFER_DIFFERENCE		(AUDIO_BUFFER_SIZE_1MS*6) // don't exceed 8ms advance
#define IDEAL_BUFFER_DIFFERENCE		(AUDIO_BUFFER_SIZE_1MS*5) // hysteresis stops when we get to this difference
#define MIN_BUFFER_DIFFERENCE		(AUDIO_BUFFER_SIZE_1MS*3) // don't go below 3ms in the buffer

//uint8_t buffer_a[AUDIO_BUFFER_SIZE_1MS];
//uint8_t buffer_b[AUDIO_BUFFER_SIZE_1MS];
//uint8_t current_buffer = 0;
udi_callback audio_callback = NULL;

bool udi_audio_control_enable() {
    // TODO reset internal stuff?
    return true;
}

void udi_audio_control_disable(){
    
}

bool udi_audio_control_setup(){
    // Figure out the setup packet, do something with it.
    return true;
}

uint8_t udi_audio_control_getsetting(void) {
    return 0; // control has no alternate settings
}

void udi_audio_control_sof_notify(void) {
    // Nothing for now
}

bool udi_audio_streaming_enable() {

    // Which alternate interface settings are we?
    // If it's zero, there's no streaming selected
    // But if it's one, we can start streaming audio
    usb_iface_desc_t* cur_iface;
    cur_iface = udc_get_interface_desc();
    udi_audio_streaming_alt_setting = cur_iface->bAlternateSetting;
    
    switch(udi_audio_streaming_alt_setting) {
        case 1: 
        // Streaming enabled
        udi_audio_streaming_state = STATE_AUDIO_STREAMING;
		udi_audio_streaming_offset = 0;
		buf_fix_state = STATE_NORMAL;
		main_audio_enable(); // Clears buffer and starts PDM samples from microphone
        // Push data to the endpoint
		if(audio_callback != NULL) {
			audio_callback();
		}
        //udi_audio_streaming_start();

        break;
        default:
        // Streaming disabled (0 or other alt interface)
		main_audio_disable(); // Stops sampling microphone
        break;
    }
    return true;
}

void udi_audio_streaming_disable() {
    if(udi_audio_streaming_alt_setting == 1) {
        // Turn off streaming
        udi_audio_streaming_state = STATE_AUDIO_IDLE;
		main_audio_disable();
    }
}

bool udi_audio_streaming_setup() {
    // Get the setup packet and request, figure out what to do
    // probably a sampling frequency choice
	
	// TODO: Fill this whole thingy. Can copy from Tiny USB if needed
    return true;
}

uint8_t udi_audio_streaming_getsetting(void) {
    return udi_audio_streaming_alt_setting;
}

void udi_audio_streaming_sof_notify(void) {
    // Nothing for now
}

/*
void udi_audio_streaming_start(void) {
	// Pull data from a buffer, send to isochronous in endpoint
	irqflags_t flags = cpu_irq_save();
	int16_t* mainbuf = pdmmic_get_audio_left_buffer();
	int16_t* secondbuf = pdmmic_get_audio_right_buffer();
	uint16_t mainbufpos = get_pdm_buffer_position();
	
	uint16_t buff_difference;
	if(mainbufpos >= udi_audio_streaming_offset)  {
		buff_difference = mainbufpos - udi_audio_streaming_offset;
		} else {
		buff_difference = PCM_BUF_SIZE + mainbufpos - udi_audio_streaming_offset;
	}
	
	// Send one extra sample if we are too fast, one fewer if too slow
	uint16_t num_samples_to_send = AUDIO_NB_SAMPLES_1MS;
	if(buff_difference > AUDIO_NB_SAMPLES_1MS) {
		num_samples_to_send++;
		} else if(buff_difference < AUDIO_NB_SAMPLES_1MS) {
		num_samples_to_send--;
	}
	
	for(uint16_t i = 0; i < num_samples_to_send; i++) {
		uint16_t main_buf_place = i+udi_audio_streaming_offset;
		
		if(main_buf_place >= PCM_BUF_SIZE) {
			// wraparound case
			main_buf_place -= PCM_BUF_SIZE;
		}
		udi_streaming_buffer[4*i] = (uint8_t)(mainbuf[main_buf_place] & 0x00FF);
		udi_streaming_buffer[4*i+1] = (uint8_t)((mainbuf[main_buf_place] & 0xFF00) >> 8);
		udi_streaming_buffer[4*i+2] = (uint8_t)(secondbuf[main_buf_place] & 0x00FF);
		udi_streaming_buffer[4*i+3] = (uint8_t)((secondbuf[main_buf_place] & 0xFF00) >> 8);
	}

	main_audio_buffer_difference = buff_difference;
	cpu_irq_restore(flags);

	udi_audio_streaming_offset += buff_difference;
	if(udi_audio_streaming_offset >= PCM_BUF_SIZE) {
		udi_audio_streaming_offset -= PCM_BUF_SIZE;
	}

	if(!udd_ep_run(UDI_AUDIO_DATA_EP_IN_0,
	false,
	//&(udi_streaming_buffer[udi_streaming_buffer_place]),
	udi_streaming_buffer,
	2UL*num_samples_to_send*USB_AUDIO_NB_CHANNELS,
	udi_audio_streaming_complete
	))
	run_error++;
}
*/

void udi_audio_send(uint8_t* buf, uint32_t buflen) {
	if(!udd_ep_run(UDI_AUDIO_DATA_EP_IN_0,
	false,
	buf,
	buflen,
	udi_audio_streaming_complete))
		run_error++;
}

void udi_audio_streaming_complete (udd_ep_status_t status,
		iram_size_t nb_transfered, udd_ep_id_t ep) {

    UNUSED(nb_transfered);
    UNUSED(ep);

    if (UDD_EP_TRANSFER_OK != status) {
		// Abort transfer
		return;
	}

    // Start streaming again
    if(udi_audio_streaming_state == STATE_AUDIO_STREAMING){
		// Send notification to audio source to fill and send another packet
		if(audio_callback != NULL) {
			audio_callback();
		}
	}
	
}

void udi_audio_register_callback(udi_callback cbk) {
	audio_callback = cbk;
}