/* Copyright (c) 2015 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

#include "app_gpiote.h"
#include "nrf_drv_gpiote.h"
#include "sdk_common.h"


/**@brief GPIOTE user type. */
typedef struct
{
    uint32_t                   pins_mask;             /**< Mask defining which pins user wants to monitor. */
    uint32_t                   pins_low_to_high_mask; /**< Mask defining which pins will generate events to this user when toggling low->high. */
    uint32_t                   pins_high_to_low_mask; /**< Mask defining which pins will generate events to this user when toggling high->low. */
    uint32_t                   sense_high_pins;       /**< Mask defining which pins are configured to generate GPIOTE interrupt on transition to high level. */
    app_gpiote_event_handler_t event_handler;         /**< Pointer to function to be executed when an event occurs. */
    bool                       enabled;               /**< Flag indicating whether user is enabled. */
} gpiote_user_t;

STATIC_ASSERT(sizeof(gpiote_user_t) <= GPIOTE_USER_NODE_SIZE);
STATIC_ASSERT(sizeof(gpiote_user_t) % 4 == 0);

static uint8_t         m_user_array_size;             /**< Size of user array. */
static uint8_t         m_user_count;                  /**< Number of registered users. */
static gpiote_user_t * mp_users = NULL;               /**< Array of GPIOTE users. */
static uint32_t        m_pins;                        /**< Mask of initialized pins. */
static uint32_t        m_last_pins_state;             /**< Most recent state of pins. */

#define MODULE_INITIALIZED (mp_users != NULL)
#include "sdk_macros.h"

void gpiote_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    int i;
    uint32_t pin_mask = 1 << pin;
    bool hitolo = (m_last_pins_state & pin_mask) ? true : false;
    m_last_pins_state = nrf_gpio_pins_read();

    for (i = 0; i < m_user_count; i++)
    {
        if (mp_users[i].enabled && (pin_mask & mp_users[i].pins_mask))
        {
            if ((pin_mask & mp_users[i].pins_high_to_low_mask) && hitolo)
            {
                mp_users[i].event_handler(0,pin_mask);
            }
            else if ((pin_mask & mp_users[i].pins_low_to_high_mask) && !hitolo)
            {
                mp_users[i].event_handler(pin_mask,0);
            }
        }
    }
}

uint32_t app_gpiote_init(uint8_t max_users, void * p_buffer)
{
    uint32_t ret_code = NRF_SUCCESS;

    if (p_buffer == NULL)
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    // Check that buffer is correctly aligned.
    if (!is_word_aligned(p_buffer))
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    // Initialize file globals.
    mp_users             = (gpiote_user_t *)p_buffer;
    m_user_array_size    = max_users;
    m_user_count         = 0;
    m_pins              = 0;

    memset(mp_users, 0, m_user_array_size * sizeof(gpiote_user_t));

    if (nrf_drv_gpiote_is_init()==false)
    {
        ret_code = nrf_drv_gpiote_init();
    }

    return ret_code;
}

uint32_t app_gpiote_user_register(app_gpiote_user_id_t     * p_user_id,
                                  uint32_t                   pins_low_to_high_mask,
                                  uint32_t                   pins_high_to_low_mask,
                                  app_gpiote_event_handler_t event_handler)
{
    uint32_t user_pin_mask;
    uint32_t ret_val = NRF_SUCCESS;

    // Check state and parameters.
    VERIFY_MODULE_INITIALIZED();

    if (event_handler == NULL)
    {
        return NRF_ERROR_INVALID_PARAM;
    }
    if (m_user_count >= m_user_array_size)
    {
        return NRF_ERROR_NO_MEM;
    }

    user_pin_mask = pins_low_to_high_mask | pins_high_to_low_mask;
    // Allocate new user.
    mp_users[m_user_count].pins_mask             = user_pin_mask;
    mp_users[m_user_count].pins_low_to_high_mask = pins_low_to_high_mask;
    mp_users[m_user_count].pins_high_to_low_mask = pins_high_to_low_mask;
    mp_users[m_user_count].event_handler         = event_handler;
    mp_users[m_user_count].enabled               = false;

    *p_user_id = m_user_count++;

    uint32_t mask = 1;
    uint32_t i;
    const nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);
    for (i = 0; i < 32; i++)
    {
        if ((mask & user_pin_mask) & ~m_pins)
        {
            ret_val = nrf_drv_gpiote_in_init(i, &config, gpiote_handler);
            VERIFY_SUCCESS(ret_val);
            m_pins |= mask;
        }
        mask <<= 1;
    }
    return ret_val;
}

__STATIC_INLINE uint32_t error_check(app_gpiote_user_id_t user_id)
{
    // Check state and parameters.
    VERIFY_MODULE_INITIALIZED();

    if (user_id >= m_user_count)
    {
        return NRF_ERROR_INVALID_PARAM;
    }
    return NRF_SUCCESS;
}
/**
 * @brief Function for enabling event on pin (if not yet enabled) or disabling the event if no other
 *        user requires it.
 *
 * @param pin    Pin to enable
 * @param enable If true function will attempt to enable the pin else it will attempt to disable it.
 */
static void pin_event_enable(uint32_t pin, bool enable)
{
    uint32_t i;
    uint32_t pin_mask = 1UL << pin;
    bool enabled = false;
    //search if any user already enabled given pin
    for (i = 0; i < m_user_count; i++)
    {
        if (mp_users[i].enabled && (mp_users[i].pins_mask & pin_mask))
        {
            enabled = true;
            break;
        }
    }
    if (!enabled)
    {
        if (enable)
        {
            m_last_pins_state = nrf_gpio_pins_read();
            nrf_drv_gpiote_in_event_enable(pin, true);
        }
        else
        {
            nrf_drv_gpiote_in_event_disable(pin);
        }
    }
}

/**
 * @brief Function for enabling or disabling events for pins used by the user.
 *
 * Function will enable pin events only if they are not yet enabled. Function will disable pin
 * events only if there is no other enabled user that is using them.
 *
 * @param user_id  User id.
 * @param enable   If true function will attempt to enable the pin else it will attempt to disable it.
 */
static uint32_t user_enable(app_gpiote_user_id_t user_id, bool enable)
{
    uint32_t ret_code = error_check(user_id);

    if (ret_code == NRF_SUCCESS)
    {
        uint32_t i;
        uint32_t mask = 1UL;
        for (i = 0; i < 32; i++)
        {
            if (mp_users[user_id].pins_mask & mask)
            {
                pin_event_enable(i, enable);
            }
            mask <<= 1;
        }
    }
    return ret_code;
}

uint32_t app_gpiote_user_enable(app_gpiote_user_id_t user_id)
{
    uint32_t ret_code = NRF_SUCCESS;

    if (mp_users[user_id].enabled == false)
    {
        ret_code = user_enable(user_id, true);
        VERIFY_SUCCESS(ret_code);

        mp_users[user_id].enabled = true;
        return ret_code;
    }
    else
    {
        return ret_code;
    }
}

uint32_t app_gpiote_user_disable(app_gpiote_user_id_t user_id)
{
    uint32_t ret_code = NRF_SUCCESS;

    if (mp_users[user_id].enabled)
    {
        mp_users[user_id].enabled = false;
        ret_code = user_enable(user_id, false);
    }

    return ret_code;
}

uint32_t app_gpiote_pins_state_get(app_gpiote_user_id_t user_id, uint32_t * p_pins)
{
    gpiote_user_t * p_user;
    uint32_t ret_code = error_check(user_id);

    if (ret_code == NRF_SUCCESS)
    {
        p_user  = &mp_users[user_id];
        // Get pins.
        *p_pins = nrf_gpio_pins_read() & p_user->pins_mask;

    }
    return ret_code;
}
