#include "rc522.h"
#include "esphome/core/log.h"

// Based on:
// - https://github.com/miguelbalboa/rfid

namespace esphome {
namespace rc522 {

static const char *TAG = "rc522";

static const uint8_t RESET_COUNT = 5;

void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) {
  int offset = 0;
  for (uint8_t i = 0; i < uid_length; i++) {
    const char *format = "%02X";
    if (i + 1 < uid_length)
      format = "%02X-";
    offset += sprintf(buf + offset, format, uid[i]);
  }
}

void RC522::setup() {
  initialize_pending_ = true;
  // Pull device out of power down / reset state.

  // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode.
  if (reset_pin_ != nullptr) {
    reset_pin_->pin_mode(INPUT);

    if (reset_pin_->digital_read() == LOW) {  // The MFRC522 chip is in power down mode.
      ESP_LOGV(TAG, "Power down mode detected. Hard resetting...");
      reset_pin_->pin_mode(OUTPUT);     // Now set the resetPowerDownPin as digital output.
      reset_pin_->digital_write(LOW);   // Make sure we have a clean LOW state.
      delayMicroseconds(2);             // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl
      reset_pin_->digital_write(HIGH);  // Exit power down mode. This triggers a hard reset.
      // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs.
      // Let us be generous: 50ms.
      reset_timeout_ = millis();
      return;
    }
  }

  // Setup a soft reset
  reset_count_ = RESET_COUNT;
  reset_timeout_ = millis();
}

void RC522::initialize_() {
  // Per originall code, wait 50 ms
  if (millis() - reset_timeout_ < 50)
    return;

  // Reset baud rates
  ESP_LOGV(TAG, "Initialize");

  pcd_write_register(TX_MODE_REG, 0x00);
  pcd_write_register(RX_MODE_REG, 0x00);
  // Reset ModWidthReg
  pcd_write_register(MOD_WIDTH_REG, 0x26);

  // When communicating with a PICC we need a timeout if something goes wrong.
  // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo].
  // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg.
  pcd_write_register(T_MODE_REG, 0x80);  // TAuto=1; timer starts automatically at the end of the transmission in all
                                         // communication modes at all speeds

  // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
  pcd_write_register(T_PRESCALER_REG, 0xA9);
  pcd_write_register(T_RELOAD_REG_H, 0x03);  // Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
  pcd_write_register(T_RELOAD_REG_L, 0xE8);

  // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting
  pcd_write_register(TX_ASK_REG, 0x40);
  pcd_write_register(MODE_REG, 0x3D);  // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC
                                       // command to 0x6363 (ISO 14443-3 part 6.2.4)
  pcd_antenna_on_();                   // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)

  initialize_pending_ = false;
}

void RC522::dump_config() {
  ESP_LOGCONFIG(TAG, "RC522:");
  switch (this->error_code_) {
    case NONE:
      break;
    case RESET_FAILED:
      ESP_LOGE(TAG, "Reset command failed!");
      break;
  }

  LOG_PIN("  RESET Pin: ", this->reset_pin_);

  LOG_UPDATE_INTERVAL(this);

  for (auto *child : this->binary_sensors_) {
    LOG_BINARY_SENSOR("  ", "Tag", child);
  }
}

void RC522::loop() {
  // First check reset is needed
  if (reset_count_ > 0) {
    pcd_reset_();
    return;
  }
  if (initialize_pending_) {
    initialize_();
    return;
  }

  if (millis() - update_wait_ < this->update_interval_)
    return;

  auto status = picc_is_new_card_present_();

  static StatusCode LAST_STATUS = StatusCode::STATUS_OK;

  if (status != LAST_STATUS) {
    ESP_LOGD(TAG, "Status is now: %d", status);
    LAST_STATUS = status;
  }

  if (status == STATUS_ERROR)  // No card
  {
    // ESP_LOGE(TAG, "Error");
    // mark_failed();
    return;
  }

  if (status != STATUS_OK)  // We can receive STATUS_TIMEOUT when no card, or unexpected status.
    return;

  // Try process card
  if (!picc_read_card_serial_()) {
    ESP_LOGW(TAG, "Requesting tag read failed!");
    return;
  };

  if (uid_.size < 4) {
    return;
    ESP_LOGW(TAG, "Read serial size: %d", uid_.size);
  }

  update_wait_ = millis();

  bool report = true;
  // 1. Go through all triggers
  for (auto *trigger : this->triggers_)
    trigger->process(uid_.uiduint8_t, uid_.size);

  // 2. Find a binary sensor
  for (auto *tag : this->binary_sensors_) {
    if (tag->process(uid_.uiduint8_t, uid_.size)) {
      // 2.1 if found, do not dump
      report = false;
    }
  }

  if (report) {
    char buf[32];
    format_uid(buf, uid_.uiduint8_t, uid_.size);
    ESP_LOGD(TAG, "Found new tag '%s'", buf);
  }
}

void RC522::update() {
  for (auto *obj : this->binary_sensors_)
    obj->on_scan_end();
}

/**
 * Performs a soft reset on the MFRC522 chip and waits for it to be ready again.
 */
void RC522::pcd_reset_() {
  // The datasheet does not mention how long the SoftRest command takes to complete.
  // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg)
  // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let
  // us be generous: 50ms.

  if (millis() - reset_timeout_ < 50)
    return;

  if (reset_count_ == RESET_COUNT) {
    ESP_LOGV(TAG, "Soft reset...");
    // Issue the SoftReset command.
    pcd_write_register(COMMAND_REG, PCD_SOFT_RESET);
  }

  // Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms)
  if ((pcd_read_register(COMMAND_REG) & (1 << 4)) == 0) {
    reset_count_ = 0;
    ESP_LOGI(TAG, "Device online.");
    // Wait for initialize
    reset_timeout_ = millis();
    return;
  }

  if (--reset_count_ == 0) {
    ESP_LOGE(TAG, "Unable to reset RC522.");
    mark_failed();
  }
}

/**
 * Turns the antenna on by enabling pins TX1 and TX2.
 * After a reset these pins are disabled.
 */
void RC522::pcd_antenna_on_() {
  uint8_t value = pcd_read_register(TX_CONTROL_REG);
  if ((value & 0x03) != 0x03) {
    pcd_write_register(TX_CONTROL_REG, value | 0x03);
  }
}

/**
 * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or
 * selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT -
 * probably due do bad antenna design.
 *
 * @return STATUS_OK on success, STATUS_??? otherwise.
 */
RC522::StatusCode RC522::picc_request_a_(
    uint8_t *buffer_atqa,  ///< The buffer to store the ATQA (Answer to request) in
    uint8_t *buffer_size   ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
) {
  return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size);
}

/**
 * Transmits REQA or WUPA commands.
 * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna
 * design.
 *
 * @return STATUS_OK on success, STATUS_??? otherwise.
 */
RC522::StatusCode RC522::picc_reqa_or_wupa_(
    uint8_t command,       ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA
    uint8_t *buffer_atqa,  ///< The buffer to store the ATQA (Answer to request) in
    uint8_t *buffer_size   ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
) {
  uint8_t valid_bits;
  RC522::StatusCode status;

  if (buffer_atqa == nullptr || *buffer_size < 2) {  // The ATQA response is 2 uint8_ts long.
    return STATUS_NO_ROOM;
  }
  pcd_clear_register_bit_mask_(COLL_REG, 0x80);  // ValuesAfterColl=1 => Bits received after collision are cleared.
  valid_bits = 7;  // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only)
                   // uint8_t. TxLastBits = BitFramingReg[2..0]
  status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits);
  if (status != STATUS_OK)
    return status;
  if (*buffer_size != 2 || valid_bits != 0) {  // ATQA must be exactly 16 bits.
    ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR");
    return STATUS_ERROR;
  }

  return STATUS_OK;
}

/**
 * Sets the bits given in mask in register reg.
 */
void RC522::pcd_set_register_bit_mask_(PcdRegister reg,  ///< The register to update. One of the PCD_Register enums.
                                       uint8_t mask      ///< The bits to set.
) {
  uint8_t tmp = pcd_read_register(reg);
  pcd_write_register(reg, tmp | mask);  // set bit mask
}

/**
 * Clears the bits given in mask from register reg.
 */
void RC522::pcd_clear_register_bit_mask_(PcdRegister reg,  ///< The register to update. One of the PCD_Register enums.
                                         uint8_t mask      ///< The bits to clear.
) {
  uint8_t tmp = pcd_read_register(reg);
  pcd_write_register(reg, tmp & (~mask));  // clear bit mask
}

/**
 * Executes the Transceive command.
 * CRC validation can only be done if backData and backLen are specified.
 *
 * @return STATUS_OK on success, STATUS_??? otherwise.
 */
RC522::StatusCode RC522::pcd_transceive_data_(
    uint8_t *send_data,  ///< Pointer to the data to transfer to the FIFO.
    uint8_t send_len,    ///< Number of uint8_ts to transfer to the FIFO.
    uint8_t *back_data,  ///< nullptr or pointer to buffer if data should be read back after executing the command.
    uint8_t *back_len,   ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned.
    uint8_t
        *valid_bits,   ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr.
    uint8_t rx_align,  ///< In: Defines the bit position in backData[0] for the first bit received. Default 0.
    bool check_crc     ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be
                       ///< validated.
) {
  uint8_t wait_i_rq = 0x30;  // RxIRq and IdleIRq
  auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits,
                                        rx_align, check_crc);

  if (ret == STATUS_OK && *back_len == 5)
    ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d,  ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0],
              back_data[1], back_data[2], back_data[3], back_data[4]);
  else
    ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret);
  return ret;
}

/**
 * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO.
 * CRC validation can only be done if backData and backLen are specified.
 *
 * @return STATUS_OK on success, STATUS_??? otherwise.
 */
RC522::StatusCode RC522::pcd_communicate_with_picc_(
    uint8_t command,      ///< The command to execute. One of the PCD_Command enums.
    uint8_t wait_i_rq,    ///< The bits in the ComIrqReg register that signals successful completion of the command.
    uint8_t *send_data,   ///< Pointer to the data to transfer to the FIFO.
    uint8_t send_len,     ///< Number of uint8_ts to transfer to the FIFO.
    uint8_t *back_data,   ///< nullptr or pointer to buffer if data should be read back after executing the command.
    uint8_t *back_len,    ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned.
    uint8_t *valid_bits,  ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits.
    uint8_t rx_align,     ///< In: Defines the bit position in backData[0] for the first bit received. Default 0.
    bool check_crc        ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be
                          ///< validated.
) {
  ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc);

  // Prepare values for BitFramingReg
  uint8_t tx_last_bits = valid_bits ? *valid_bits : 0;
  uint8_t bit_framing =
      (rx_align << 4) + tx_last_bits;  // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]

  pcd_write_register(COMMAND_REG, PCD_IDLE);               // Stop any active command.
  pcd_write_register(COM_IRQ_REG, 0x7F);                   // Clear all seven interrupt request bits
  pcd_write_register(FIFO_LEVEL_REG, 0x80);                // FlushBuffer = 1, FIFO initialization
  pcd_write_register(FIFO_DATA_REG, send_len, send_data);  // Write sendData to the FIFO
  pcd_write_register(BIT_FRAMING_REG, bit_framing);        // Bit adjustments
  pcd_write_register(COMMAND_REG, command);                // Execute the command
  if (command == PCD_TRANSCEIVE) {
    pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80);  // StartSend=1, transmission of data starts
  }

  // Wait for the command to complete.
  // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops
  // transmitting. Each iteration of the do-while-loop takes 17.86μs.
  // TODO check/modify for other architectures than Arduino Uno 16bit
  uint16_t i;
  for (i = 4; i > 0; i--) {
    uint8_t n = pcd_read_register(
        COM_IRQ_REG);     // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq
    if (n & wait_i_rq) {  // One of the interrupts that signal success has been set.
      break;
    }
    if (n & 0x01) {  // Timer interrupt - nothing received in 25ms
      return STATUS_TIMEOUT;
    }
  }
  // 35.7ms and nothing happend. Communication with the MFRC522 might be down.
  if (i == 0) {
    return STATUS_TIMEOUT;
  }

  // Stop now if any errors except collisions were detected.
  uint8_t error_reg_value = pcd_read_register(
      ERROR_REG);  // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr
  if (error_reg_value & 0x13) {  // BufferOvfl ParityErr ProtocolErr
    return STATUS_ERROR;
  }

  uint8_t valid_bits_local = 0;

  // If the caller wants data back, get it from the MFRC522.
  if (back_data && back_len) {
    uint8_t n = pcd_read_register(FIFO_LEVEL_REG);  // Number of uint8_ts in the FIFO
    if (n > *back_len) {
      return STATUS_NO_ROOM;
    }
    *back_len = n;                                             // Number of uint8_ts returned
    pcd_read_register(FIFO_DATA_REG, n, back_data, rx_align);  // Get received data from FIFO
    valid_bits_local =
        pcd_read_register(CONTROL_REG) & 0x07;  // RxLastBits[2:0] indicates the number of valid bits in the last
                                                // received uint8_t. If this value is 000b, the whole uint8_t is valid.
    if (valid_bits) {
      *valid_bits = valid_bits_local;
    }
  }

  // Tell about collisions
  if (error_reg_value & 0x08) {  // CollErr
    return STATUS_COLLISION;
  }

  // Perform CRC_A validation if requested.
  if (back_data && back_len && check_crc) {
    // In this case a MIFARE Classic NAK is not OK.
    if (*back_len == 1 && valid_bits_local == 4) {
      return STATUS_MIFARE_NACK;
    }
    // We need at least the CRC_A value and all 8 bits of the last uint8_t must be received.
    if (*back_len < 2 || valid_bits_local != 0) {
      return STATUS_CRC_WRONG;
    }
    // Verify CRC_A - do our own calculation and store the control in controlBuffer.
    uint8_t control_buffer[2];
    RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]);
    if (status != STATUS_OK) {
      return status;
    }
    if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) {
      return STATUS_CRC_WRONG;
    }
  }

  return STATUS_OK;
}

/**
 * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A.
 *
 * @return STATUS_OK on success, STATUS_??? otherwise.
 */

RC522::StatusCode RC522::pcd_calculate_crc_(
    uint8_t *data,   ///< In: Pointer to the data to transfer to the FIFO for CRC calculation.
    uint8_t length,  ///< In: The number of uint8_ts to transfer.
    uint8_t *result  ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first.
) {
  ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length);
  pcd_write_register(COMMAND_REG, PCD_IDLE);        // Stop any active command.
  pcd_write_register(DIV_IRQ_REG, 0x04);            // Clear the CRCIRq interrupt request bit
  pcd_write_register(FIFO_LEVEL_REG, 0x80);         // FlushBuffer = 1, FIFO initialization
  pcd_write_register(FIFO_DATA_REG, length, data);  // Write data to the FIFO
  pcd_write_register(COMMAND_REG, PCD_CALC_CRC);    // Start the calculation

  // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs.
  // TODO check/modify for other architectures than Arduino Uno 16bit

  // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us.
  for (uint16_t i = 5000; i > 0; i--) {
    // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved
    uint8_t n = pcd_read_register(DIV_IRQ_REG);
    if (n & 0x04) {                               // CRCIRq bit set - calculation done
      pcd_write_register(COMMAND_REG, PCD_IDLE);  // Stop calculating CRC for new content in the FIFO.
      // Transfer the result from the registers to the result buffer
      result[0] = pcd_read_register(CRC_RESULT_REG_L);
      result[1] = pcd_read_register(CRC_RESULT_REG_H);

      ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK");
      return STATUS_OK;
    }
  }
  ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT");
  // 89ms passed and nothing happend. Communication with the MFRC522 might be down.
  return STATUS_TIMEOUT;
}
/**
 * Returns STATUS_OK if a PICC responds to PICC_CMD_REQA.
 * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored.
 *
 * @return  STATUS_OK on success, STATUS_??? otherwise.
 */

RC522::StatusCode RC522::picc_is_new_card_present_() {
  uint8_t buffer_atqa[2];
  uint8_t buffer_size = sizeof(buffer_atqa);

  // Reset baud rates
  pcd_write_register(TX_MODE_REG, 0x00);
  pcd_write_register(RX_MODE_REG, 0x00);
  // Reset ModWidthReg
  pcd_write_register(MOD_WIDTH_REG, 0x26);

  auto result = picc_request_a_(buffer_atqa, &buffer_size);

  ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result);
  return result;
}

/**
 * Simple wrapper around PICC_Select.
 * Returns true if a UID could be read.
 * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first.
 * The read UID is available in the class variable uid.
 *
 * @return bool
 */
bool RC522::picc_read_card_serial_() {
  RC522::StatusCode result = picc_select_(&this->uid_);
  ESP_LOGVV(TAG, "picc_select_(...) -> %d", result);
  return (result == STATUS_OK);
}

/**
 * Transmits SELECT/ANTICOLLISION commands to select a single PICC.
 * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or
 * PICC_WakeupA(). On success:
 *     - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the
 * ISO/IEC 14443-3 draft.)
 *     - The UID size and value of the chosen PICC is returned in *uid along with the SAK.
 *
 * A PICC UID consists of 4, 7 or 10 uint8_ts.
 * Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used:
 *     UID size  Number of UID uint8_ts    Cascade levels    Example of PICC
 *     ========  ===================    ==============    ===============
 *     single         4            1        MIFARE Classic
 *     double         7            2        MIFARE Ultralight
 *     triple        10            3        Not currently in use?
 *
 * @return STATUS_OK on success, STATUS_??? otherwise.
 */
RC522::StatusCode RC522::picc_select_(
    Uid *uid,           ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID.
    uint8_t valid_bits  ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply
                        ///< uid->size.
) {
  bool uid_complete;
  bool select_done;
  bool use_cascade_tag;
  uint8_t cascade_level = 1;
  RC522::StatusCode result;
  uint8_t count;
  uint8_t check_bit;
  uint8_t index;
  uint8_t uid_index;                // The first index in uid->uiduint8_t[] that is used in the current Cascade Level.
  int8_t current_level_known_bits;  // The number of known UID bits in the current Cascade Level.
  uint8_t buffer[9];    // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A
  uint8_t buffer_used;  // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO.
  uint8_t rx_align;     // Used in BitFramingReg. Defines the bit position for the first bit received.
  uint8_t tx_last_bits;  // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t.
  uint8_t *response_buffer;
  uint8_t response_length;

  // Description of buffer structure:
  //    uint8_t 0: SEL         Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
  //    uint8_t 1: NVB          Number of Valid Bits (in complete command, not just the UID): High nibble: complete
  // uint8_ts,
  // Low nibble: Extra bits.     uint8_t 2: UID-data or CT    See explanation below. CT means Cascade Tag.     uint8_t
  // 3: UID-data uint8_t 4: UID-data     uint8_t 5: UID-data     uint8_t 6: BCC          Block Check Character - XOR of
  // uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits
  // of the current Cascade Level.
  //
  // Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels)
  //    UID size  Cascade level  uint8_t2  uint8_t3  uint8_t4  uint8_t5
  //    ========  =============  =====  =====  =====  =====
  //     4 uint8_ts    1      uid0  uid1  uid2  uid3
  //     7 uint8_ts    1      CT    uid0  uid1  uid2
  //            2      uid3  uid4  uid5  uid6
  //    10 uint8_ts    1      CT    uid0  uid1  uid2
  //            2      CT    uid3  uid4  uid5
  //            3      uid6  uid7  uid8  uid9

  // Sanity checks
  if (valid_bits > 80) {
    return STATUS_INVALID;
  }

  ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits);

  // Prepare MFRC522
  pcd_clear_register_bit_mask_(COLL_REG, 0x80);  // ValuesAfterColl=1 => Bits received after collision are cleared.

  // Repeat Cascade Level loop until we have a complete UID.
  uid_complete = false;
  while (!uid_complete) {
    // Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2.
    switch (cascade_level) {
      case 1:
        buffer[0] = PICC_CMD_SEL_CL1;
        uid_index = 0;
        use_cascade_tag = valid_bits && uid->size > 4;  // When we know that the UID has more than 4 uint8_ts
        break;

      case 2:
        buffer[0] = PICC_CMD_SEL_CL2;
        uid_index = 3;
        use_cascade_tag = valid_bits && uid->size > 7;  // When we know that the UID has more than 7 uint8_ts
        break;

      case 3:
        buffer[0] = PICC_CMD_SEL_CL3;
        uid_index = 6;
        use_cascade_tag = false;  // Never used in CL3.
        break;

      default:
        return STATUS_INTERNAL_ERROR;
        break;
    }

    // How many UID bits are known in this Cascade Level?
    current_level_known_bits = valid_bits - (8 * uid_index);
    if (current_level_known_bits < 0) {
      current_level_known_bits = 0;
    }
    // Copy the known bits from uid->uiduint8_t[] to buffer[]
    index = 2;  // destination index in buffer[]
    if (use_cascade_tag) {
      buffer[index++] = PICC_CMD_CT;
    }
    uint8_t uint8_ts_to_copy = current_level_known_bits / 8 +
                               (current_level_known_bits % 8
                                    ? 1
                                    : 0);  // The number of uint8_ts needed to represent the known bits for this level.
    if (uint8_ts_to_copy) {
      uint8_t maxuint8_ts =
          use_cascade_tag ? 3 : 4;  // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag
      if (uint8_ts_to_copy > maxuint8_ts) {
        uint8_ts_to_copy = maxuint8_ts;
      }
      for (count = 0; count < uint8_ts_to_copy; count++) {
        buffer[index++] = uid->uiduint8_t[uid_index + count];
      }
    }
    // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits
    if (use_cascade_tag) {
      current_level_known_bits += 8;
    }

    // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations.
    select_done = false;
    while (!select_done) {
      // Find out how many bits and uint8_ts to send and receive.
      if (current_level_known_bits >= 32) {  // All UID bits in this Cascade Level are known. This is a SELECT.

        if (response_length < 4) {
          ESP_LOGW(TAG, "Not enough data received.");
          return STATUS_INVALID;
        }

        // Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
        buffer[1] = 0x70;  // NVB - Number of Valid Bits: Seven whole uint8_ts
        // Calculate BCC - Block Check Character
        buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];
        // Calculate CRC_A
        result = pcd_calculate_crc_(buffer, 7, &buffer[7]);
        if (result != STATUS_OK) {
          return result;
        }
        tx_last_bits = 0;  // 0 => All 8 bits are valid.
        buffer_used = 9;
        // Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx)
        response_buffer = &buffer[6];
        response_length = 3;
      } else {  // This is an ANTICOLLISION.
        // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
        tx_last_bits = current_level_known_bits % 8;
        count = current_level_known_bits / 8;     // Number of whole uint8_ts in the UID part.
        index = 2 + count;                        // Number of whole uint8_ts: SEL + NVB + UIDs
        buffer[1] = (index << 4) + tx_last_bits;  // NVB - Number of Valid Bits
        buffer_used = index + (tx_last_bits ? 1 : 0);
        // Store response in the unused part of buffer
        response_buffer = &buffer[index];
        response_length = sizeof(buffer) - index;
      }

      // Set bit adjustments
      rx_align = tx_last_bits;  // Having a separate variable is overkill. But it makes the next line easier to read.
      pcd_write_register(
          BIT_FRAMING_REG,
          (rx_align << 4) + tx_last_bits);  // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]

      // Transmit the buffer and receive the response.
      result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align);
      if (result == STATUS_COLLISION) {  // More than one PICC in the field => collision.
        uint8_t value_of_coll_reg = pcd_read_register(
            COLL_REG);  // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0]
        if (value_of_coll_reg & 0x20) {  // CollPosNotValid
          return STATUS_COLLISION;       // Without a valid collision position we cannot continue
        }
        uint8_t collision_pos = value_of_coll_reg & 0x1F;  // Values 0-31, 0 means bit 32.
        if (collision_pos == 0) {
          collision_pos = 32;
        }
        if (collision_pos <= current_level_known_bits) {  // No progress - should not happen
          return STATUS_INTERNAL_ERROR;
        }
        // Choose the PICC with the bit set.
        current_level_known_bits = collision_pos;
        count = current_level_known_bits % 8;  // The bit to modify
        check_bit = (current_level_known_bits - 1) % 8;
        index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0);  // First uint8_t is index 0.
        if (response_length > 2)  // Note: Otherwise buffer[index] might be not initialized
          buffer[index] |= (1 << check_bit);
      } else if (result != STATUS_OK) {
        return result;
      } else {                                 // STATUS_OK
        if (current_level_known_bits >= 32) {  // This was a SELECT.
          select_done = true;                  // No more anticollision
                                               // We continue below outside the while.
        } else {                               // This was an ANTICOLLISION.
          // We now have all 32 bits of the UID in this Cascade Level
          current_level_known_bits = 32;
          // Run loop again to do the SELECT.
        }
      }
    }  // End of while (!selectDone)

    // We do not check the CBB - it was constructed by us above.

    // Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[]
    index = (buffer[2] == PICC_CMD_CT) ? 3 : 2;  // source index in buffer[]
    uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4;
    for (count = 0; count < uint8_ts_to_copy; count++) {
      uid->uiduint8_t[uid_index + count] = buffer[index++];
    }

    // Check response SAK (Select Acknowledge)
    if (response_length != 3 || tx_last_bits != 0) {  // SAK must be exactly 24 bits (1 uint8_t + CRC_A).
      return STATUS_ERROR;
    }
    // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed
    // anymore.
    result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]);
    if (result != STATUS_OK) {
      return result;
    }
    if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) {
      return STATUS_CRC_WRONG;
    }
    if (response_buffer[0] & 0x04) {  // Cascade bit set - UID not complete yes
      cascade_level++;
    } else {
      uid_complete = true;
      uid->sak = response_buffer[0];
    }
  }  // End of while (!uidComplete)

  // Set correct uid->size
  uid->size = 3 * cascade_level + 1;

  return STATUS_OK;
}

bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) {
  if (len != this->uid_.size())
    return false;

  for (uint8_t i = 0; i < len; i++) {
    if (data[i] != this->uid_[i])
      return false;
  }

  this->publish_state(true);
  this->found_ = true;
  return true;
}
void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) {
  char buf[32];
  format_uid(buf, uid, uid_length);
  this->trigger(std::string(buf));
}

}  // namespace rc522
}  // namespace esphome
