#include <string.h>
#include <unicore-mx/stm32/rcc.h>
#include <unicore-mx/stm32/gpio.h>
#include <unicore-mx/stm32/timer.h>
#include <unicore-mx/cm3/nvic.h>
#include <unicore-mx/stm32/exti.h>
#include <unicore-mx/stm32/dma.h>

#include "led.h"
#include "usart.h"

void dma_setup(void);
void timer_setup(void);

#define DMABUFSIZE 16
uint16_t dmabuf[DMABUFSIZE], dmabuf2[DMABUFSIZE], timbuf[DMABUFSIZE], timbuf2[DMABUFSIZE];
void dma_setup(void) {
	// Fill them in, to check if anything is being written at all
	memset(dmabuf, 0xdd, sizeof(dmabuf));
	memset(dmabuf2, 0xcc, sizeof(dmabuf2));
	memset(timbuf, 0xbb, sizeof(timbuf));
	memset(timbuf2, 0xaa, sizeof(timbuf2));

	rcc_periph_clock_enable(RCC_DMA2);
	nvic_enable_irq(NVIC_DMA2_STREAM2_IRQ);

	DMA_SPAR(DMA2, DMA_STREAM2) = &GPIOD_IDR;  // Read 16 bits from GPIO D
	DMA_SM0AR(DMA2, DMA_STREAM2) = dmabuf;
	DMA_SM1AR(DMA2, DMA_STREAM2) = dmabuf2;    // Double buffering
	DMA_SNDTR(DMA2, DMA_STREAM2) = DMABUFSIZE; // Set number of words to read
	DMA_SCR(DMA2, DMA_STREAM2) = 0
		| DMA_SxCR_CHSEL(6)                    // Stream 2, Channel 6 = TIM1_CH2
		| DMA_SxCR_DBM                         // Enable double buffering
		| DMA_SxCR_PL_VERY_HIGH                // Highest priority
		| DMA_SxCR_PSIZE_16BIT                 // Read 16 bits, not 8
		| DMA_SxCR_MSIZE_16BIT                 // Increment 16 bits
		| DMA_SxCR_MINC                        // Increment memory location
		| DMA_SxCR_CIRC                        // Circular mode
		| DMA_SxCR_DIR_PERIPHERAL_TO_MEM
		| DMA_SxCR_TCIE                        // Enable Transfer Complete Interrupt
		| DMA_SxCR_EN
		; // Enable DMA

	// DMA_SPAR(DMA2, DMA_STREAM6) = &TIM2_CNT;
	// DMA_SM0AR(DMA2, DMA_STREAM6) = timbuf;
	// DMA_SM1AR(DMA2, DMA_STREAM6) = timbuf2;
	// DMA_SNDTR(DMA2, DMA_STREAM6) = DMABUFSIZE;
	// DMA_SCR(DMA2, DMA_STREAM6) =
	// 	DMA_SxCR_CHSEL(0) |
	// 	DMA_SxCR_DBM |
	// 	DMA_SxCR_PBURST_SINGLE |
	// 	DMA_SxCR_MBURST_SINGLE |
	// 	DMA_SxCR_PL_HIGH |
	// 	DMA_SxCR_PSIZE_16BIT |
	// 	DMA_SxCR_MSIZE_16BIT |
	// 	DMA_SxCR_MINC |
	// 	DMA_SxCR_CIRC |
	// 	DMA_SxCR_DIR_PERIPHERAL_TO_MEM |
	// 	DMA_SxCR_EN;

	nvic_enable_irq(NVIC_TIM1_CC_IRQ);
	rcc_periph_clock_enable(RCC_TIM1);
	rcc_periph_clock_enable(RCC_GPIOE);
	gpio_mode_setup(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO11);
	gpio_set_af(GPIOE, GPIO_AF1, GPIO11); // PE11 Alternate Function 1 = TIM1_CH2

	// Setup Timer 1 capture and compare mode for channel 2
	// It is used to trigger DMA above
	TIM1_CCMR1 = TIM_CCMR1_CC2S_IN_TI2;
	TIM1_CCER = TIM_CCER_CC2E | TIM_CCER_CC2P;
	TIM1_DIER = TIM_DIER_CC2DE;
	TIM1_SMCR = TIM_SMCR_SMS_ECM1 | TIM_SMCR_TS_TI2FP2;
	TIM1_CR1 = TIM_CR1_CEN;
}

void dma2_stream2_isr(void) {
	if(dma_get_interrupt_flag(DMA2, DMA_STREAM2, DMA_TCIF)) {
		dma_clear_interrupt_flags(DMA2, DMA_STREAM2, DMA_TCIF);

		uint16_t *readybuf = (DMA_SCR(DMA2, DMA_STREAM2) & DMA_SxCR_CT) ? dmabuf2 : dmabuf;
//		uint16_t *timerbuf = (DMA_SCR(DMA2, DMA_STREAM2) & DMA_SxCR_CT) ? timbuf2 : timbuf;

		gpio_set(GPIOC, GPIO7);
//		static uint16_t prevtim;
		uint16_t w = 0xeeee;                // marker
		_write(1, (char *)&w, 2);  // send marker
		for(int i = 0; i < DMABUFSIZE; i++) {
//			if((readybuf[i] & GPIO13) == 0) {
//				uint16_t tim = timerbuf[i];
//				w = tim - prevtim;
//				prevtim = tim;
//				_write(1, (char *)&w, 2);  // send samples
				_write(1, (char *)&readybuf[i], 2);  // send data
//			}
		}
		gpio_clear(GPIOC, GPIO7);
	}
}

void timer_setup(void) {
	rcc_periph_clock_enable(RCC_TIM2);
//	nvic_enable_irq(NVIC_TIM2_IRQ);
	timer_reset(TIM2);
	TIM2_ARR = 65535;
	TIM2_PSC = 3809;
//	TIM1_DIER = TIM_DIER_UIE;
	TIM2_CR1 = TIM_CR1_CEN;
}

int main(void) {
	rcc_clock_setup_hse_3v3(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_168MHZ]);

	led_setup();
	usart_setup();

	// The data read port
	// D0-D7, A0-A3, /RD, /CS
	rcc_periph_clock_enable(RCC_GPIOD);
	gpio_mode_setup(GPIOD, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | GPIO5 | GPIO6 | GPIO7 | GPIO8 | GPIO9 | GPIO10 | GPIO11 | GPIO12 | GPIO15);
	gpio_mode_setup(GPIOD, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO13 | GPIO14);

	// Just some status pin to probe with the scope
	rcc_periph_clock_enable(RCC_GPIOC);
	gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO7 | GPIO8);
	gpio_set_output_options(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_100MHZ, GPIO7 | GPIO8);

	dma_setup();
	timer_setup();

	while(1) {
		usart_poll();
	}
}
