/*
 * 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_SWITCH_EXT_H
#define ECXX_PLATFORM_DEBOUNCE_SWITCH_EXT_H


#include <eCxx/CTL/Types.h>


namespace eCxx {


//
// An extended switch debouncer
//
// Based on https://www.avrfreaks.net/forum/switch-debounce-code
//
// # Each instance of this class can debounce up to N switches.
// # The switches are assumed to be active high.
//
// # Each bit on the 'swBitMask' represents one switch.
// # To debounce multiple switches, pass the OR-ed 'swBitMask' of all the switches to the debounce() function.
// # To check the state of a switch, pass only the switch's 'swBitMask' to the pressed() et al. functions.
//
// # The debounce() function must be called regularly, let us say every a few milliseconds.
// # If the debounce() function is called by an ISR, then the pressed() et al. function must be called using
//   an atomic block.
//
template <typename ValueT>
class BasicDebounceSwitchExt : public Uncopyable {
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");
    static_assert(eCxx::isUnsigned<ValueT>::Value, "template argument substituting ValueT is not an unsigned integral");

    public:
        typedef ValueT ValueType;

    public:
        inline ~BasicDebounceSwitchExt() = default;

    public:
        inline BasicDebounceSwitchExt() noexcept;

        inline void debounce(ValueType swBitMask) noexcept;

        inline bool pressed(ValueType swBitMask) noexcept;
        inline bool released(ValueType swBitMask) noexcept;

        inline bool up(ValueType swBitMask) const noexcept;
        inline bool down(ValueType swBitMask) const noexcept;

    private:
        ValueType _swState;
        ValueType _swPress;
        ValueType _swRelease;
        ValueType _ct0;
        ValueType _ct1;
};


template <typename ValueT>
inline BasicDebounceSwitchExt<ValueT>::BasicDebounceSwitchExt() noexcept
: _swState  (0)
, _swPress  (0)
, _swRelease(0)
, _ct0      (0)
, _ct1      (0)
{}


template <typename ValueT>
inline void BasicDebounceSwitchExt<ValueT>::debounce(ValueType swBitMask) noexcept
{
    ValueType kch = _swState ^ swBitMask; // Detect state change

    _ct0          =       ~(_ct0 & kch);  // Reset or count _ct0
    _ct1          = _ct0 ^ (_ct1 & kch);  // Reset or count _ct1
    kch          &= _ct0 & _ct1;          // Count until roll over

    _swState     ^= kch;                  // Toggle debounced state

    _swPress     |=  _swState & kch;      // Detect switch press
    _swRelease   |= ~_swState & kch;      // Detect switch release
}


template <typename ValueT>
inline bool BasicDebounceSwitchExt<ValueT>::pressed(ValueType swBitMask) noexcept
{
    swBitMask &= _swPress;  // Read  switch(es)
    _swPress  ^= swBitMask; // Clear switch(es)

    return swBitMask;
}


template <typename ValueT>
inline bool BasicDebounceSwitchExt<ValueT>::released(ValueType swBitMask) noexcept
{
    swBitMask  &= _swRelease; // Read  switch(es)
    _swRelease ^= swBitMask;  // Clear switch(es)

    return swBitMask;
}


template <typename ValueT>
inline bool BasicDebounceSwitchExt<ValueT>::up(ValueType swBitMask) const noexcept
{ return !(_swState & swBitMask); }


template <typename ValueT>
inline bool BasicDebounceSwitchExt<ValueT>::down(ValueType swBitMask) const noexcept
{ return _swState & swBitMask; }


//
// For convenience
//
typedef BasicDebounceSwitchExt< uint8_t> Debounce8SwitchExt;
typedef BasicDebounceSwitchExt<uint16_t> Debounce16SwitchExt;
typedef BasicDebounceSwitchExt<uint32_t> Debounce32SwitchExt;
typedef BasicDebounceSwitchExt<uint64_t> Debounce64SwitchExt;


} // namespace eCxx


#endif
