#include <Arduino.h>
#include <SoftwareSerial.h>   // https://github.com/plerup/espsoftwareserial/

//////////////////////////////////////////////////////////////////////////////
/// @brief Class for reading in data via a stream (serial) interface
///
/// At least one char array must be passed to the constructor as a buffer.
/// Optionally, one character each can be specified as a start and end marker
/// for the input.
///
/// If neither of these parameters is specified, the end marker is '\n' (return).
/// If no start marker but an end marker != '\n' is to be used,
/// enter '\0' as the start marker.
///
//////////////////////////////////////////////////////////////////////////////
class SerialReceive {
public:
  template <size_t ITEMS>
  SerialReceive(char (&buffer)[ITEMS], const char startMarker = '\0', const char endMarker = '\n')
      : strBuffer {buffer}, buffLen {ITEMS}, startMarker {startMarker}, endMarker {endMarker} {
    static_assert(ITEMS > 2, "Buffer deklaration too small (must be > 1)");
  }
  uint16_t receive(Stream &);
  const char *buffer() const { return strBuffer; }
  size_t bufferLen() const { return buffLen; }
  size_t strLen() const { return strlen(strBuffer); }

private:
  void reset(Stream &stream) {
    recvInProgress = false;
    index = 0;
    while (stream.available()) { stream.read(); }
  }

  char *strBuffer;
  const size_t buffLen;
  const char startMarker;
  const char endMarker;
  bool recvInProgress {false};
  byte index;
};

//////////////////////////////////////////////////////////////////////////////
/// @brief Receive data from a serial interface
///
/// @param &stream   Reference to an (serial) stream object
/// @return size_t   Number of characters read in
//////////////////////////////////////////////////////////////////////////////
uint16_t SerialReceive::receive(Stream &stream) {
  uint16_t counter {0};
  while (stream.available() > 0 && counter == 0) {
    // if no start marker is specified don't wait for it
    if (startMarker == '\0' && index == 0) { recvInProgress = true; }
    const int rb {stream.read()};
    if (recvInProgress == true) {
      switch (rb == endMarker) {
        case false:
          if (rb == '\n' || rb == '\r') {   // Input completed but endMarker was not transmitted
            strBuffer[0] = '\0';            // terminate the string
            reset(stream);                  // Clear the send buffer if data was sent without the required end marker
          } else {
            strBuffer[index] = rb;
            index = (index < buffLen) ? index + 1 : index;
          }
          break;
        case true:
          counter = index;
          strBuffer[index] = '\0';   // terminate the string
          reset(stream);             // Clear the send buffer for the next input
          break;
      }
    } else if (rb == startMarker) {
      recvInProgress = true;
    }
  }
  return counter;
}
/// Class End ////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/// Definition of constants and global variables/objects
///
//////////////////////////////////////////////////////////////////////////////

// Constants for SoftwareSerial
constexpr byte SSERIAL_RX {GPIO_NUM_26};
constexpr byte SSERIAL_TX {GPIO_NUM_27};
constexpr unsigned long BAUD_RATE {4800};

// Constants for voltage calculations
constexpr int MAX_VCC {477};   // Maximum Voltage
constexpr int MIN_VCC {310};   // Minimum Voltage
constexpr int PERCENT_DIVIDER {MAX_VCC - MIN_VCC};

constexpr byte DATA_ITEMS_FROM_SENDER {3};
constexpr unsigned char DATA_FIELD_SEPARATOR {','};

// Structure with text elements for output on the display.
// The constants will also used for snprintf!
// Caution with Unicode characters e.g. two byte chars like ° !
constexpr byte TEMPLEN {10};        // -XX.X° C0
constexpr byte HUMIDITYLEN {6};     // XXX %0
constexpr byte VCCLEN {7};          // X.XX V0
constexpr byte VCCPERCENTLEN {6};   // XXX %0

struct DisplayData {
  char sTemperature[TEMPLEN];
  char sHumidity[HUMIDITYLEN];
  char sVcc[VCCLEN];
  char sVccPercent[VCCPERCENTLEN];
};

char recBuffer[30];   // Buffer memory for receiving transmitter data
DisplayData dpData;

SoftwareSerial HC12Serial;
// SerialReceive<SoftwareSerial> rb {recBuffer};   // Object for controlling the input via a serial interface.
SerialReceive rb {recBuffer, '\0', '@'};   // Object for controlling the input via a serial interface.

//////////////////////////////////////////////////////////////////////////////
/// Forwardeclarations of used functions
///
//////////////////////////////////////////////////////////////////////////////
void prepareData(const char[], DisplayData &);
unsigned int strCnt(const char *, unsigned int, unsigned char);
bool checkContent(const char *, unsigned int);
void genTestText();

//////////////////////////////////////////////////////////////////////////////
/// Main Program
///
//////////////////////////////////////////////////////////////////////////////

void setup() {
  // HC12Serial.begin(BAUD_RATE);
  HC12Serial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, SSERIAL_RX, SSERIAL_TX);
  delay(5000);
  HC12Serial.println(F("\r\nSample Data (copy into the input line):"));
  genTestText();
}

void loop() {
  if (rb.receive(HC12Serial) > 0) {
    const char *bufferPtr = rb.buffer();
    unsigned int strLen = rb.strLen();
    if (strCnt(bufferPtr, strLen, DATA_FIELD_SEPARATOR) == DATA_ITEMS_FROM_SENDER &&
        checkContent(bufferPtr, strLen) == true) {
      prepareData(bufferPtr, dpData);
    } else {
      prepareData("99.9,99,0.00,", dpData);   // Error
    }
    //
    // Insert the function call for the display here
    //
    // E.g. doDisplay(DisplayData &dpData);
    //      call it with "doDisplay(dpData)"
    //
  }

  //
  // Add the remaining required operations here.
  //
}

//////////////////////////////////////////////////////////////////////////////
/// @brief The data received via the serial interface is prepared
///        for output on the display.
///
/// @param dataString
/// @param dpd
//////////////////////////////////////////////////////////////////////////////
void prepareData(const char dataString[], DisplayData &dpd) {
  float temp;
  int humidity;
  float vcc;

  sscanf(dataString, "%f,%d,%f,", &temp, &humidity, &vcc);

  snprintf(dpd.sTemperature, TEMPLEN, "%5.1f° C", temp);   // -XX.X Three digits with one decimal place + decimal point.
  snprintf(dpd.sHumidity, HUMIDITYLEN, "%3d %%", humidity);   // XXX Three digits with no decimal places.
  snprintf(dpd.sVcc, VCCLEN, "%4.2f V", vcc);   // X.XX Three digits with two decimal places  + decimal point.

  int vccValue = static_cast<int>(vcc * 100);
  // Conversion of the VCC value into a percentage
  int vccPercent = constrain(((vccValue - MIN_VCC) * 100) / PERCENT_DIVIDER, 0, 100);
  snprintf(dpd.sVccPercent, VCCPERCENTLEN, "%3d %%", vccPercent);   // XXX

  HC12Serial.print(dpd.sTemperature);
  HC12Serial.print(" - ");
  HC12Serial.print(dpd.sHumidity);
  HC12Serial.print(" - ");
  HC12Serial.print(dpd.sVcc);
  HC12Serial.print(" - ");
  HC12Serial.println(dpd.sVccPercent);
}

//////////////////////////////////////////////////////////////////////////////
/// @brief Determine whether a specific character occurs in the transferred string.
///        The number of characters found is returned.
///
/// @param string
/// @param len            // Length of the String
/// @param searchChar     // Character you are looking for
/// @return unsigned int  // Number of characters found
//////////////////////////////////////////////////////////////////////////////
unsigned int strCnt(const char *string, unsigned int len, unsigned char searchChar) {
  unsigned int count = 0;
  for (unsigned int i = 0; i < len; ++i) {
    if (string[i] == searchChar) ++count;
  }
  return count;
}

//////////////////////////////////////////////////////////////////////////////
/// @brief Check whether the string passed contains only digits
///        and the characters "," , "." and "-".
///
/// @param string
/// @param len      // Length of the String
/// @return true    // The string corresponds to the test pattern.
/// @return false   // it do not correspod to
//////////////////////////////////////////////////////////////////////////////
bool checkContent(const char *string, unsigned int len) {
  bool isOk = true;

  for (unsigned int i = 0; i < len; ++i) {
    unsigned char Char = string[i];
    switch (Char) {
      case '0' ... '9': [[fallthrough]];
      case '.': [[fallthrough]];
      case '-': [[fallthrough]];
      case ',': break;
      default: isOk = false;
    }
  }
  return isOk;
}

//////////////////////////////////////////////////////////////////////////////
/// @brief Create a test string This can be copied to the console
///        as serial input. No production code. Just for testting purposes.
///
/// @param toSend
/// @return char*
//////////////////////////////////////////////////////////////////////////////
void genTestText() {
  char toSend[30];
  float Temperatur {20.3};
  float Luftfeuchtigkeit {57.3};
  float vcc {4.32};

  // if (dht_read_data(&dht, &Temperatur, &Luftfeuchtigkeit) == 1) {
  char tempData[5];
  char humData[5];
  char vccData[5];
  char trenner[] = ",";

  // 4 is mininum width, 1 is precision; float value is copied onto buff
  dtostrf(Temperatur, 4, 1, tempData);
  dtostrf(Luftfeuchtigkeit, 2, 0, humData);
  dtostrf(vcc, 4, 2, vccData);   // Adjust precision as needed
  // toSend[0] = '{';
  strcat(toSend, tempData);
  strcat(toSend, trenner);
  strcat(toSend, humData);
  strcat(toSend, trenner);
  strcat(toSend, vccData);
  strcat(toSend, trenner);
  // strcat(toSend, "}");
  // } else {
  //   strcpy(toSend, "{99.9,99,0.00,}");   // Error
  //  }
  HC12Serial.println(toSend);
}