#pragma once

#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <cstring>

#include "light_sensors.hpp"

namespace detail {

constexpr auto ENDL = "\r\n";
constexpr auto HELP_CMD = "help";
constexpr auto READ_CMD = "read";
constexpr auto READ_CSV_CMD = "read csv";
constexpr auto RANGE_CMD = "range";

constexpr auto VERSION_CMD = "version";
constexpr auto VERSION = "1.2";

static inline bool substringEquals(const char* str1, const char* str2, const size_t& size)
{
    return (std::strncmp(str1, str2, size) == 0);
}

static inline bool stringEquals(const char* str1, const char* str2, const size_t& size)
{
    if(size == std::strlen(str2)) {
        return substringEquals(str1, str2, size);
    }

    return false;
}

} // namespace detail

template<class Uart>
class Terminal {
  public:
    static void init()
    {
        m_serial.init();

        m_serial << detail::ENDL;
        printVersion();
        m_serial << detail::ENDL << "$ ";
    }

    static void callback()
    {
        if(receiveInput()) {
            parseInput();
        }
        m_serial.flushTx();
    }

  private:
    static constexpr auto INPUT_BUFFER_SIZE = 128;
    static constexpr auto BACKSPACE = uint8_t{0x7f};
    static constexpr auto CTRL_C = uint8_t{0x03};

    static Uart m_serial;
    static char m_inputBuffer[INPUT_BUFFER_SIZE];
    static uint16_t m_inputSize;
    static LightSensors m_lightSensors;

    static bool receiveInput()
    {
        uint8_t inputByte;

        while(m_serial.rxByte(inputByte)) {
            if(std::isprint(inputByte) || inputByte == CTRL_C) {
                m_inputBuffer[m_inputSize++] = inputByte;

                // Handle Ctrl + C
                if(inputByte == CTRL_C) {
                    m_serial << "^C" << detail::ENDL;
                    return true;
                }
                // Echo
                else {
                    m_serial << static_cast<char>(inputByte);
                }
            }

            // Handle backspace
            if(inputByte == BACKSPACE && m_inputSize > 0) {
                m_serial << "\b \b";
                --m_inputSize;
            }
            // Handle line terminator
            else if(inputByte == '\r' || inputByte == '\n') {
                // Consume possible second line terminator
                if(m_serial.peek(inputByte) && (inputByte == '\r' || inputByte == '\n')) {
                    m_serial.rxByte(inputByte);
                }
                m_serial << detail::ENDL;
                return true;
            }

            if(m_inputSize >= INPUT_BUFFER_SIZE) {
                m_serial << detail::ENDL << "WARNING: Terminal input buffer overflow!" << detail::ENDL;
                return true;
            }
        }

        return false;
    }

    static void parseInput()
    {
        if(m_inputSize) {
            if(m_inputBuffer[m_inputSize - 1] == CTRL_C) {
                handleCtrlC();
            }
            else {
                if(detail::substringEquals(m_inputBuffer, detail::HELP_CMD, m_inputSize)) {
                    printHelp();
                }
                else if(detail::substringEquals(m_inputBuffer, detail::READ_CMD, m_inputSize)) {
                    readSensors();
                }
                else if(detail::stringEquals(m_inputBuffer, detail::READ_CSV_CMD, m_inputSize)) {
                    readSensorsCsv();
                }
                else if(detail::stringEquals(m_inputBuffer, detail::RANGE_CMD, m_inputSize)) {
                    getSensorsRange();
                }
                else if(detail::substringEquals(m_inputBuffer, detail::VERSION_CMD, m_inputSize)) {
                    printVersion();
                }
                else {
                    printUnknown();
                }
            }
        }

        m_inputSize = 0;
        m_serial << "$ ";
    }

    static void handleCtrlC() { m_serial << "Abort!" << detail::ENDL; }

    static void printHelp()
    {
        m_serial << "AdaptiveBrightness command overview: " << detail::ENDL;
        m_serial << detail::HELP_CMD << " .......: prints this help message" << detail::ENDL;
        m_serial << detail::READ_CMD << " .......: reads and displays all LDR values" << detail::ENDL;
        m_serial << detail::READ_CSV_CMD << " ...: read LDR values as csv" << detail::ENDL;
        m_serial << detail::RANGE_CMD << " ......: print LDR sensor range" << detail::ENDL;
        m_serial << detail::VERSION_CMD << " ....: displays firmware version" << detail::ENDL;
    }

    static void readSensors()
    {
        const auto sensorValues = m_lightSensors.getValues();
        m_serial << "Sensor values: " << detail::ENDL;
        for(size_t i = 0; i < sensorValues.size(); ++i) {
            m_serial << "LDR";
            m_serial.txNumber(i + 1);
            m_serial << ": ";
            m_serial.template txNumber<uint16_t, 10, 4, ' '>(sensorValues[i]);
            m_serial << " - ";
            m_serial.template txNumber<uint8_t, 10, 3, ' '>(sensorValues[i] * 100 / m_lightSensors.MAX_VALUE);
            m_serial << "%" << detail::ENDL;
        }
    }

    static void readSensorsCsv()
    {
        const auto sensorValues = m_lightSensors.getValues();
        for(size_t i = 0; i < sensorValues.size(); ++i) {
            m_serial.txNumber(sensorValues[i]);
            m_serial << ((i + 1 == sensorValues.size()) ? "" : ",");
        }
        m_serial << detail::ENDL;
    }

    static void getSensorsRange()
    {
        m_serial << "0,";
        m_serial.txNumber(m_lightSensors.MAX_VALUE);
        m_serial << detail::ENDL;
    }

    static void printVersion() { m_serial << "AdaptiveBrightness v" << detail::VERSION << detail::ENDL; }

    static void printUnknown()
    {
        m_serial << "Unknown command \"";
        for(uint16_t i = 0; i < m_inputSize; ++i) {
            m_serial << static_cast<char>(m_inputBuffer[i]);
        }
        m_serial << "\"" << detail::ENDL;
    }
};

template<class Uart>
char Terminal<Uart>::m_inputBuffer[INPUT_BUFFER_SIZE];

template<class Uart>
uint16_t Terminal<Uart>::m_inputSize = 0;
