//target header
#include "../target/stm32f103xb.h"

//custom header
#include "../config.h"

//std headers
#include <stdlib.h>

//driver header
#include "io.h"

static OnIO io_cb[16]={
	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

void EXTI0_IRQHandler() {
	if (io_cb[0]) (io_cb[0])();
	EXTI->PR = 1<<0;
}

void EXTI1_IRQHandler() {
	if (io_cb[1]) (io_cb[1])();
	EXTI->PR = 1<<1;
}

void EXTI2_IRQHandler() {
	if (io_cb[2]) (io_cb[2])();
	EXTI->PR = 1<<2;
}

void EXTI3_IRQHandler() {
	if (io_cb[3]) (io_cb[3])();
	EXTI->PR = 1<<3;
}

void EXTI4_IRQHandler() {
	if (io_cb[4]) (io_cb[4])();
	EXTI->PR = 1<<4;
}

void EXTI9_5_IRQHandler() {
	if (EXTI->PR & (1<<5)) {
		if (io_cb[5]) (io_cb[5])();
		EXTI->PR = 1<<5;
	} else if (EXTI->PR & (1<<6)) {
		if (io_cb[6]) (io_cb[6])();
		EXTI->PR = 1<<6;
	} else if (EXTI->PR & (1<<7)) {
		if (io_cb[7]) (io_cb[7])();
		EXTI->PR = 1<<7;
	} else if (EXTI->PR & (1<<8)) {
		if (io_cb[8]) (io_cb[8])();
		EXTI->PR = 1<<8;
	} else if (EXTI->PR & (1<<9)) {
		if (io_cb[9]) (io_cb[9])();
		EXTI->PR = 1<<9;
	}
}

void EXTI15_10_IRQHandler() {
	if (EXTI->PR & (1<<10)) {
		if (io_cb[10]) (io_cb[10])();
		EXTI->PR = 1<<10;
	} else if (EXTI->PR & (1<<11)) {
		if (io_cb[11]) (io_cb[11])();
		EXTI->PR = 1<<11;
	} else if (EXTI->PR & (1<<12)) {
		if (io_cb[12]) (io_cb[12])();
		EXTI->PR = 1<<12;
	} else if (EXTI->PR & (1<<13)) {
		if (io_cb[13]) (io_cb[13])();
		EXTI->PR = 1<<13;
	} else if (EXTI->PR & (1<<14)) {
		if (io_cb[14]) (io_cb[14])();
		EXTI->PR = 1<<14;
	} else if (EXTI->PR & (1<<15)) {
		if (io_cb[15]) (io_cb[15])();
		EXTI->PR = 1<<15;
	}
}

/* Definitions for EXTI configuration */
enum io_exti_mask {
	SYSCFG_EXTI_PA_MASK 	= 0x0,
	SYSCFG_EXTI_PB_MASK		= 0x1,
	SYSCFG_EXTI_PC_MASK		= 0x2,
	SYSCFG_EXTI_PD_MASK		= 0x3,
	SYSCFG_EXTI_PE_MASK		= 0x4
};

int io_configure(GPIO_TypeDef *gpio, uint16_t pin, uint16_t pin_cfg, OnIO cb) {

	// enable GPIOx subsystem clocking
	if (gpio == GPIOA) RCC->APB2ENR |= 1<<2;
	else if (gpio == GPIOB) RCC->APB2ENR |= 1<<3;
	else if (gpio == GPIOC) RCC->APB2ENR |= 1<<4;
	else if (gpio == GPIOD) RCC->APB2ENR |= 1<<5;
	else if (gpio == GPIOE) RCC->APB2ENR |= 1<<6;

	// setup pin mask for crx registers
	uint64_t pin_mask = 0;
	for(int i=0; i<16; ++i) {
		if((pin >> i) & 0x1) pin_mask |= 0x1ll << 4*i;
	}

	// clear previous data
	uint64_t crx = pin_mask * 0x4; //reset value is 0x4
	uint16_t odr = pin;
	gpio->CRH &= ~(crx >> 32);
	gpio->CRL &= ~(crx & 0xFFFFFFFF);
	gpio->BSRR |= odr << 16;

	// set up the new configuration
	crx = pin_mask * (pin_cfg & 0xF);
	odr = pin * ((pin_cfg & 0x10) >> 4);
	gpio->CRH |= crx >> 32;
	gpio->CRL |= crx & 0xFFFFFFFF;
	gpio->BSRR |= odr;

	//TODO manage alternate functions
	
	// manage IRQs //TODO allow IRQ reset
	if (!cb) return 0; //no callback attached

	if (pin_cfg & 0x7) return -1; //callback set, but not in input mode

	RCC->APB2ENR |= 0x1; //enable AFIO clocking

	// prepare mask
	uint8_t port_mask = 0;
	//	uint32_t pin_mask = 0;
	if (gpio == GPIOA) port_mask = SYSCFG_EXTI_PA_MASK;
	else if (gpio == GPIOB) port_mask = SYSCFG_EXTI_PB_MASK;
	else if (gpio == GPIOC) port_mask = SYSCFG_EXTI_PC_MASK;
	else if (gpio == GPIOD) port_mask = SYSCFG_EXTI_PD_MASK;
	else if (gpio == GPIOE) port_mask = SYSCFG_EXTI_PE_MASK;

	// setup external IRQ lines
	uint64_t afio_mask;
	for(int i=0; i<4; ++i) {
		afio_mask = ((pin_mask & (0xFFFFll << (i*16))) >> (i*16)) * port_mask;
		if(afio_mask) AFIO->EXTICR[i] |= afio_mask;
	}

	// clear pending IRQs on concerned lines
	EXTI->PR |= pin;

	// configure IRQ options and masks
	EXTI->IMR |= pin;
	if(pin_cfg & 0x100) EXTI->RTSR |= pin;
	if(pin_cfg & 0x200) EXTI->FTSR |= pin;	//in case both falling and rising
											//are set, both will be a trigger

	// register IRQ callbacks
	for(int i=0; i<16; ++i) {
		if((pin >> i) & 0x1) {
			io_cb[i] = cb;

			// Setup NVIC
			switch (i) {
				case 0:
					NVIC_SetPriority(EXTI0_IRQn, EXTI0_IRQ_PRIORITY);
					NVIC_EnableIRQ(EXTI0_IRQn);
					break;
				case 1:
					NVIC_SetPriority(EXTI1_IRQn, EXTI1_IRQ_PRIORITY);
					NVIC_EnableIRQ(EXTI1_IRQn);
					break;
				case 2:
					NVIC_SetPriority(EXTI2_IRQn, EXTI2_IRQ_PRIORITY);
					NVIC_EnableIRQ(EXTI2_IRQn);
					break;
				case 3:
					NVIC_SetPriority(EXTI3_IRQn, EXTI3_IRQ_PRIORITY);
					NVIC_EnableIRQ(EXTI3_IRQn);
					break;
				case 4:
					NVIC_SetPriority(EXTI4_IRQn, EXTI4_IRQ_PRIORITY);
					NVIC_EnableIRQ(EXTI4_IRQn);
				break;
				case 5:
				case 6:
				case 7:
				case 8:
				case 9:
					NVIC_SetPriority(EXTI9_5_IRQn, EXTI9_5_IRQ_PRIORITY);
					NVIC_EnableIRQ(EXTI9_5_IRQn);
					break;
				case 10:
				case 11:
				case 12:
				case 13:
				case 14:
				case 15:
					NVIC_SetPriority(EXTI15_10_IRQn, EXTI15_10_IRQ_PRIORITY);
					NVIC_EnableIRQ(EXTI15_10_IRQn);
					break;
				default:
					return -1; //impossible to get there
			}
		}
	}
	return 0;
}


uint32_t io_read(GPIO_TypeDef *gpio, uint16_t mask)
{	
	return gpio->IDR & mask;	
}

void io_write(GPIO_TypeDef *gpio, uint16_t val, uint16_t mask)
{
	gpio->BSRR = (uint32_t)(mask) << (val ? 0 : 16);  
}

void io_write_n(GPIO_TypeDef *gpio, uint16_t val, uint16_t mask)
{
	gpio->BSRR = (uint32_t)(mask) << (val ? 16 : 0);  
}

void io_set(GPIO_TypeDef *gpio, uint16_t mask)
{
	gpio->BSRR = mask;  
}

void io_clear(GPIO_TypeDef *gpio, uint16_t mask)
{
	gpio->BSRR = (uint32_t)(mask) << 16;  
}
