#include <cassert>

#include "platform/nrf52/InterruptHandler.h"
#include "platform/nrf52/InterruptGuardian.h"

#include "platform/nrf52/gpiote.h"

extern "C" {
    #include "nrf52.h"
    #include "nrf52_bitfields.h"

    NRF_GPIOTE_Type *const GPIOTE_REGS = reinterpret_cast<NRF_GPIOTE_Type *>(NRF_GPIOTE_BASE);
}

using namespace pinetime::platform::nrf52;

Gpiote::Gpiote()
    : InterruptHandler(InterruptGuardian::Nrf52IrqN::GPIOTE_IRQ)
    , num_registered(0)
{
    for(auto it = this->gpiote_channels.begin(); it != this->gpiote_channels.end(); ++it) {
        *it = nullptr;
    }
}

void Gpiote::enable()
{
    NVIC_SetPriority(GPIOTE_IRQn, 7);
    NVIC_EnableIRQ(GPIOTE_IRQn);
}

void Gpiote::disable()
{
    NVIC_DisableIRQ(GPIOTE_IRQn);
}

void Gpiote::register_handler(pinetime::interfaces::GpioInterface *gpio)
{
    if(num_registered == GPIOTE_CHANNELS) {
        //FIXME: Error notification
        return;
    }

    if(num_registered == 0) {
        this->enable();
    } else {
        //check if gpio already registered
        for(auto it = this->gpiote_channels.begin(); it != this->gpiote_channels.end(); ++it) {
            if(*it == gpio) {
                return;
            }
        }
    }
    uint32_t i = 0;
    for(auto it = this->gpiote_channels.begin(); it != this->gpiote_channels.end(); ++it) {
        if(*it == nullptr) {
            *it = gpio;
            GPIOTE_REGS->INTENSET = GPIOTE_INTENSET_IN0_Msk;
            GPIOTE_REGS->CONFIG[i] = (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
                                   | ((gpio->pin_number()) << GPIOTE_CONFIG_PSEL_Pos)
                                   | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
        }
        i++;
    }
}

void Gpiote::handle()
{
    for(uint32_t i = 0; i < GPIOTE_CHANNELS; ++i) {
        if(GPIOTE_REGS->EVENTS_IN[i] != 0) {
            GPIOTE_REGS->EVENTS_IN[i] = 0;
            pinetime::interfaces::GpioInterface *h = this->gpiote_channels[i];
            assert(h != nullptr);
            h->handle();
        }
    }
}
