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


#ifndef ECXX_PLATFORM_KEYPAD_SCANNER_H
#define ECXX_PLATFORM_KEYPAD_SCANNER_H


#include <eCxx/CTL/Types.h>


namespace eCxx {


//
// A simple keypad scanner
//
/*
 * # This keypad scanner does not perform key debouncing (for this, another class is needed).
 *
 * # This keypad scanner always drives the rows and reads the columns, this behavior cannot be
 *   changed.
 *
 * # This keypad scanner will automatically set the pins as inputs and outputs as needed, hence,
 *   there is no need to manually set the pin modes.
 *
 * # The recommended pull-up or pull-down resistors for the keypad is between 18 kOhm and 47 kOhm
 *   (or use the MCU's pins internal pull-up or pull-down resistors).
 *
 * # The recommended series resistors between the MCU pins (or the decoder pins) that drives the
 *   rows and the keypad is between 1 kOhm and 3.3 kOhm. This series resistors are necessary to
 *   prevent short circuit in case multiple keys are pressed at the same time.
 *
 * # It is also safer if the columns also have series resistors, in case the hardware is for simple
 *   keypad but the driver is incorrectly configured for keypad with decoder and multiplexer.
 *
 * # In order to correctly read multiple keys pressed together, the series resistors for the row
 *   should be replaced by diodes. However, this would mean pull-up or pull-down resistors are
 *   mandatory for both simple keypad and keypad with decoder and multiplexer.
 *
 * # Please pay attention to the diodes' orientation (polarity). The example images below show the
 *   hardware configuration for active low keypads with series resistors at the columns and diodes
 *   at the rows.
 *
 * -------------                                 ---------------------------------
 * Simple Keypad                                 Keypad with Decoder & Multiplexer
 * -------------                                 ---------------------------------
 *
 *             C0  C1  C2                                                            M
 *             ▲   ▲   ▲                                                             ▲
 *             │   │   │                                                             │
 *             ║   ║   ║                                                             │
 *             │   │   │                                                             │
 *           ┌───┬───┬───┐                                                     ┌───────────────────────────────┐
 * R0 ─┤◁──▶ │   │   │   │                       C0 ─────────────────────────▶ │                               │
 *           ├───┼───┼───┤                       C1 ─────────────────────────▶ │      Multiplexer 8 to 1       │
 * R1 ─┤◁──▶ │   │   │   │                       C2 ─────────────────────────▶ │      e.g. 74HC151             │
 *           ├───┼───┼───┤                                                     │                               │
 * R2 ─┤◁──▶ │   │   │   │                                                     └───────────────────────────────┘
 *           └───┴───┴───┘                                                       ▲   ▲   ▲   ▲   ▲   ▲   ▲   ▲
 *                                                                               │   │   │   │   │   │   │   │
 * The keypad is active-low if it uses                                           ║   ║   ║   ║   ║   ║   ║   ║
 * pull-up resistors.                                                            │   │   │   │   │   │   │   │
 *                                                         ┌─────────┐         ┌───┬───┬───┬───┬───┬───┬───┬───┐
 * The keypad is active-high if it uses          R0 ─────▶ │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 * pull-down resitors.                           R1 ─────▶ │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                               R2 ─────▶ │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 * Using 3 outputs and 3 inputs, 9 keys                    │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 * can be scanned.                                         │ Decoder ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                         │ 3 to 8  │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                         │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                         │ e.g.    │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                         │ 74HC137 ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                         │ 74HC138 │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                         │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                         │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                         │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                         │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                         │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                         └─────────┘         └───┴───┴───┴───┴───┴───┴───┴───┘
 *
 *                                               The keypad is active-low if the decoder output is active-low while
 *                                               the multiplexer output is active-high, or the other way around.
 *
 *                                               The keypad is active-high if both the decoder and multiplexer outputs
 *                                               are active-low or active-high.
 *
 *                                               Using 6 outputs and 1 input, 64 keys can be scanned.
 *
 * ------------------------------------------------------------------------------------------
 * Keypad with Serial-In Parallel-Out Shift Register with Output-Latch, Decoder & Multiplexer
 * ------------------------------------------------------------------------------------------
 *
 *                                                                          M
 *                                                                          ▲
 *                                                                          │
 *             ┌──────────┐                                                 │
 *             │          │                                                 │
 *   DS ─────▶ │          │                                           ┌───────────────────────────────┐
 * SHCP ─────▶ │          │─── Q0 ───── C0 ─────────────────────────▶ │                               │
 * STCP ─────▶ │          │─── Q1 ───── C1 ─────────────────────────▶ │      Multiplexer 8 to 1       │
 *             │          │─── Q2 ───── C2 ─────────────────────────▶ │      e.g. 74HC151             │
 *             │ Serial   │                                           │                               │
 *             │ to       │                                           └───────────────────────────────┘
 *             │ Parallel │                                             ▲   ▲   ▲   ▲   ▲   ▲   ▲   ▲
 *             │          │                                             │   │   │   │   │   │   │   │
 *             │ e.g.     │                                             ║   ║   ║   ║   ║   ║   ║   ║
 *             │ 74HC595  │                                             │   │   │   │   │   │   │   │
 *             │          │                       ┌─────────┐         ┌───┬───┬───┬───┬───┬───┬───┬───┐
 *             │          │─── Q3 ───── R0 ─────▶ │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *             │          │─── Q4 ───── R1 ─────▶ │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *             │          │─── Q5 ───── R2 ─────▶ │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *             │          │                       │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *             │          │                       │ Decoder ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *             └──────────┘                       │ 3 to 8  │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                │ e.g.    │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                │ 74HC137 ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                │ 74HC138 │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                │         │         ├───┼───┼───┼───┼───┼───┼───┼───┤
 *                                                │         ├○ ─┤◁──▶ │   │   │   │   │   │   │   │   │
 *                                                └─────────┘         └───┴───┴───┴───┴───┴───┴───┴───┘
 *
 * The keypad is active-low if the decoder output is active-low while the multiplexer output is active-high,
 * or the other way around.
 *
 * The keypad is active-high if both the decoder and multiplexer outputs are active-low or active-high.
 *
 * Using 3 outputs and 1 input, 64 keys can be scanned.
 */
template <uint8_t RowN, uint8_t ColN, uint8_t MultiKeyN = 1>
class KeypadScanner : public Uncopyable {
    static_assert(1 <= MultiKeyN && MultiKeyN <= 4, "template argument substituting MultiKeyN is not between 1 and 4");

    public:
        static constexpr uint8_t NumberOfRows      = RowN;
        static constexpr uint8_t NumberOfColumns   = ColN;
        static constexpr uint8_t NumberOfMultiKeys = MultiKeyN;

    public:
        inline ~KeypadScanner() = default;

    public:
        inline KeypadScanner() noexcept;

        inline void begin(
            ActiveState   keypadState,
            const uint8_t (&rowPin)[NumberOfRows   ], // Output
            const uint8_t (&colPin)[NumberOfColumns], // Input
            bool          pullUpColPins               // Only meaningful if the keypad is active-low
        ) noexcept;

        inline void begin(
            ActiveState   keypadState,
            const uint8_t (&rowPin)[NumberOfRows   ], // Output
            const uint8_t (&colPin)[NumberOfColumns], // Output
            uint8_t       muxPin,                     // Input
            ActiveState   decoderAddressState     = ActiveState::High,
            ActiveState   multiplexerAddressState = ActiveState::High
        ) noexcept;

        inline void begin(
            ActiveState   keypadState,
            uint8_t       dsPin,   // Output
            uint8_t       shcpPin, // Output
            uint8_t       stcpPin, // Output
            uint8_t       muxPin,  // Input
            ActiveState   decoderAddressState     = ActiveState::High,
            ActiveState   multiplexerAddressState = ActiveState::High
        ) noexcept;

        inline void end() noexcept;

        inline uint8_t scan(uint8_t (&keyState)[NumberOfMultiKeys]) noexcept;

    private:
        inline uint8_t scanSimple() noexcept;
        inline uint8_t scanDecMux() noexcept;
        inline uint8_t scanSIPO() noexcept;

        inline void sendRowAddress(uint8_t addr) const noexcept;
        inline void sendColAddress(uint8_t addr) const noexcept;

        inline void sendRowColAddress(uint8_t raddr, uint8_t caddr) const noexcept;

    private:
        enum class KeypadMode : uint8_t {
            Uninitialized,
            SimpleActiveLow,
            SimpleActiveHigh,
            DecMuxActiveLow,
            DecMuxActiveHigh,
            SIPOActiveLow,
            SIPOActiveHigh
        };

        struct SimpleKeypad {
            uint8_t rowPin[NumberOfRows   ];
            uint8_t colPin[NumberOfColumns];
        };

        struct DecMuxKeypad {
            uint8_t     rowPin[NumberOfRows   ];
            uint8_t     colPin[NumberOfColumns];
            uint8_t     muxPin;
            ActiveState decAddrAS;
            ActiveState muxAddrAS;
        };

        struct SIPOKeypad {
            uint8_t     dsPin;
            uint8_t     shcpPin;
            uint8_t     stcpPin;
            uint8_t     muxPin;
            ActiveState decAddrAS;
            ActiveState muxAddrAS;
        };

        union PinSpec {
            uint8_t      mode;
            SimpleKeypad simple;
            DecMuxKeypad decmux;
            SIPOKeypad   sipo;
        };

    private:
        KeypadMode _keypadMode;
        PinSpec    _pinSpec;

        uint8_t    _keyState[NumberOfMultiKeys];
};


//
// Include the implementation file
//
#define INCLUDE_ECXX_PLATFORM_KEYPAD_SCANNER_TPP
#include "KeypadScanner.tpp"


} // namespace eCxx


#endif
