#include "MgmLIS3MDLHandler.h"

#include <cmath>

#include "fsfw/datapool/PoolReadGuard.h"

MgmLIS3MDLHandler::MgmLIS3MDLHandler(object_id_t objectId, object_id_t deviceCommunication,
                                     CookieIF *comCookie, uint32_t transitionDelay)
    : DeviceHandlerBase(objectId, deviceCommunication, comCookie),
      dataset(this),
      transitionDelay(transitionDelay) {
  // Set to default values right away
  registers[0] = mgmLis3::CTRL_REG1_DEFAULT;
  registers[1] = mgmLis3::CTRL_REG2_DEFAULT;
  registers[2] = mgmLis3::CTRL_REG3_DEFAULT;
  registers[3] = mgmLis3::CTRL_REG4_DEFAULT;
  registers[4] = mgmLis3::CTRL_REG5_DEFAULT;
}

MgmLIS3MDLHandler::~MgmLIS3MDLHandler() {}

void MgmLIS3MDLHandler::doStartUp() {
  switch (internalState) {
    case (InternalState::STATE_NONE): {
      internalState = InternalState::STATE_FIRST_CONTACT;
      break;
    }
    case (InternalState::STATE_FIRST_CONTACT): {
      /* Will be set by checking device ID (WHO AM I register) */
      if (commandExecuted) {
        commandExecuted = false;
        internalState = InternalState::STATE_SETUP;
      }
      break;
    }
    case (InternalState::STATE_SETUP): {
      internalState = InternalState::STATE_CHECK_REGISTERS;
      break;
    }
    case (InternalState::STATE_CHECK_REGISTERS): {
      /* Set up cached registers which will be used to configure the MGM. */
      if (commandExecuted) {
        commandExecuted = false;
        if (goToNormalMode) {
          setMode(MODE_NORMAL);
        } else {
          setMode(_MODE_TO_ON);
        }
      }
      break;
    }
    default:
      break;
  }
}

void MgmLIS3MDLHandler::doShutDown() { setMode(_MODE_POWER_DOWN); }

ReturnValue_t MgmLIS3MDLHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) {
  switch (internalState) {
    case (InternalState::STATE_NONE):
    case (InternalState::STATE_NORMAL): {
      return DeviceHandlerBase::NOTHING_TO_SEND;
    }
    case (InternalState::STATE_FIRST_CONTACT): {
      *id = mgmLis3::IDENTIFY_DEVICE;
      break;
    }
    case (InternalState::STATE_SETUP): {
      *id = mgmLis3::SETUP_MGM;
      break;
    }
    case (InternalState::STATE_CHECK_REGISTERS): {
      *id = mgmLis3::READ_CONFIG_AND_DATA;
      break;
    }
    default: {
      /* might be a configuration error. */
#if FSFW_CPP_OSTREAM_ENABLED == 1
      sif::warning << "GyroHandler::buildTransitionDeviceCommand: Unknown internal state!"
                   << std::endl;
#else
      sif::printWarning("GyroHandler::buildTransitionDeviceCommand: Unknown internal state!\n");
#endif /* FSFW_CPP_OSTREAM_ENABLED == 1 */
      return returnvalue::OK;
    }
  }
  return buildCommandFromCommand(*id, NULL, 0);
}

void MgmLIS3MDLHandler::setupMgm() {
  registers[0] = mgmLis3::CTRL_REG1_DEFAULT;
  registers[1] = mgmLis3::CTRL_REG2_DEFAULT;
  registers[2] = mgmLis3::CTRL_REG3_DEFAULT;
  registers[3] = mgmLis3::CTRL_REG4_DEFAULT;
  registers[4] = mgmLis3::CTRL_REG5_DEFAULT;

  prepareCtrlRegisterWrite();
}

ReturnValue_t MgmLIS3MDLHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) {
  // Data/config register will be read in an alternating manner.
  if (communicationStep == CommunicationStep::DATA) {
    *id = mgmLis3::READ_CONFIG_AND_DATA;
    communicationStep = CommunicationStep::TEMPERATURE;
    return buildCommandFromCommand(*id, NULL, 0);
  } else {
    *id = mgmLis3::READ_TEMPERATURE;
    communicationStep = CommunicationStep::DATA;
    return buildCommandFromCommand(*id, NULL, 0);
  }
}

ReturnValue_t MgmLIS3MDLHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand,
                                                         const uint8_t *commandData,
                                                         size_t commandDataLen) {
  switch (deviceCommand) {
    case (mgmLis3::READ_CONFIG_AND_DATA): {
      std::memset(commandBuffer, 0, sizeof(commandBuffer));
      commandBuffer[0] = mgmLis3::readCommand(mgmLis3::CTRL_REG1, true);

      rawPacket = commandBuffer;
      rawPacketLen = mgmLis3::NR_OF_DATA_AND_CFG_REGISTERS + 1;
      return returnvalue::OK;
    }
    case (mgmLis3::READ_TEMPERATURE): {
      std::memset(commandBuffer, 0, 3);
      commandBuffer[0] = mgmLis3::readCommand(mgmLis3::TEMP_LOWBYTE, true);

      rawPacket = commandBuffer;
      rawPacketLen = 3;
      return returnvalue::OK;
    }
    case (mgmLis3::IDENTIFY_DEVICE): {
      return identifyDevice();
    }
    case (mgmLis3::TEMP_SENSOR_ENABLE): {
      return enableTemperatureSensor(commandData, commandDataLen);
    }
    case (mgmLis3::SETUP_MGM): {
      setupMgm();
      return returnvalue::OK;
    }
    case (mgmLis3::ACCURACY_OP_MODE_SET): {
      return setOperatingMode(commandData, commandDataLen);
    }
    default:
      return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED;
  }
  return returnvalue::FAILED;
}

ReturnValue_t MgmLIS3MDLHandler::identifyDevice() {
  uint32_t size = 2;
  commandBuffer[0] = mgmLis3::readCommand(mgmLis3::IDENTIFY_DEVICE_REG_ADDR);
  commandBuffer[1] = 0x00;

  rawPacket = commandBuffer;
  rawPacketLen = size;

  return returnvalue::OK;
}

ReturnValue_t MgmLIS3MDLHandler::scanForReply(const uint8_t *start, size_t len,
                                              DeviceCommandId_t *foundId, size_t *foundLen) {
  *foundLen = len;
  if (len == mgmLis3::NR_OF_DATA_AND_CFG_REGISTERS + 1) {
    *foundLen = len;
    *foundId = mgmLis3::READ_CONFIG_AND_DATA;
    // Check validity by checking config registers
    if (start[1] != registers[0] or start[2] != registers[1] or start[3] != registers[2] or
        start[4] != registers[3] or start[5] != registers[4]) {
#if FSFW_VERBOSE_LEVEL >= 1
#if FSFW_CPP_OSTREAM_ENABLED == 1
      sif::warning << "MGMHandlerLIS3MDL::scanForReply: Invalid registers!" << std::endl;
#else
      sif::printWarning("MGMHandlerLIS3MDL::scanForReply: Invalid registers!\n");
#endif
#endif
      return DeviceHandlerIF::INVALID_DATA;
    }
    if (getMode() == _MODE_START_UP) {
      commandExecuted = true;
    }

  } else if (len == mgmLis3::TEMPERATURE_REPLY_LEN) {
    *foundLen = len;
    *foundId = mgmLis3::READ_TEMPERATURE;
  } else if (len == mgmLis3::SETUP_REPLY_LEN) {
    *foundLen = len;
    *foundId = mgmLis3::SETUP_MGM;
  } else if (len == SINGLE_COMMAND_ANSWER_LEN) {
    *foundLen = len;
    *foundId = getPendingCommand();
    if (*foundId == mgmLis3::IDENTIFY_DEVICE) {
      if (start[1] != mgmLis3::DEVICE_ID) {
#if FSFW_VERBOSE_LEVEL >= 1
#if FSFW_CPP_OSTREAM_ENABLED == 1
        sif::warning << "MGMHandlerLIS3MDL::scanForReply: "
                        "Device identification failed!"
                     << std::endl;
#else
        sif::printWarning(
            "MGMHandlerLIS3MDL::scanForReply: "
            "Device identification failed!\n");
#endif
#endif
        return DeviceHandlerIF::INVALID_DATA;
      }

      if (getMode() == _MODE_START_UP) {
        commandExecuted = true;
      }
    }
  } else {
    return DeviceHandlerIF::INVALID_DATA;
  }

  /* Data with SPI Interface always has this answer */
  if (start[0] == 0b11111111) {
    return returnvalue::OK;
  } else {
    return DeviceHandlerIF::INVALID_DATA;
  }
}
ReturnValue_t MgmLIS3MDLHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) {
  switch (id) {
    case mgmLis3::IDENTIFY_DEVICE: {
      break;
    }
    case mgmLis3::SETUP_MGM: {
      break;
    }
    case mgmLis3::READ_CONFIG_AND_DATA: {
      using namespace mgmLis3;
      // TODO: Store configuration in new local datasets.
      float sensitivityFactor = getSensitivityFactor(getSensitivity(registers[2]));

      int16_t mgmMeasurementRawX =
          packet[mgmLis3::X_HIGHBYTE_IDX] << 8 | packet[mgmLis3::X_LOWBYTE_IDX];
      int16_t mgmMeasurementRawY =
          packet[mgmLis3::Y_HIGHBYTE_IDX] << 8 | packet[mgmLis3::Y_LOWBYTE_IDX];
      int16_t mgmMeasurementRawZ =
          packet[mgmLis3::Z_HIGHBYTE_IDX] << 8 | packet[mgmLis3::Z_LOWBYTE_IDX];

      // Target value in microtesla
      float mgmX = static_cast<float>(mgmMeasurementRawX) * sensitivityFactor *
                   mgmLis3::GAUSS_TO_MICROTESLA_FACTOR;
      float mgmY = static_cast<float>(mgmMeasurementRawY) * sensitivityFactor *
                   mgmLis3::GAUSS_TO_MICROTESLA_FACTOR;
      float mgmZ = static_cast<float>(mgmMeasurementRawZ) * sensitivityFactor *
                   mgmLis3::GAUSS_TO_MICROTESLA_FACTOR;

      if (periodicPrintout) {
        if (debugDivider.checkAndIncrement()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
          sif::info << "MGMHandlerLIS3: Magnetic field strength in"
                       " microtesla:"
                    << std::endl;
          sif::info << "X: " << mgmX << " uT" << std::endl;
          sif::info << "Y: " << mgmY << " uT" << std::endl;
          sif::info << "Z: " << mgmZ << " uT" << std::endl;
#else
          sif::printInfo("MGMHandlerLIS3: Magnetic field strength in microtesla:\n");
          sif::printInfo("X: %f uT\n", mgmX);
          sif::printInfo("Y: %f uT\n", mgmY);
          sif::printInfo("Z: %f uT\n", mgmZ);
#endif /* FSFW_CPP_OSTREAM_ENABLED == 0 */
        }
      }

      PoolReadGuard readHelper(&dataset);
      if (readHelper.getReadResult() == returnvalue::OK) {
        if (std::abs(mgmX) > absLimitX or std::abs(mgmY) > absLimitY or
            std::abs(mgmZ) > absLimitZ) {
          dataset.setIsValid = false;
        }
        if (std::abs(mgmX) < absLimitX) {
          dataset.fieldStrengths[0] = mgmX;
        }

        if (std::abs(mgmY) < absLimitY) {
          dataset.fieldStrengths[1] = mgmY;
        }

        if (std::abs(mgmZ) < absLimitZ) {
          dataset.fieldStrengths[2] = mgmZ;
        }
        dataset.setIsValid = true;
      }
      break;
    }

    case mgmLis3::READ_TEMPERATURE: {
      int16_t tempValueRaw = packet[2] << 8 | packet[1];
      float tempValue = 25.0 + ((static_cast<float>(tempValueRaw)) / 8.0);
      if (periodicPrintout) {
        if (debugDivider.check()) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
          sif::info << "MGMHandlerLIS3: Temperature: " << tempValue << " C" << std::endl;
#else
          sif::printInfo("MGMHandlerLIS3: Temperature: %f C\n");
#endif
        }
      }

      ReturnValue_t result = dataset.read();
      if (result == returnvalue::OK) {
        dataset.temperature = tempValue;
        dataset.commit();
      }
      break;
    }

    default: {
      return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY;
    }
  }
  return returnvalue::OK;
}

ReturnValue_t MgmLIS3MDLHandler::enableTemperatureSensor(const uint8_t *commandData,
                                                         size_t commandDataLen) {
  if (commandData == nullptr) {
    return INVALID_COMMAND_PARAMETER;
  }
  triggerEvent(CHANGE_OF_SETUP_PARAMETER);
  uint32_t size = 2;
  commandBuffer[0] = mgmLis3::writeCommand(mgmLis3::CTRL_REG1);
  if (commandDataLen > 1) {
    return INVALID_NUMBER_OR_LENGTH_OF_PARAMETERS;
  }
  switch (commandData[0]) {
    case (mgmLis3::ON): {
      commandBuffer[1] = registers[0] | (1 << 7);
      break;
    }
    case (mgmLis3::OFF): {
      commandBuffer[1] = registers[0] & ~(1 << 7);
      break;
    }
    default:
      return INVALID_COMMAND_PARAMETER;
  }
  registers[0] = commandBuffer[1];

  rawPacket = commandBuffer;
  rawPacketLen = size;

  return returnvalue::OK;
}

ReturnValue_t MgmLIS3MDLHandler::setOperatingMode(const uint8_t *commandData,
                                                  size_t commandDataLen) {
  triggerEvent(CHANGE_OF_SETUP_PARAMETER);
  if (commandDataLen != 1) {
    return INVALID_NUMBER_OR_LENGTH_OF_PARAMETERS;
  }

  switch (commandData[0]) {
    case mgmLis3::LOW:
      registers[0] = (registers[0] & (~(1 << mgmLis3::OM1))) & (~(1 << mgmLis3::OM0));
      registers[3] = (registers[3] & (~(1 << mgmLis3::OMZ1))) & (~(1 << mgmLis3::OMZ0));
      break;
    case mgmLis3::MEDIUM:
      registers[0] = (registers[0] & (~(1 << mgmLis3::OM1))) | (1 << mgmLis3::OM0);
      registers[3] = (registers[3] & (~(1 << mgmLis3::OMZ1))) | (1 << mgmLis3::OMZ0);
      break;

    case mgmLis3::HIGH:
      registers[0] = (registers[0] | (1 << mgmLis3::OM1)) & (~(1 << mgmLis3::OM0));
      registers[3] = (registers[3] | (1 << mgmLis3::OMZ1)) & (~(1 << mgmLis3::OMZ0));
      break;

    case mgmLis3::ULTRA:
      registers[0] = (registers[0] | (1 << mgmLis3::OM1)) | (1 << mgmLis3::OM0);
      registers[3] = (registers[3] | (1 << mgmLis3::OMZ1)) | (1 << mgmLis3::OMZ0);
      break;
    default:
      break;
  }

  return prepareCtrlRegisterWrite();
}

void MgmLIS3MDLHandler::fillCommandAndReplyMap() {
  insertInCommandAndReplyMap(mgmLis3::READ_CONFIG_AND_DATA, 1, &dataset);
  insertInCommandAndReplyMap(mgmLis3::READ_TEMPERATURE, 1);
  insertInCommandAndReplyMap(mgmLis3::SETUP_MGM, 1);
  insertInCommandAndReplyMap(mgmLis3::IDENTIFY_DEVICE, 1);
  insertInCommandAndReplyMap(mgmLis3::TEMP_SENSOR_ENABLE, 1);
  insertInCommandAndReplyMap(mgmLis3::ACCURACY_OP_MODE_SET, 1);
}

void MgmLIS3MDLHandler::setToGoToNormalMode(bool enable) { this->goToNormalMode = enable; }

ReturnValue_t MgmLIS3MDLHandler::prepareCtrlRegisterWrite() {
  commandBuffer[0] = mgmLis3::writeCommand(mgmLis3::CTRL_REG1, true);

  for (size_t i = 0; i < mgmLis3::NR_OF_CTRL_REGISTERS; i++) {
    commandBuffer[i + 1] = registers[i];
  }
  rawPacket = commandBuffer;
  rawPacketLen = mgmLis3::NR_OF_CTRL_REGISTERS + 1;

  // We dont have to check if this is working because we just did i
  return returnvalue::OK;
}

void MgmLIS3MDLHandler::doTransition(Mode_t modeFrom, Submode_t subModeFrom) {
  DeviceHandlerBase::doTransition(modeFrom, subModeFrom);
}

uint32_t MgmLIS3MDLHandler::getTransitionDelayMs(Mode_t from, Mode_t to) { return transitionDelay; }

void MgmLIS3MDLHandler::modeChanged(void) { internalState = InternalState::STATE_NONE; }

/*
ReturnValue_t MgmLIS3MDLHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap,
                                                         PeriodicHkGenerationHelper &poolManager) {
  localDataPoolMap.emplace(mgmLis3::FIELD_STRENGTHS, &mgmXYZ);
  localDataPoolMap.emplace(mgmLis3::TEMPERATURE_CELCIUS, &temperature);
  poolManager.setPeriodicFrequency(dataset.getSid(), 10'000);
  return returnvalue::OK;
}
 */

void MgmLIS3MDLHandler::setAbsoluteLimits(float xLimit, float yLimit, float zLimit) {
  this->absLimitX = xLimit;
  this->absLimitY = yLimit;
  this->absLimitZ = zLimit;
}

void MgmLIS3MDLHandler::enablePeriodicPrintouts(bool enable, uint8_t divider) {
  periodicPrintout = enable;
  debugDivider.setDivider(divider);
}