// Copyright 2018 Brett T. Warden

/// @file
/// @brief Disney Made With Magic (MWM) Support
/// derived from ir_Lasertag.cpp
/// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/557

// Supports:
//   Brand: Disney,  Model: Made With Magic (Glow With The Show) wand

#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"

// Constants
const uint16_t kMWMMinSamples = 6;  // Msgs are >=3 bytes, bytes have >=2
                                    // samples
const uint16_t kMWMTick = 417;
const uint32_t kMWMMinGap = 30000;  // Typical observed delay b/w commands
const uint8_t kMWMTolerance = 0;    // Percentage error margin.
const uint16_t kMWMExcess = 0;      // See kMarkExcess.
const uint16_t kMWMDelta = 150;     // Use instead of Excess and Tolerance.
const uint8_t kMWMMaxWidth = 9;     // Maximum number of successive bits at a
                                    // single level - worst case
const int16_t kSpace = 1;
const int16_t kMark = 0;

#if SEND_MWM
/// Send a MWM packet/message.
/// Status: Implemented.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @note This protocol is 2400 bps serial, 1 start bit (mark),
///   1 stop bit (space), no parity
void IRsend::sendMWM(const uint8_t data[], const uint16_t nbytes,
                     const uint16_t repeat) {
  if (nbytes < 3) return;  // Shortest possible message is 3 bytes

  // Set 38kHz IR carrier frequency & a 1/4 (25%) duty cycle.
  // NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols.
  enableIROut(38, 25);

  for (uint16_t r = 0; r <= repeat; r++) {
    // Data
    for (uint16_t i = 0; i < nbytes; i++) {
      uint8_t byte = data[i];

      // Start bit
      mark(kMWMTick);

      // LSB first, space=1
      for (uint8_t mask = 0x1; mask; mask <<= 1) {
        if (byte & mask) {  // 1
          space(kMWMTick);
        } else {  // 0
          mark(kMWMTick);
        }
      }
      // Stop bit
      space(kMWMTick);
    }
    // Footer
    space(kMWMMinGap);
  }
}
#endif  // SEND_MWM

#if DECODE_MWM
/// Decode the supplied MWM message.
/// Status: Implemented.
/// @param[in,out] results Ptr to the data to decode & where to store the result
/// @param[in] offset The starting index to use when attempting to decode the
///   raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return True if it can decode it, false if it can't.
/// @note This protocol is 2400 bps serial, 1 start bit (mark),
///   1 stop bit (space), no parity
bool IRrecv::decodeMWM(decode_results *results, uint16_t offset,
                       const uint16_t nbits, const bool strict) {
  DPRINTLN("DEBUG: decodeMWM");

  // Compliance
  if (results->rawlen <= kMWMMinSamples + offset) {
    DPRINTLN("DEBUG: decodeMWM: too few samples");
    return false;
  }

  uint16_t used = 0;
  uint64_t data = 0;
  uint16_t frame_bits = 0;
  uint16_t data_bits = 0;

  // No Header

  // Data
  uint8_t bits_per_frame = 10;
  for (; offset < results->rawlen && results->bits < 8 * kStateSizeMax;
       frame_bits++) {
    DPRINT("DEBUG: decodeMWM: offset = ");
    DPRINTLN(offset);
    int16_t level = getRClevel(results, &offset, &used, kMWMTick, kMWMTolerance,
                               kMWMExcess, kMWMDelta, kMWMMaxWidth);
    if (level < 0) {
      DPRINTLN("DEBUG: decodeMWM: getRClevel returned error");
      break;
    }
    switch (frame_bits % bits_per_frame) {
      case 0:
        // Start bit
        if (level != kMark) {
          DPRINTLN("DEBUG: decodeMWM: framing error - invalid start bit");
          goto done;
        }
        break;
      case 9:
        // Stop bit
        if (level != kSpace) {
          DPRINTLN("DEBUG: decodeMWM: framing error - invalid stop bit");
          return false;
        } else {
          DPRINT("DEBUG: decodeMWM: data_bits = ");
          DPRINTLN(data_bits);
          DPRINT("DEBUG: decodeMWM: Finished byte: ");
          DPRINTLN(uint64ToString(data));
          results->state[data_bits / 8 - 1] = data & 0xFF;
          results->bits = data_bits;
          data = 0;
        }
        break;
      default:
        // Data bits
        DPRINT("DEBUG: decodeMWM: Storing bit: ");
        DPRINTLN((level == kSpace));
        // Transmission is LSB-first, space=1
        data |= ((level == kSpace)) << 8;
        data >>= 1;
        data_bits++;
        break;
    }
  }

done:
  // Footer (None)

  // Compliance
  DPRINT("DEBUG: decodeMWM: frame_bits = ");
  DPRINTLN(frame_bits);
  DPRINT("DEBUG: decodeMWM: data_bits = ");
  DPRINTLN(data_bits);
  if (data_bits < nbits) {
    DPRINT("DEBUG: decodeMWM: too few bits; expected ");
    DPRINTLN(nbits);
    return false;  // Less data than we expected.
  }

  uint16_t payload_length = 0;
  switch (results->state[0] & 0xf0) {
    case 0x90:
    case 0xf0:
      // Normal commands
      payload_length = results->state[0] & 0x0f;
      DPRINT("DEBUG: decodeMWM: payload_length = ");
      DPRINTLN(payload_length);
      break;
    default:
      if (strict) {
        // Show commands
        if (results->state[0] != 0x55 && results->state[1] != 0xAA) {
          return false;
        }
      }
      break;
  }
  if (data_bits < (payload_length + 3) * 8) {
    DPRINT("DEBUG: decodeMWM: too few bytes; expected ");
    DPRINTLN((payload_length + 3));
    return false;
  }
  if (strict) {
    if (payload_length && (data_bits > (payload_length + 3) * 8)) {
      DPRINT("DEBUG: decodeMWM: too many bytes; expected ");
      DPRINTLN((payload_length + 3));
      return false;
    }
  }

  // Success
  results->decode_type = MWM;
  results->repeat = false;
  return true;
}
#endif  // DECODE_MWM

// vim: et:ts=2:sw=2
