/*
 * 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_H
#define ECXX_PLATFORM_DEBOUNCE_SWITCH_H


#include <eCxx/CTL/Types.h>


namespace eCxx {


//
// A simple 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 if a switch has been pressed, pass only the switch's 'swBitMask' to the pressed() function.
//
// # 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() function must be called using
//   an atomic block.
//
template <typename ValueT>
class BasicDebounceSwitch : 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 ~BasicDebounceSwitch() = default;

    public:
        inline BasicDebounceSwitch() noexcept;

        inline void debounce(ValueType swBitMask) noexcept;
        inline bool pressed(ValueType swBitMask) noexcept;

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


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


template <typename ValueT>
inline void BasicDebounceSwitch<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
}


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

    return swBitMask;
}


//
// For convenience
//
typedef BasicDebounceSwitch< uint8_t> Debounce8Switch;
typedef BasicDebounceSwitch<uint16_t> Debounce16Switch;
typedef BasicDebounceSwitch<uint32_t> Debounce32Switch;
typedef BasicDebounceSwitch<uint64_t> Debounce64Switch;


} // namespace eCxx


#endif
