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

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

#include "platform/nrf52/gpiote.h"
#include "platform/nrf52/gpio.h"

using namespace pinetime::platform::nrf52;

Gpio::Gpio()
    : pin(0)
    , blocking(false)
    , wait_for_event(false)
{
}

Gpio::Gpio(uint32_t pin)
    : pin(pin)
    , blocking(false)
    , wait_for_event(false)
{
    this->set_direction(direction::OUT);
    this->clear();
}

void Gpio::set_direction(direction dir, bool blocking_read)
{
    uint32_t direct = GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos;
    uint32_t input = GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos;
    uint32_t pull = GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos;
    uint32_t drive = GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos;
    uint32_t sense = GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos;
    if(dir == direction::IN) {
        direct = GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos;
        input = GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos;

        // FIXME: Make this configurable
        // GPIO_PIN_CNF_PULL_Disabled, ///<  Pin pull-up resistor disabled.
        // GPIO_PIN_CNF_PULL_Pulldown, ///<  Pin pull-down resistor enabled.
        // GPIO_PIN_CNF_PULL_Pullup,   ///<  Pin pull-up resistor enabled.
        pull = GPIO_PIN_CNF_PULL_Pullup  << GPIO_PIN_CNF_PULL_Pos;
        drive = GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos;
        sense = GPIO_PIN_CNF_SENSE_Low << GPIO_PIN_CNF_SENSE_Pos;

        gpiote.instance().register_handler(this);

        this->blocking = blocking_read;

        gpiote.instance().enable();
    }
    GPIO_REGS->PIN_CNF[pin] = direct | input | pull | drive | sense;
}

void Gpio::pin_number(uint32_t pin)
{
    this->pin = pin;
}

uint32_t Gpio::pin_number()
{
    return this->pin;
}

uint32_t Gpio::get()
{
    if(this->blocking) {
        this->wait_for_event = true;
        while(this->wait_for_event == true) {
            // FIXME: Low Power
            asm volatile("nop");
        }
    }
    uint32_t res = (GPIO_REGS->IN  >> pin) & 1UL;

    return res;
}

void Gpio::set()
{
    GPIO_REGS->OUTSET = 1UL << (this->pin);
}

void Gpio::clear()
{
    GPIO_REGS->OUTCLR = 1UL << (this->pin);
}

void Gpio::toggle()
{
    uint32_t state = GPIO_REGS->OUT;

    GPIO_REGS->OUTSET = (~state & (1UL << (this->pin)));
    GPIO_REGS->OUTCLR = (state & (1UL << (this->pin)));
}

void Gpio::handle()
{
    this->wait_for_event = false;
}
