#ifndef __STM_PIN_HPP__
#define __STM_PIN_HPP__

//#include "../../interfaces/pin.hpp" 
//#include "../Drivers/CMSIS/Device/ST/STM32F0xx/Include/stm32f042x6.h"
#include <array>
#include <stdint.h>

#define MODER_IN		0x0UL		
#define MODER_OUT		0x1UL		
#define MODER_ALTERNATE	0x2UL		
#define MODER_ANALOG	0x3UL	

#define OSPEEDR_LOW		0x0UL
#define OSPEEDR_MEDIUM	0x1UL
#define OSPEEDR_HIGH	0x3UL

#define PUPDR_NO_PULL	0x0UL
#define PUPDR_PULL_UP	0x1UL
#define PUPDR_PULL_DOWN	0x2UL

// https://accu.org/journals/overload/13/68/goodliffe_281/

#define LQFP32 // TODO : If that makes sens it will be implmendet on the Cmake.

// How can we check the assert for each port ?
#ifdef LQFP32

#define PORT_A_PIN_COUNT 15
#define PORT_B_PIN_COUNT 8
#define PORT_C_PIN_COUNT 0
#define PORT_F_PIN_COUNT 2


// 0b1111 1111 Means all the piuns are present it's to de bale to make an & comprison
#define PORT_A_PIN_MISSING_MSK 0xFF
#define PORT_B_PIN_MISSING_MSK 0xFB
#define PORT_C_PIN_MISSING_MSK 0x00
#define PORT_F_PIN_MISSING_MSK 0xFC

#endif


enum GPIO_Port {
    Port_A_base_address = GPIOA_BASE,	// 0x4800'0000
    Port_B_base_address = GPIOB_BASE,	// 0x4800'0400
    Port_C_base_address = GPIOC_BASE,	// 0x4800'0800
    Port_F_base_address = GPIOF_BASE	// 0x4800'1400
};

enum Pin_no{
		pin_0,
		pin_1,
		pin_2,
		pin_3,
		pin_4,
		pin_5,
		pin_6,
		pin_8,
		pin_9,
		pin_10,
		pin_11,
		pin_12,
		pin_13,
		pin_14,
		pin_15
};

template <Pin_no pin_no, GPIO_Port port_base_address>
class STM_Pin : Pin
{
    public:

        STM_Pin() {
				static_assert(pin_no < PORT_A_PIN_COUNT, "GPIO has only 14 ports!");
        }
		
        void setMode(mode mode) override
        {
            switch (mode)
            {
            case undefined:
                return;
					// TODO : Implement some sort of error	
                break;
            case input:
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->MODER &= ~(MODER_IN << (pin_no * 2));
                return; 
            case output:
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->MODER |= (MODER_OUT << (pin_no * 2));
                return;
			case analog:
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->MODER |= (MODER_ANALOG << (pin_no * 2));                
                return;
			case alternate:
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->MODER |= (MODER_ALTERNATE << (pin_no * 2));
                return;
            default:
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->MODER |= (MODER_ANALOG << (pin_no * 2));                
                break;
            };            
        }

		void setOutputState(state state) override
        {
            // TODO: test it!
            switch (state)
            {
            case openDrain:
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->OTYPER |= (1UL << pin_no);
                return;
                break;
            case pushPull:
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->OTYPER &= ~(1UL << pin_no);
                return;
                break;
            case floating:
                break;
            default:
                break;
            };
        }

		void setPullUpDonw(pullUpDown resistance) override
        {
           switch(resistance) {
               case none:
                    reinterpret_cast<GPIO_TypeDef*>(port_base_address)->PUPDR &= ~(PUPDR_NO_PULL << (2 * pin_no));
                    return;
                case pullUp:
                    reinterpret_cast<GPIO_TypeDef*>(port_base_address)->PUPDR |= (PUPDR_PULL_UP << (2 * pin_no));
                    return;
                case pullDown:
                    reinterpret_cast<GPIO_TypeDef*>(port_base_address)->PUPDR |= (PUPDR_PULL_DOWN << (2 * pin_no));
                    return;
                default:
                    break;
           };
			            
        }

		void setSpeed(speed speed) override
        {
           //reinterpret_cast<GPIO_TypeDef*>(port_base_address)->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEEDR0_Msk << (2 * pin_no));
           switch(speed) {
               case slow:
                    reinterpret_cast<GPIO_TypeDef*>(port_base_address)->OSPEEDR &= ~(OSPEEDR_LOW << (2 * pin_no));
                    return;
                case normal:
                    reinterpret_cast<GPIO_TypeDef*>(port_base_address)->OSPEEDR |= (OSPEEDR_MEDIUM << (2 * pin_no));
                    return;
                case fast:
                    reinterpret_cast<GPIO_TypeDef*>(port_base_address)->OSPEEDR |= (OSPEEDR_HIGH << (2 * pin_no));
                    return;
                case veryFast:
                    return;
                default:
                    break;
           };
        }

		void config(mode mode, state state, pullUpDown resistance, speed speed) override
        {
            
        }
		
		bool read() override
        {
			 return (reinterpret_cast<GPIO_TypeDef*>(port_base_address)->IDR & (1<<pin_no)) >> pin_no;
		}

		void toggle() override
        {
            
        }

		void write(bool state) override
        {

            if(state) {
                reinterpret_cast<GPIO_TypeDef*>(port_base_address)->BSRR |= (GPIO_BSRR_BS_0 << pin_no);
                return;
            }
            reinterpret_cast<GPIO_TypeDef*>(port_base_address)->BSRR |= (GPIO_BSRR_BR_0<< pin_no);
        }

		void init() override
        {
            // TODO: find better way
            switch (port_base_address)
            {
            case GPIOA_BASE:
                RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
                return;
                break;
            case GPIOB_BASE:
                RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
                return;
                break;
            case GPIOC_BASE:
                RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
                return;
                break;
            case GPIOF_BASE:
                RCC->AHBENR |= RCC_AHBENR_GPIOFEN;
                return;
                break;           
            default:
                break;
            };
        }

		void deInit() override
        {
            
        }
		
		void hardwareInfo() override
        {
            
        }

	private:

        void throwError(uint16_t line, errors errNo) override
        {
            
        }
};

#endif //__STM_PIN_HPP__
