#ifndef __GPIO_H__
#define __GPIO_H__

#include "types.h"
#include "base_addresses.h"
#include "register.h"

class GPIO
{
	public:

		typedef enum {
			TOTEM_POLE						= 0,
			OPEN_DRAIN						= 1
		} PinDriver;

		typedef enum {
			INPUT							= 0,
			OUTPUT							= 1,
			ALTERNATE_FUNCTION				= 2,
			ANALOG							= 3
		} PinMode;

		typedef enum {
			FLOATING						= 0,
			PULL_UP							= 1,
			PULL_DOWN						= 2
		} PinState;

		typedef enum {
			LOW_SPEED						= 0,	//   2 MHz
			MEDIUM_SPEED					= 1,	//  25 MHz
			FAST_SPEED						= 2,	//  50 MHz
			HIGH_SPEED						= 3		// 100 MHz
		} PinSpeed;

		typedef enum {
			SYSTEM							=  0,
			TIM1_TIM2						=  1,
			TIM3_TIM4_TIM5					=  2,
			TIM8_TIM9_TIM10_TIM11			=  3,
			I2C1_I2C2_I2C3					=  4,
			SPI1_SPI2_I2S_I2SEXT			=  5,
			SPI3_I2SEXT_I2C3				=  6,
			USART1_USART2_USART3_I2SEXT		=  7,
			UART4_UART5_USART6				=  8,
			CAN1_CAN2_TIM12_TIM13_TIM14		=  9,
			OTG_FS_OTG_HS					= 10,
			ETH								= 11,
			FSMC_SDIO_OTG_FS				= 12,
			DCMI							= 13,
			EVENTOUT						= 15
		} PinAlternateFunction;

		void ConfigurePin(uint32_t pinName, PinMode mode = OUTPUT, PinDriver driver = TOTEM_POLE,
				          PinState state = FLOATING, PinSpeed speed = LOW_SPEED, PinAlternateFunction af = SYSTEM ) {
			uint32_t pin  	= pinName & 0xF;

			OTYPER.BitSet(OTYPER.BitMask(driver));
			OSPEEDR.InsertBits(((pin << 1) + 1),(pin << 1), speed);
			PUPDR.InsertBits(((pin << 1) + 1),(pin << 1), state);
			if (pin < 8) {
				AFR[0].InsertBits(((pin << 2) + 3), (pin << 2), af);
			} else {
				AFR[1].InsertBits(((pin << 2) + 3), (pin << 2), af);
			}
			MODER.InsertBits(((pin << 1) + 1),(pin << 1), mode);
		}

		void Set(uint32_t pin) {
			ODR.BitSet(pin);
		}

		void Clear(uint32_t pin) {
			ODR.BitClear(pin);
		}

		uint32_t Test(uint32_t pin) const {
			return ODR.BitGet(pin);
		}

		uint32_t Get(uint32_t pin) const {
			return IDR.BitGet(pin);
		}

	private:

		Register <uint32_t> MODER;    // Port mode register,               Address offset: 0x00
		Register <uint32_t> OTYPER;   // Port output type register,        Address offset: 0x04
		Register <uint32_t> OSPEEDR;  // Port output speed register,       Address offset: 0x08
		Register <uint32_t> PUPDR;    // Port pull-up/pull-down register,  Address offset: 0x0C
		Register <uint32_t> IDR;      // Port input data register,         Address offset: 0x10
		Register <uint32_t> ODR;      // Port output data register,        Address offset: 0x14
		Register <uint16_t> BSRRL;    // Port bit set/reset low register,  Address offset: 0x18
		Register <uint16_t> BSRRH;    // Port bit set/reset high register, Address offset: 0x1A
		Register <uint32_t> LCKR;     // Port configuration lock register, Address offset: 0x1C
		Register <uint32_t> AFR[2];   // Alternate function registers,     Address offset: 0x20-0x27
		Register <uint32_t> PAD[246]; // Fill until 0x400 bytes in size    Address offset: 0x28
};

class GPIOA : public GPIO
{
	public:
		enum {address = GPIOA_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOB: public GPIO
{
	public:
		enum { address = GPIOB_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOC: public GPIO
{
	public:
		enum {address = GPIOC_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOD : public GPIO
{
	public:
		enum { address = GPIOD_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOE : public GPIO
{
	public:
		enum { address = GPIOE_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOF : public GPIO
{
	public:
		enum {address = GPIOF_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOG : public GPIO
{
	public:
		enum {address = GPIOG_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOH : public GPIO
{
	public:
		enum {address = GPIOH_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

class GPIOI : public GPIO
{
	public:
		enum {address = GPIOI_BASE };
		void *operator new(size_t) { return reinterpret_cast<void *> (address); }
};

#endif // __GPIO_H__
