/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef ECXX_PLATFORM_DEBOUNCE_KEYPAD_H
#define ECXX_PLATFORM_DEBOUNCE_KEYPAD_H


#include <eCxx/Platform/DebounceSwitchExt.h>


namespace eCxx {


//
// An simple keypad debouncer
//
template <typename KeypadScannerT, uint8_t ModifierKeyN>
class DebounceKeypad : public Uncopyable {
    static_assert(0 <= ModifierKeyN && ModifierKeyN <= 2, "template argument substituting ModifierKeyN is not between 0 and 2");
    static_assert(KeypadScannerT::NumberOfMultiKeys > ModifierKeyN, "template argument substituting ModifierKeyN is not smaller than KeypadScannerT::NumberOfMultiKeys");

    public:
        typedef Debounce8SwitchExt::ValueType ValueType;

        static constexpr uint8_t NumberOfMultiKeys = KeypadScannerT::NumberOfMultiKeys;
        static constexpr uint8_t NumberOfStdKeys   = NumberOfMultiKeys - ModifierKeyN;
        static constexpr uint8_t NumberOfModKeys   = ModifierKeyN;

    public:
        inline ~DebounceKeypad() = default;

    public:
        inline DebounceKeypad() noexcept;
        inline DebounceKeypad(const uint8_t (&modKey)[NumberOfModKeys]) noexcept;

        inline void debounce(const uint8_t (&keyState)[NumberOfMultiKeys], uint8_t numOfCapturedKeys) noexcept;

        inline bool modKeyPressed(uint8_t keyCode) noexcept;

        inline uint8_t getStdKeyPressed(uint8_t (&keyState)[NumberOfStdKeys]) noexcept;

    private:
        Debounce8SwitchExt _db8sw;
        uint8_t            _stdKey[NumberOfStdKeys];
        uint8_t            _modKey[NumberOfModKeys];
};


template <typename KeypadScannerT, uint8_t ModifierKeyN>
inline DebounceKeypad<KeypadScannerT, ModifierKeyN>::DebounceKeypad() noexcept
{
    static_assert(ModifierKeyN == 0, "template argument substituting ModifierKeyN is not 0");

    for(uint8_t i = 0; i < NumberOfModKeys; ++i) _modKey[i] = 0;
}


template <typename KeypadScannerT, uint8_t ModifierKeyN>
inline DebounceKeypad<KeypadScannerT, ModifierKeyN>::DebounceKeypad(const uint8_t (&modKey)[NumberOfModKeys]) noexcept
{
    static_assert(ModifierKeyN != 0, "template argument substituting ModifierKeyN is 0");

    for(uint8_t i = 0; i < NumberOfModKeys; ++i) _modKey[i] = modKey[i];
}


template <typename KeypadScannerT, uint8_t ModifierKeyN>
inline void DebounceKeypad<KeypadScannerT, ModifierKeyN>::debounce(const uint8_t (&keyState)[NumberOfMultiKeys], uint8_t numOfCapturedKeys) noexcept
{
    // Bitmask for debouncing
    // 76543210
    // MMMMSSSS
    ValueType swBitMask = 0;

    // Index for storing standard keys
    uint8_t stdIdx = 0;

    // Walk trough the capture keys
    for(uint8_t k = 0; k < numOfCapturedKeys; ++k) {
        // Walk trough the predefined modifier keys
        bool modKeyCaptured = false;
        for(uint8_t m = 0; m < NumberOfModKeys; ++m) {
            // Skip if does not match a modifier key
            if(keyState[k] != _modKey[m]) continue;
            // It match a modifier key, add to the bit mask
            swBitMask |= _BV(m + 4);
            // Set flag and break the loop
            modKeyCaptured = true;
            break;
        }
        // Check if a standard key has been captured
        if(!modKeyCaptured) {
            swBitMask |= _BV(stdIdx);
            _stdKey[stdIdx++] = keyState[k];
        }
    }

    // Debounce the keys
    _db8sw.debounce(swBitMask);
}


template <typename KeypadScannerT, uint8_t ModifierKeyN>
inline bool DebounceKeypad<KeypadScannerT, ModifierKeyN>::modKeyPressed(uint8_t keyCode) noexcept
{
    // Walk trough the predefined modifier keys
    for(uint8_t m = 0; m < NumberOfModKeys; ++m) {
        // Check if the key code match with the modifier key code
        if(_modKey[m] != keyCode) continue;
        // Check if the key is down
        if( !_db8sw.down( _BV(m + 4) ) ) continue;
        return true;
    }

    // The modifier key is not pressed
    return false;
}


template <typename KeypadScannerT, uint8_t ModifierKeyN>
inline uint8_t DebounceKeypad<KeypadScannerT, ModifierKeyN>::getStdKeyPressed(uint8_t (&keyState)[NumberOfStdKeys]) noexcept
{
    // Index for storing standard keys
    uint8_t stdIdx = 0;

    // Walk trough the stored standard keys
    for(uint8_t s = 0; s < NumberOfStdKeys; ++s) {
        // Check if the key is pressed
        if( !_db8sw.pressed( _BV(s) ) ) continue;
        // Store the pressed key code
        keyState[stdIdx++] = _stdKey[s];
    }

    // Return the number of pressed key(s)
    return stdIdx;
}


} // namespace eCxx


#endif
