text: // Copyright 2017 David Conran
  
  #include "IRutils.h"
  #ifndef UNIT_TEST
  #include <Arduino.h>
  #endif
  
  #define __STDC_LIMIT_MACROS
  #include <stdint.h>
  #include <algorithm>
  #ifndef ARDUINO
  #include <string>
  #endif
  #include "IRrecv.h"
  #include "IRremoteESP8266.h"
  
  // Reverse the order of the requested least significant nr. of bits.
  // Args:
  //   input: Bit pattern/integer to reverse.
  //   nbits: Nr. of bits to reverse.
  // Returns:
  //   The reversed bit pattern.
  uint64_t reverseBits(uint64_t input, uint16_t nbits) {
    if (nbits <= 1)
      return input;  // Reversing <= 1 bits makes no change at all.
    // Cap the nr. of bits to rotate to the max nr. of bits in the input.
    nbits = std::min(nbits, (uint16_t) (sizeof(input) * 8));
    uint64_t output = 0;
    for (uint16_t i = 0; i < nbits; i++) {
      output <<= 1;
      output |= (input & 1);
      input >>= 1;
    }
    // Merge any remaining unreversed bits back to the top of the reversed bits.
    return (input << nbits) | output;
  }
  
  // Convert a uint64_t (unsigned long long) to a string.
  // Arduino String/toInt/Serial.print() can't handle printing 64 bit values.
  //
  // Args:
  //   input: The value to print
  //   base:  The output base.
  // Returns:
  //   A string representation of the integer.
  // Note: Based on Arduino's Print::printNumber()
  #ifdef ARDUINO  // Arduino's & C++'s string implementations can't co-exist.
  String uint64ToString(uint64_t input, uint8_t base) {
    String result = "";
  #else
  std::string uint64ToString(uint64_t input, uint8_t base) {
    std::string result = "";
  #endif
    // prevent issues if called with base <= 1
    if (base < 2) base = 10;
    // Check we have a base that we can actually print.
    // i.e. [0-9A-Z] == 36
    if (base > 36) base = 10;
  
    do {
      char c = input % base;
      input /= base;
  
      if (c < 10)
        c +='0';
      else
        c += 'A' - 10;
      result = c + result;
    } while (input);
    return result;
  }
  
  #ifdef ARDUINO
  // Print a uint64_t/unsigned long long to the Serial port
  // Serial.print() can't handle printing long longs. (uint64_t)
  //
  // Args:
  //   input: The value to print
  //   base: The output base.
  void serialPrintUint64(uint64_t input, uint8_t base) {
    Serial.print(uint64ToString(input, base));
  }
  #endif
  
  // Convert a protocol type (enum etc) to a human readable string.
  // Args:
  //   protocol: Nr. (enum) of the protocol.
  //   isRepeat: A flag indicating if it is a repeat message of the protocol.
  // Returns:
  //   A string containing the protocol name.
  #ifdef ARDUINO  // Arduino's & C++'s string implementations can't co-exist.
  String typeToString(const decode_type_t protocol, const bool isRepeat) {
    String result = "";
  #else
  std::string typeToString(const decode_type_t protocol,
                           const bool isRepeat) {
    std::string result = "";
  #endif
    switch (protocol) {
      default:
      case UNKNOWN:       result = "UNKNOWN";           break;
      case UNUSED:        result = "UNUSED";            break;
      case AIWA_RC_T501:  result = "AIWA_RC_T501";      break;
      case ARGO:          result = "ARGO";              break;
      case COOLIX:        result = "COOLIX";            break;
      case DAIKIN:        result = "DAIKIN";            break;
      case DENON:         result = "DENON";             break;
      case DISH:          result = "DISH";              break;
      case FUJITSU_AC:    result = "FUJITSU_AC";        break;
      case GLOBALCACHE:   result = "GLOBALCACHE";       break;
      case GREE:          result = "GREE";              break;
      case JVC:           result = "JVC";               break;
      case KELVINATOR:    result = "KELVINATOR";        break;
      case LG:            result = "LG";                break;
      case MAGIQUEST:     result = "MAGIQUEST";         break;
      case MIDEA:         result = "MIDEA";             break;
      case MITSUBISHI:    result = "MITSUBISHI";        break;
      case MITSUBISHI_AC: result = "MITSUBISHI_AC";     break;
      case NEC:           result = "NEC";               break;
      case NEC_LIKE:      result = "NEC (non-strict)";  break;
      case NIKAI:         result = "NIKAI";             break;
      case PANASONIC:     result = "PANASONIC";         break;
      case PRONTO:        result = "PRONTO";            break;
      case RAW:           result = "RAW";               break;
      case RC5:           result = "RC5";               break;
      case RC5X:          result = "RC5X";              break;
      case RC6:           result = "RC6";               break;
      case RCMM:          result = "RCMM";              break;
      case SAMSUNG:       result = "SAMSUNG";           break;
      case SANYO:         result = "SANYO";             break;
      case SANYO_LC7461:  result = "SANYO_LC7461";      break;
      case SHARP:         result = "SHARP";             break;
      case SHERWOOD:      result = "SHERWOOD";          break;
      case SONY:          result = "SONY";              break;
      case TOSHIBA_AC:    result = "TOSHIBA_AC";        break;
      case TROTEC:        result = "TROTEC";            break;
      case WHYNTER:       result = "WHYNTER";           break;
    }
    if (isRepeat) result += " (Repeat)";
    return result;
  }
  
  // Does the given protocol use a complex state as part of the decode?
  bool hasACState(const decode_type_t protocol) {
    switch (protocol) {
      case DAIKIN:
      case FUJITSU_AC:
      case KELVINATOR:
      case TOSHIBA_AC:
        return true;
      default:
        return false;
    }
  }
  
  // Return the corrected length of a 'raw' format array structure
  // after over-large values are converted into multiple entries.
  // Args:
  //   results: A ptr to a decode result.
  // Returns:
  //   A uint16_t containing the length.
  uint16_t getCorrectedRawLength(const decode_results *results) {
    uint16_t extended_length = results->rawlen - 1;
    for (uint16_t i = 0; i < results->rawlen - 1; i++) {
      uint32_t usecs = results->rawbuf[i] * RAWTICK;
      // Add two extra entries for multiple larger than UINT16_MAX it is.
      extended_length += (usecs / (UINT16_MAX + 1)) * 2;
    }
    return extended_length;
  }
  
  // Return a string containing the key values of a decode_results structure
  // in a C/C++ code style format.
  #ifdef ARDUINO
  String resultToSourceCode(const decode_results *results) {
    String output = "";
  #else
  std::string resultToSourceCode(const decode_results *results) {
    std::string output = "";
  #endif
    // Start declaration
    output += "uint16_t ";  // variable type
    output += "rawData[";   // array name
    output += uint64ToString(getCorrectedRawLength(results), 10);
      // array size
    output += "] = {";                   // Start declaration
  
    // Dump data
    for (uint16_t i = 1; i < results->rawlen; i++) {
      uint32_t usecs;
      for (usecs = results->rawbuf[i] * RAWTICK;
           usecs > UINT16_MAX;
           usecs -= UINT16_MAX) {
        output += uint64ToString(UINT16_MAX);
        if (i % 2)
          output += ", 0,  ";
        else
          output += ",  0, ";
      }
      output += uint64ToString(usecs, 10);
      if (i < results->rawlen - 1)
        output += ", ";  // ',' not needed on the last one
      if (i % 2 == 0)  output += " ";  // Extra if it was even.
    }
  
    // End declaration
    output +="};";
  
    // Comment
    output += "  // " + typeToString(results->decode_type, results->repeat);
    // Only display the value if the decode type doesn't have an A/C state.
    if (!hasACState(results->decode_type))
      output += " " + uint64ToString(results->value, 16);
    output += "\n";
  
    // Now dump "known" codes
    if (results->decode_type != UNKNOWN) {
      if (hasACState(results->decode_type)) {
  #if DECODE_AC
        uint16_t nbytes = results->bits / 8;
        output += "uint8_t state[" + uint64ToString(nbytes) + "] = {";
        for (uint16_t i = 0; i < nbytes; i++) {
          output += "0x";
          if (results->state[i] < 0x10)  output += "0";
          output += uint64ToString(results->state[i], 16);
          if (i < nbytes - 1)
            output += ", ";
        }
        output += "};\n";
  #endif  // DECODE_AC
      } else {
        // Simple protocols
        // Some protocols have an address &/or command.
        // NOTE: It will ignore the atypical case when a message has been
        // decoded but the address & the command are both 0.
        if (results->address > 0 || results->command > 0) {
          output += "uint32_t address = 0x" +
                    uint64ToString(results->address, 16) + ";\n";
          output += "uint32_t command = 0x" +
                    uint64ToString(results->command, 16) + ";\n";
        }
        // Most protocols have data
        output += "uint64_t data = 0x" + uint64ToString(results->value, 16) +
                  ";\n";
      }
    }
    return output;
  }
  
  // Dump out the decode_results structure.
  //
  #ifdef ARDUINO
  String resultToTimingInfo(const decode_results *results) {
    String output = "";
    String value = "";
  #else
  std::string resultToTimingInfo(const decode_results *results) {
    std::string output = "";
    std::string value = "";
  #endif
    output += "Raw Timing[" + uint64ToString(results->rawlen - 1, 10) + "]:\n";
  
    for (uint16_t i = 1; i < results->rawlen; i++) {
      if (i % 2 == 0)
        output += "-";  // even
      else
        output += "   +";  // odd
      value = uint64ToString(results->rawbuf[i] * RAWTICK);
      // Space pad the value till it is at least 6 chars long.
      while (value.length() < 6)
        value = " " + value;
      output += value;
      if (i < results->rawlen - 1)
        output += ", ";  // ',' not needed for last one
      if (!(i % 8))  output += "\n";  // Newline every 8 entries.
    }
    output += "\n";
    return output;
  }
  
  // Dump out the decode_results structure.
  //
  #ifdef ARDUINO
  String resultToHumanReadableBasic(const decode_results *results) {
    String output = "";
  #else
  std::string resultToHumanReadableBasic(const decode_results *results) {
    std::string output = "";
  #endif
    // Show Encoding standard
    output += "Encoding  : " +
              typeToString(results->decode_type, results->repeat) + "\n";
  
    // Show Code & length
    output += "Code      : ";
    if (hasACState(results->decode_type)) {
  #if DECODE_AC
        for (uint16_t i = 0; results->bits > i * 8; i++) {
          if (results->state[i] < 0x10)  output += "0";  // Zero pad
          output += uint64ToString(results->state[i], 16);
        }
  #endif  // DECODE_AC
    } else {
      output += uint64ToString(results->value, 16);
    }
    output += " (" + uint64ToString(results->bits) + " bits)\n";
    return output;
  }
  
  uint8_t sumBytes(uint8_t *start, const uint16_t length, const uint8_t init) {
    uint8_t checksum = init;
    uint8_t *ptr;
    for (ptr = start; ptr - start < length; ptr++)
      checksum += *ptr;
    return checksum;
  }
  