#include "Max31865RtdLowlevelHandler.h"

#include <fsfw/tasks/TaskFactory.h>
#include <fsfw/timemanager/Stopwatch.h>
#include <fsfw_hal/linux/spi/ManualCsLockGuard.h>

#define OBSW_RTD_AUTO_MODE 1

#if OBSW_RTD_AUTO_MODE == 1
static constexpr uint8_t BASE_CFG = (MAX31865::Bias::ON << MAX31865::CfgBitPos::BIAS_SEL) |
                                    (MAX31865::Wires::FOUR_WIRE << MAX31865::CfgBitPos::WIRE_SEL) |
                                    (MAX31865::ConvMode::AUTO << MAX31865::CfgBitPos::CONV_MODE);
#else
static constexpr uint8_t BASE_CFG =
    (MAX31865::Bias::OFF << MAX31865::CfgBitPos::BIAS_SEL) |
    (MAX31865::Wires::FOUR_WIRE << MAX31865::CfgBitPos::WIRE_SEL) |
    (MAX31865::ConvMode::NORM_OFF << MAX31865::CfgBitPos::CONV_MODE);
#endif

Max31865RtdReader::Max31865RtdReader(object_id_t objectId, SpiComIF* lowLevelComIF, GpioIF* gpioIF)
    : SystemObject(objectId), rtds(EiveMax31855::NUM_RTDS), comIF(lowLevelComIF), gpioIF(gpioIF) {
  readerMutex = MutexFactory::instance()->createMutex();
}

ReturnValue_t Max31865RtdReader::performOperation(uint8_t operationCode) {
  using namespace MAX31865;
  ReturnValue_t result = returnvalue::OK;
  static_cast<void>(result);
  // Stopwatch watch;
  if (periodicInitHandling()) {
#if OBSW_RTD_AUTO_MODE == 0
    // 10 ms delay for VBIAS startup
    TaskFactory::delayTask(10);
#endif
  } else {
    // No devices usable (e.g. TCS board off)
    return returnvalue::OK;
  }

#if OBSW_RTD_AUTO_MODE == 0
  result = periodicReadReqHandling();
  if (result != returnvalue::OK) {
    return result;
  }
  // After requesting, 65 milliseconds delay required
  TaskFactory::delayTask(65);
#endif

  return periodicReadHandling();
}

bool Max31865RtdReader::rtdIsActive(uint8_t idx) {
  if (rtds[idx]->on and rtds[idx]->db.active and rtds[idx]->db.configured) {
    return true;
  }
  return false;
}

bool Max31865RtdReader::periodicInitHandling() {
  using namespace MAX31865;
  MutexGuard mg(readerMutex);
  ReturnValue_t result = returnvalue::OK;
  if (mg.getLockResult() != returnvalue::OK) {
    sif::warning << "Max31865RtdReader::periodicInitHandling: Mutex lock failed" << std::endl;
    return false;
  }

  for (auto& rtd : rtds) {
    if (rtd == nullptr) {
      continue;
    }
    if ((rtd->on or rtd->db.active) and not rtd->db.configured and rtd->cd.hasTimedOut()) {
      ManualCsLockWrapper mg(csLock, gpioIF, rtd->spiCookie, csTimeoutType, csTimeoutMs);
      if (mg.lockResult != returnvalue::OK or mg.gpioResult != returnvalue::OK) {
        sif::error << "Max31865RtdReader::periodicInitHandling: Manual CS lock failed" << std::endl;
        break;
      }
      result = writeCfgReg(rtd->spiCookie, BASE_CFG);
      if (result != returnvalue::OK) {
        handleSpiError(rtd, result, "writeCfgReg");
      }
      if (rtd->writeLowThreshold) {
        result = writeLowThreshold(rtd->spiCookie, rtd->lowThreshold);
        if (result != returnvalue::OK) {
          handleSpiError(rtd, result, "writeLowThreshold");
        }
      }
      if (rtd->writeHighThreshold) {
        result = writeHighThreshold(rtd->spiCookie, rtd->highThreshold);
        if (result != returnvalue::OK) {
          handleSpiError(rtd, result, "writeHighThreshold");
        }
      }
      result = clearFaultStatus(rtd->spiCookie);
      if (result != returnvalue::OK) {
        handleSpiError(rtd, result, "clearFaultStatus");
      }
      rtd->db.configured = true;
      rtd->db.active = true;
    }
  }
  bool someRtdUsable = false;
  for (auto& rtd : rtds) {
    if (rtd == nullptr) {
      continue;
    }
    if (rtdIsActive(rtd->idx)) {
#if OBSW_RTD_AUTO_MODE == 0
      result = writeBiasSel(Bias::ON, rtd->spiCookie, BASE_CFG);
#endif
      someRtdUsable = true;
    }
  }
  return someRtdUsable;
}

ReturnValue_t Max31865RtdReader::periodicReadReqHandling() {
  using namespace MAX31865;
  MutexGuard mg(readerMutex);
  if (mg.getLockResult() != returnvalue::OK) {
    sif::warning << "Max31865RtdReader::periodicReadReqHandling: Mutex lock failed" << std::endl;
    return returnvalue::FAILED;
  }
  // Now request one shot config for all active RTDs
  for (auto& rtd : rtds) {
    if (rtd == nullptr) {
      continue;
    }
    if (rtdIsActive(rtd->idx)) {
      ReturnValue_t result = writeCfgReg(rtd->spiCookie, BASE_CFG | (1 << CfgBitPos::ONE_SHOT));
      if (result != returnvalue::OK) {
        handleSpiError(rtd, result, "writeCfgReg");
        // Release mutex ASAP
        return returnvalue::FAILED;
      }
    }
  }
  return returnvalue::OK;
}

ReturnValue_t Max31865RtdReader::periodicReadHandling() {
  using namespace MAX31865;
  auto result = returnvalue::OK;
  MutexGuard mg(readerMutex);
  if (mg.getLockResult() != returnvalue::OK) {
    sif::warning << "Max31865RtdReader::periodicReadReqHandling: Mutex lock failed" << std::endl;
    return returnvalue::FAILED;
  }
  // Now read the RTD values
  for (auto& rtd : rtds) {
    if (rtd == nullptr) {
      continue;
    }
    if (rtdIsActive(rtd->idx)) {
      uint16_t rtdVal = 0;
      bool faultBitSet = false;
      result = readRtdVal(rtd->spiCookie, rtdVal, faultBitSet);
      if (result != returnvalue::OK) {
        handleSpiError(rtd, result, "readRtdVal");
        return returnvalue::FAILED;
      }
      if (faultBitSet) {
        rtd->db.faultBitSet = faultBitSet;
      }
      rtd->db.adcCode = rtdVal;
    }
  }
#if OBSW_RTD_AUTO_MODE == 0
  for (auto& rtd : rtds) {
    if (rtd == nullptr) {
      continue;
    }
    // Even if a device was made inactive, turn off the bias here. If it was turned off, not
    // necessary anymore..
    if (rtd->on) {
      result = writeBiasSel(Bias::OFF, rtd->spiCookie, BASE_CFG);
    }
  }
#endif
  return returnvalue::OK;
}

ReturnValue_t Max31865RtdReader::initializeInterface(CookieIF* cookie) {
  if (cookie == nullptr) {
    throw std::invalid_argument("Invalid MAX31865 Reader Cookie");
  }
  auto* rtdCookie = dynamic_cast<Max31865ReaderCookie*>(cookie);
  ReturnValue_t result = comIF->initializeInterface(rtdCookie->spiCookie);
  if (result != returnvalue::OK) {
    return result;
  }
  if (rtdCookie->idx > EiveMax31855::NUM_RTDS) {
    throw std::invalid_argument("Invalid RTD index");
  }
  rtds[rtdCookie->idx] = rtdCookie;
  MutexGuard mg(readerMutex);
  if (dbLen == 0) {
    dbLen = rtdCookie->db.getSerializedSize();
  }
  return returnvalue::OK;
}

ReturnValue_t Max31865RtdReader::sendMessage(CookieIF* cookie, const uint8_t* sendData,
                                             size_t sendLen) {
  if (cookie == nullptr) {
    return returnvalue::FAILED;
  }
  // Empty command.. don't fail for now
  if (sendLen < 1) {
    return returnvalue::OK;
  }
  MutexGuard mg(readerMutex);
  if (mg.getLockResult() != returnvalue::OK) {
    sif::warning << "Max31865RtdReader::sendMessage: Mutex lock failed" << std::endl;
    return returnvalue::FAILED;
  }
  auto* rtdCookie = dynamic_cast<Max31865ReaderCookie*>(cookie);
  uint8_t cmdRaw = sendData[0];
  if (cmdRaw > EiveMax31855::RtdCommands::NUM_CMDS) {
    sif::warning << "Max31865RtdReader::sendMessage: Invalid command" << std::endl;
    return returnvalue::FAILED;
  }

  auto thresholdHandler = [](Max31865ReaderCookie* rtdCookie, const uint8_t* sendData) {
    rtdCookie->lowThreshold = (sendData[1] << 8) | sendData[2];
    rtdCookie->highThreshold = (sendData[3] << 8) | sendData[4];
    rtdCookie->writeLowThreshold = true;
    rtdCookie->writeHighThreshold = true;
  };

  auto cmd = static_cast<EiveMax31855::RtdCommands>(sendData[0]);
  switch (cmd) {
    case (EiveMax31855::RtdCommands::ON): {
      if (not rtdCookie->on) {
        rtdCookie->cd.setTimeout(MAX31865::WARMUP_MS);
        rtdCookie->cd.resetTimer();
        rtdCookie->on = true;
        rtdCookie->db.active = false;
        rtdCookie->db.configured = false;
        if (sendLen == 5) {
          thresholdHandler(rtdCookie, sendData);
        }
      }
      break;
    }
    case (EiveMax31855::RtdCommands::ACTIVE): {
      if (not rtdCookie->on) {
        rtdCookie->cd.setTimeout(MAX31865::WARMUP_MS);
        rtdCookie->cd.resetTimer();
        rtdCookie->on = true;
        rtdCookie->db.active = true;
        rtdCookie->db.configured = false;
      } else {
        rtdCookie->db.active = true;
      }
      if (sendLen == 5) {
        thresholdHandler(rtdCookie, sendData);
      }
      break;
    }
    case (EiveMax31855::RtdCommands::OFF): {
      rtdCookie->on = false;
      rtdCookie->db.active = false;
      rtdCookie->db.configured = false;
      break;
    }
    case (EiveMax31855::RtdCommands::HIGH_TRESHOLD): {
      if (sendLen == 3) {
        rtdCookie->highThreshold = (sendData[1] << 8) | sendData[2];
        rtdCookie->writeHighThreshold = true;
      } else {
        return returnvalue::FAILED;
      }
      break;
    }
    case (EiveMax31855::RtdCommands::LOW_THRESHOLD): {
      if (sendLen == 3) {
        rtdCookie->lowThreshold = (sendData[1] << 8) | sendData[2];
        rtdCookie->writeLowThreshold = true;
      } else {
        return returnvalue::FAILED;
      }
      break;
    }
    case (EiveMax31855::RtdCommands::CFG):
    default: {
      // TODO: Only implement if needed
      break;
    }
  }
  return returnvalue::OK;
}

ReturnValue_t Max31865RtdReader::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; }

ReturnValue_t Max31865RtdReader::requestReceiveMessage(CookieIF* cookie, size_t requestLen) {
  return returnvalue::OK;
}

ReturnValue_t Max31865RtdReader::readReceivedMessage(CookieIF* cookie, uint8_t** buffer,
                                                     size_t* size) {
  MutexGuard mg(readerMutex);
  if (mg.getLockResult() != returnvalue::OK) {
    // TODO: Emit warning
    return returnvalue::FAILED;
  }
  auto* rtdCookie = dynamic_cast<Max31865ReaderCookie*>(cookie);
  if (rtdCookie == nullptr) {
    return returnvalue::FAILED;
  }
  uint8_t* exchangePtr = rtdCookie->exchangeBuf.data();
  size_t serLen = 0;
  auto result = rtdCookie->db.serialize(&exchangePtr, &serLen, rtdCookie->exchangeBuf.size(),
                                        SerializeIF::Endianness::MACHINE);
  if (result != returnvalue::OK) {
    // TODO: Emit warning
    return returnvalue::FAILED;
  }
  *buffer = reinterpret_cast<uint8_t*>(rtdCookie->exchangeBuf.data());
  *size = serLen;
  return returnvalue::OK;
}

ReturnValue_t Max31865RtdReader::writeCfgReg(SpiCookie* cookie, uint8_t cfg) {
  using namespace MAX31865;
  return writeNToReg(cookie, CONFIG, 1, &cfg, nullptr);
}

ReturnValue_t Max31865RtdReader::writeBiasSel(MAX31865::Bias bias, SpiCookie* cookie,
                                              uint8_t baseCfg) {
  using namespace MAX31865;
  if (bias == MAX31865::Bias::OFF) {
    baseCfg &= ~(1 << CfgBitPos::BIAS_SEL);
  } else {
    baseCfg |= (1 << CfgBitPos::BIAS_SEL);
  }
  return writeCfgReg(cookie, baseCfg);
}

ReturnValue_t Max31865RtdReader::clearFaultStatus(SpiCookie* cookie) {
  using namespace MAX31865;
  // Read back the current configuration to avoid overwriting it when clearing te fault status
  uint8_t currentCfg = 0;
  auto result = readCfgReg(cookie, currentCfg);
  if (result != returnvalue::OK) {
    return result;
  }
  // Clear bytes 5, 3 and 2 which need to be 0
  currentCfg &= ~0x2C;
  currentCfg |= (1 << CfgBitPos::FAULT_STATUS_CLEAR);
  return writeCfgReg(cookie, currentCfg);
}

ReturnValue_t Max31865RtdReader::readCfgReg(SpiCookie* cookie, uint8_t& cfg) {
  using namespace MAX31865;
  uint8_t* replyPtr = nullptr;
  auto result = readNFromReg(cookie, CONFIG, 1, &replyPtr);
  if (result == returnvalue::OK) {
    cfg = replyPtr[0];
  }
  return result;
}

ReturnValue_t Max31865RtdReader::writeLowThreshold(SpiCookie* cookie, uint16_t val) {
  using namespace MAX31865;
  uint8_t cmd[2] = {static_cast<uint8_t>((val >> 8) & 0xff), static_cast<uint8_t>(val & 0xff)};
  return writeNToReg(cookie, LOW_THRESHOLD, 2, cmd, nullptr);
}

ReturnValue_t Max31865RtdReader::writeHighThreshold(SpiCookie* cookie, uint16_t val) {
  using namespace MAX31865;
  uint8_t cmd[2] = {static_cast<uint8_t>((val >> 8) & 0xff), static_cast<uint8_t>(val & 0xff)};
  return writeNToReg(cookie, HIGH_THRESHOLD, 2, cmd, nullptr);
}

ReturnValue_t Max31865RtdReader::readLowThreshold(SpiCookie* cookie, uint16_t& lowThreshold) {
  using namespace MAX31865;
  uint8_t* replyPtr = nullptr;
  auto result = readNFromReg(cookie, LOW_THRESHOLD, 2, &replyPtr);
  if (result == returnvalue::OK) {
    lowThreshold = (replyPtr[0] << 8) | replyPtr[1];
  }
  return result;
}

ReturnValue_t Max31865RtdReader::readHighThreshold(SpiCookie* cookie, uint16_t& highThreshold) {
  using namespace MAX31865;
  uint8_t* replyPtr = nullptr;
  auto result = readNFromReg(cookie, HIGH_THRESHOLD, 2, &replyPtr);
  if (result == returnvalue::OK) {
    highThreshold = (replyPtr[0] << 8) | replyPtr[1];
  }
  return result;
}

ReturnValue_t Max31865RtdReader::writeNToReg(SpiCookie* cookie, uint8_t reg, size_t n, uint8_t* cmd,
                                             uint8_t** reply) {
  using namespace MAX31865;
  if (n > cmdBuf.size() - 1) {
    return returnvalue::FAILED;
  }
  cmdBuf[0] = reg | WRITE_BIT;
  for (size_t idx = 0; idx < n; idx++) {
    cmdBuf[idx + 1] = cmd[idx];
  }
  return comIF->sendMessage(cookie, cmdBuf.data(), n + 1);
}

ReturnValue_t Max31865RtdReader::readRtdVal(SpiCookie* cookie, uint16_t& val, bool& faultBitSet) {
  using namespace MAX31865;
  uint8_t* replyPtr = nullptr;
  auto result = readNFromReg(cookie, RTD, 2, &replyPtr);
  if (result != returnvalue::OK) {
    return result;
  }
  if (replyPtr[1] & 0b0000'0001) {
    faultBitSet = true;
  }
  // Shift 1 to the right to remove fault bit
  val = ((replyPtr[0] << 8) | replyPtr[1]) >> 1;
  return result;
}

ReturnValue_t Max31865RtdReader::readNFromReg(SpiCookie* cookie, uint8_t reg, size_t n,
                                              uint8_t** reply) {
  using namespace MAX31865;
  if (n > 4) {
    return returnvalue::FAILED;
  }
  // Clear write bit in any case
  reg &= ~WRITE_BIT;
  cmdBuf[0] = reg;
  std::memset(cmdBuf.data() + 1, 0, n);
  ReturnValue_t result = comIF->sendMessage(cookie, cmdBuf.data(), n + 1);
  if (result != returnvalue::OK) {
    return returnvalue::FAILED;
  }

  size_t dummyLen = 0;
  uint8_t* replyPtr = nullptr;
  result = comIF->readReceivedMessage(cookie, &replyPtr, &dummyLen);
  if (result != returnvalue::OK) {
    return result;
  }
  if (reply != nullptr) {
    *reply = replyPtr + 1;
  }
  return returnvalue::OK;
}

ReturnValue_t Max31865RtdReader::handleSpiError(Max31865ReaderCookie* cookie, ReturnValue_t result,
                                                const char* ctx) {
  cookie->db.spiErrorCount.value += 1;
  sif::warning << "Max31865RtdReader::handleSpiError: " << ctx << " | Failed with result " << result
               << std::endl;
  return result;
}

ReturnValue_t Max31865RtdReader::initialize() {
  csLock = comIF->getCsMutex();
  return SystemObject::initialize();
}