/*
 * Copyright (C) 2019  Max Regan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#pragma once

#include <functional>

#include "Bsp/TaskScheduler.h"
#include "Bsp/Task.h"
#include "Bsp/ReturnCode.h"

namespace BSP {

class ButtonManager : public BSP::Schedule::Task {
public:
    ButtonManager(BSP::Schedule::TaskScheduler &scheduler,
                  uint8_t up_gpio_idx,
                  uint8_t mid_gpio_idx,
                  uint8_t down_gpio_idx,
                  BSP::time_t debounce_time)
        : m_scheduler(scheduler)
        , m_buttons
        {
         button_state(up_gpio_idx, debounce_time, ButtonManager::nop_callback),
         button_state(mid_gpio_idx, debounce_time, ButtonManager::nop_callback),
         button_state(down_gpio_idx, debounce_time, ButtonManager::nop_callback),
        }
    {
        m_instance = this;
    }

    ButtonManager() = delete;

    // TODO: Make a real singleton instead
    static ButtonManager *m_instance;

    enum Button {
        UP = 0,
        MID,
        DOWN,
        Count
    };

    enum class ButtonState {
         PRESSED,
         NOT_PRESSED
    };

    using ChangeCallback = std::function<void(ButtonState)>;

    BSP::Schedule::NextTime execute() override;

    BSP::ReturnCode init();
    void set_callback(Button btn, ChangeCallback callback);
    void remove_callback(Button btn);

    static void irq();

private:

    struct button_state {

        button_state(uint8_t gpio_index,
                     BSP::time_t debounce_time,
                     ChangeCallback callback)
            : m_gpio_idx(gpio_index)
            , m_debounce_time(debounce_time)
            , m_state(ButtonState::NOT_PRESSED)
            , m_state_change_ts(0)
            , m_callback(callback)
        {}
        uint8_t const m_gpio_idx;
        BSP::time_t const m_debounce_time;

        ButtonState m_prev_call_state; /*<! The last state the button was in when it the callback was called */
        ButtonState m_state; /*<! The state the button was in during the last iteration */
        BSP::time_t m_state_change_ts; /*<! The system time when the button entered its current state */
        ChangeCallback m_callback; /*<! The callback to call when the button has changed states (post-debounce) */
    };

    BSP::Schedule::TaskScheduler &m_scheduler;
    button_state m_buttons[Button::Count];

    static void nop_callback(ButtonState) {};
};

}
