
#include "Bsp/Drivers/GpioDriver.h"
#include "Bsp/macros.h"

namespace BSP {

    /*********************************************
     *
     * GpioPin
     *
     *********************************************/

    GpioDriver::GpioDriver(GPIO_TypeDef *gpio)
        : m_gpio(gpio)
    {}

    void GpioDriver::init() {
        return;
    }

    void GpioDriver::enable() {
#if defined(STM32L0XX)
        if (m_gpio == GPIOA) {
            SET(RCC->IOPENR, RCC_IOPENR_IOPAEN);
        } else if (m_gpio == GPIOB) {
            SET(RCC->IOPENR, RCC_IOPENR_IOPBEN);
        } else if (m_gpio == GPIOC) {
            SET(RCC->IOPENR, RCC_IOPENR_IOPCEN);
        } else if (m_gpio == GPIOH) {
            SET(RCC->IOPENR, RCC_IOPENR_IOPHEN);
        }
#elif defined(STM32L4XX)
        if (m_gpio == GPIOA) {
            SET(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
        } else if (m_gpio == GPIOB) {
            SET(RCC->AHB2ENR, RCC_AHB2ENR_GPIOBEN);
        } else if (m_gpio == GPIOC) {
            SET(RCC->AHB2ENR, RCC_AHB2ENR_GPIOCEN);
        } else if (m_gpio == GPIOH) {
            SET(RCC->AHB2ENR, RCC_AHB2ENR_GPIOHEN);
        }
#else
#error "Unknown device family"
#endif
    }

    void GpioDriver::disable() {
#if defined(STM32L0XX)
        if (m_gpio == GPIOA) {
            CLR(RCC->IOPENR, RCC_IOPENR_IOPAEN);
        } else if (m_gpio == GPIOB) {
            CLR(RCC->IOPENR, RCC_IOPENR_IOPBEN);
        } else if (m_gpio == GPIOC) {
            CLR(RCC->IOPENR, RCC_IOPENR_IOPCEN);
        } else if (m_gpio == GPIOH) {
            CLR(RCC->IOPENR, RCC_IOPENR_IOPHEN);
        }
#elif defined(STM32L4XX)
        if (m_gpio == GPIOA) {
            CLR(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
        } else if (m_gpio == GPIOB) {
            CLR(RCC->AHB2ENR, RCC_AHB2ENR_GPIOBEN);
        } else if (m_gpio == GPIOC) {
            CLR(RCC->AHB2ENR, RCC_AHB2ENR_GPIOCEN);
        } else if (m_gpio == GPIOH) {
            CLR(RCC->AHB2ENR, RCC_AHB2ENR_GPIOHEN);
        }
#else
#error "Unknown device family"
#endif
    }

    void GpioDriver::set_pin_moder(uint32_t index, moder_t mode) {
        uint32_t moder_value = 0;
        switch (mode) {
        case moder_t::INPUT:
            moder_value = 0;
            break;
        case moder_t::OUTPUT:
            moder_value = 1;
            break;
        case moder_t::ALTERNATE_FUNCTION:
            moder_value = 2;
            break;
        }
        SET_STRIDE_TO(m_gpio->MODER, 2, index, moder_value);
    }
    
    void GpioDriver::set_pin_pupdr(uint32_t index, input_pull_t pull_mode) {
        uint32_t pupdr_value = 0;
        switch (pull_mode) {
        case input_pull_t::FLOATING:
            pupdr_value = 0;
            break;
        case input_pull_t::PULL_UP:
            pupdr_value = 1;
            break;
        case input_pull_t::PULL_DOWN:
            pupdr_value = 2;
            break;
        }
        SET_STRIDE_TO(m_gpio->PUPDR, 2, index, pupdr_value);
    }

    void GpioDriver::set_pin_otyper(uint32_t index, output_mode_t mode) {
        uint32_t mask = 1 << index;
        if (mode == output_mode_t::OPEN_DRAIN) {
            SET(m_gpio->OTYPER, mask);
        } else {
            CLR(m_gpio->OTYPER, mask);
        }
    }

    void GpioDriver::set_pin_ospeedr(uint32_t index, output_speed_t speed) {
        uint32_t speed_val = 0;

        if (speed == output_speed_t::LOW) {
            speed_val = 0;
        } else if (speed == output_speed_t::MEDIUM) {
            speed_val = 1;
        } else if (speed == output_speed_t::HIGH) {
            speed_val = 2;
        } else if (speed == output_speed_t::VERY_HIGH) {
            speed_val = 3;
        }

        SET_STRIDE_TO(m_gpio->OSPEEDR, 2, index, speed_val);
    }

    void GpioDriver::configure_pin_input(uint32_t index, input_pull_t pull_mode) {
        if (index > 15) {
            return;
        }

        set_pin_pupdr(index, pull_mode);
        set_pin_moder(index, moder_t::INPUT);
    }

    void GpioDriver::configure_pin_output(uint32_t index, output_mode_t mode, output_speed_t speed) {
        if (index > 15) {
            return;
        }

        set_pin_moder(index, moder_t::INPUT); // Avoid glitches during configuration
        set_pin_otyper(index, mode);
        set_pin_ospeedr(index, speed);
        set_pin_moder(index, moder_t::OUTPUT);
    }

    void GpioDriver::configure_pin_alternate_function(uint32_t index, uint32_t function) {
        if (index > 15) {
            return;
        }
        if (function > 7) {
            return;
        }

        set_pin_moder(index, moder_t::INPUT); // Avoid glitches during configuration
        if (index < 8) {
            SET_STRIDE_TO(m_gpio->AFR[0], 4, index, function);
        } else {
            SET_STRIDE_TO(m_gpio->AFR[1], 4, index - 8, function);
        }
        set_pin_moder(index, moder_t::ALTERNATE_FUNCTION);
    }

    bool GpioDriver::read_pin(uint32_t index) {
        return (m_gpio->IDR >> index) & 1;
    }

    void GpioDriver::write_pin(uint32_t index, bool value) {
        SET_STRIDE_TO(m_gpio->ODR, 1, index, value);
    }

    /*********************************************
     *
     * GpioPin
     *
     *********************************************/

    GpioPin::GpioPin(GpioDriver &driver, uint32_t index)
        : m_gpio(driver)
        , m_index(index)
    {}

    void GpioPin::configure_input(GpioDriver::input_pull_t pull_mode) {
        m_gpio.configure_pin_input(m_index, pull_mode);
    }

    void GpioPin::configure_output(GpioDriver::output_mode_t mode, GpioDriver::output_speed_t speed) {
        m_gpio.configure_pin_output(m_index, mode, speed);
    }

    void GpioPin::configure_alternate_function(uint32_t function) {
        m_gpio.configure_pin_alternate_function(m_index, function);
    }

    bool GpioPin::read() {
        return m_gpio.read_pin(m_index);
    }

    void GpioPin::write(bool val) {
        return m_gpio.write_pin(m_index, val);
    }

    uint32_t GpioPin::get_index() {
        return m_index;
    }

    GpioDriver& GpioPin::get_driver() {
        return m_gpio;
    }


}
