#include "PlocMPSoCHelper.h"

#include <filesystem>
#include <fstream>

#include "OBSWConfig.h"
#ifdef XIPHOS_Q7S
#include "bsp_q7s/fs/FilesystemHelper.h"
#endif

#include "mission/utility/Timestamp.h"

using namespace ploc;

PlocMPSoCHelper::PlocMPSoCHelper(object_id_t objectId) : SystemObject(objectId) {
  spParams.buf = commandBuffer;
  spParams.maxSize = sizeof(commandBuffer);
}

PlocMPSoCHelper::~PlocMPSoCHelper() {}

ReturnValue_t PlocMPSoCHelper::initialize() {
#ifdef XIPHOS_Q7S
  sdcMan = SdCardManager::instance();
  if (sdcMan == nullptr) {
    sif::warning << "PlocMPSoCHelper::initialize: Invalid SD Card Manager" << std::endl;
    return returnvalue::FAILED;
  }
#endif
  return returnvalue::OK;
}

ReturnValue_t PlocMPSoCHelper::performOperation(uint8_t operationCode) {
  ReturnValue_t result = returnvalue::OK;
  semaphore.acquire();
  while (true) {
    switch (internalState) {
      case InternalState::IDLE: {
        semaphore.acquire();
        break;
      }
      case InternalState::FLASH_WRITE: {
        result = performFlashWrite();
        if (result == returnvalue::OK) {
          triggerEvent(MPSOC_FLASH_WRITE_SUCCESSFUL);
        } else {
          triggerEvent(MPSOC_FLASH_WRITE_FAILED);
        }
        internalState = InternalState::IDLE;
        break;
      }
      default:
        sif::debug << "PlocMPSoCHelper::performOperation: Invalid state" << std::endl;
        break;
    }
  }
}

ReturnValue_t PlocMPSoCHelper::setComIF(DeviceCommunicationIF* communicationInterface_) {
  uartComIF = dynamic_cast<SerialComIF*>(communicationInterface_);
  if (uartComIF == nullptr) {
    sif::warning << "PlocMPSoCHelper::initialize: Invalid uart com if" << std::endl;
    return returnvalue::FAILED;
  }
  return returnvalue::OK;
}

void PlocMPSoCHelper::setComCookie(CookieIF* comCookie_) { comCookie = comCookie_; }

void PlocMPSoCHelper::setSequenceCount(SourceSequenceCounter* sequenceCount_) {
  sequenceCount = sequenceCount_;
}

ReturnValue_t PlocMPSoCHelper::startFlashWrite(std::string obcFile, std::string mpsocFile) {
  ReturnValue_t result = returnvalue::OK;
#ifdef XIPHOS_Q7S
  result = FilesystemHelper::checkPath(obcFile);
  if (result != returnvalue::OK) {
    return result;
  }
  result = FilesystemHelper::fileExists(mpsocFile);
  if (result != returnvalue::OK) {
    return result;
  }
#endif
#ifdef TE0720_1CFA
  if (not std::filesystem::exists(obcFile)) {
    sif::warning << "PlocMPSoCHelper::startFlashWrite: File " << obcFile << "does not exist"
                 << std::endl;
    return returnvalue::FAILED;
  }
#endif

  flashWrite.obcFile = obcFile;
  flashWrite.mpsocFile = mpsocFile;
  internalState = InternalState::FLASH_WRITE;
  result = resetHelper();
  if (result != returnvalue::OK) {
    return result;
  }
  return result;
}

ReturnValue_t PlocMPSoCHelper::resetHelper() {
  ReturnValue_t result = returnvalue::OK;
  semaphore.release();
  spParams.buf = commandBuffer;
  terminate = false;
  result = uartComIF->flushUartRxBuffer(comCookie);
  return result;
}

void PlocMPSoCHelper::stopProcess() { terminate = true; }

ReturnValue_t PlocMPSoCHelper::performFlashWrite() {
  ReturnValue_t result = returnvalue::OK;
  result = flashfopen();
  if (result != returnvalue::OK) {
    return result;
  }
  uint8_t tempData[mpsoc::MAX_DATA_SIZE];
  std::ifstream file(flashWrite.obcFile, std::ifstream::binary);
  // Set position of next character to end of file input stream
  file.seekg(0, file.end);
  // tellg returns position of character in input stream
  size_t remainingSize = file.tellg();
  size_t dataLength = 0;
  size_t bytesRead = 0;
  while (remainingSize > 0) {
    if (terminate) {
      return returnvalue::OK;
    }
    if (remainingSize > mpsoc::MAX_DATA_SIZE) {
      dataLength = mpsoc::MAX_DATA_SIZE;
    } else {
      dataLength = remainingSize;
    }
    if (file.is_open()) {
      file.seekg(bytesRead, file.beg);
      file.read(reinterpret_cast<char*>(tempData), dataLength);
      bytesRead += dataLength;
      remainingSize -= dataLength;
    } else {
      return FILE_CLOSED_ACCIDENTALLY;
    }
    (*sequenceCount)++;
    mpsoc::TcFlashWrite tc(spParams, *sequenceCount);
    result = tc.buildPacket(tempData, dataLength);
    if (result != returnvalue::OK) {
      return result;
    }
    result = handlePacketTransmission(tc);
    if (result != returnvalue::OK) {
      return result;
    }
  }
  result = flashfclose();
  if (result != returnvalue::OK) {
    return result;
  }
  return result;
}

ReturnValue_t PlocMPSoCHelper::flashfopen() {
  ReturnValue_t result = returnvalue::OK;
  spParams.buf = commandBuffer;
  (*sequenceCount)++;
  mpsoc::FlashFopen flashFopen(spParams, *sequenceCount);
  result = flashFopen.createPacket(flashWrite.mpsocFile, mpsoc::FlashFopen::APPEND);
  if (result != returnvalue::OK) {
    return result;
  }
  result = handlePacketTransmission(flashFopen);
  if (result != returnvalue::OK) {
    return result;
  }
  return returnvalue::OK;
}

ReturnValue_t PlocMPSoCHelper::flashfclose() {
  ReturnValue_t result = returnvalue::OK;
  spParams.buf = commandBuffer;
  (*sequenceCount)++;
  mpsoc::FlashFclose flashFclose(spParams, *sequenceCount);
  result = flashFclose.createPacket(flashWrite.mpsocFile);
  if (result != returnvalue::OK) {
    return result;
  }
  result = handlePacketTransmission(flashFclose);
  if (result != returnvalue::OK) {
    return result;
  }
  return returnvalue::OK;
}

ReturnValue_t PlocMPSoCHelper::handlePacketTransmission(ploc::SpTcBase& tc) {
  ReturnValue_t result = returnvalue::OK;
  result = sendCommand(tc);
  if (result != returnvalue::OK) {
    return result;
  }
  result = handleAck();
  if (result != returnvalue::OK) {
    return result;
  }
  result = handleExe();
  if (result != returnvalue::OK) {
    return result;
  }
  return returnvalue::OK;
}

ReturnValue_t PlocMPSoCHelper::sendCommand(ploc::SpTcBase& tc) {
  ReturnValue_t result = returnvalue::OK;
  result = uartComIF->sendMessage(comCookie, tc.getFullPacket(), tc.getFullPacketLen());
  if (result != returnvalue::OK) {
    sif::warning << "PlocMPSoCHelper::sendCommand: Failed to send command" << std::endl;
    triggerEvent(MPSOC_SENDING_COMMAND_FAILED, result, static_cast<uint32_t>(internalState));
    return result;
  }
  return result;
}

ReturnValue_t PlocMPSoCHelper::handleAck() {
  ReturnValue_t result = returnvalue::OK;
  result = handleTmReception(mpsoc::SIZE_ACK_REPORT);
  if (result != returnvalue::OK) {
    return result;
  }
  SpTmReader tmPacket(tmBuf.data(), tmBuf.size());
  result = checkReceivedTm(tmPacket);
  if (result != returnvalue::OK) {
    return result;
  }
  uint16_t apid = tmPacket.getApid();
  if (apid != mpsoc::apid::ACK_SUCCESS) {
    handleAckApidFailure(apid);
    return returnvalue::FAILED;
  }
  return returnvalue::OK;
}

void PlocMPSoCHelper::handleAckApidFailure(uint16_t apid) {
  if (apid == mpsoc::apid::ACK_FAILURE) {
    triggerEvent(MPSOC_ACK_FAILURE_REPORT, static_cast<uint32_t>(internalState));
    sif::warning << "PlocMPSoCHelper::handleAckApidFailure: Received acknowledgement failure "
                 << "report" << std::endl;
  } else {
    triggerEvent(MPSOC_ACK_INVALID_APID, apid, static_cast<uint32_t>(internalState));
    sif::warning << "PlocMPSoCHelper::handleAckApidFailure: Expected acknowledgement report "
                 << "but received space packet with apid " << std::hex << apid << std::endl;
  }
}

ReturnValue_t PlocMPSoCHelper::handleExe() {
  ReturnValue_t result = returnvalue::OK;

  result = handleTmReception(mpsoc::SIZE_EXE_REPORT);
  if (result != returnvalue::OK) {
    return result;
  }
  ploc::SpTmReader tmPacket(tmBuf.data(), tmBuf.size());
  result = checkReceivedTm(tmPacket);
  if (result != returnvalue::OK) {
    return result;
  }
  uint16_t apid = tmPacket.getApid();
  if (apid != mpsoc::apid::EXE_SUCCESS) {
    handleExeApidFailure(apid);
    return returnvalue::FAILED;
  }
  return returnvalue::OK;
}

void PlocMPSoCHelper::handleExeApidFailure(uint16_t apid) {
  if (apid == mpsoc::apid::EXE_FAILURE) {
    triggerEvent(MPSOC_EXE_FAILURE_REPORT, static_cast<uint32_t>(internalState));
    sif::warning << "PlocMPSoCHelper::handleExeApidFailure: Received execution failure "
                 << "report" << std::endl;
  } else {
    triggerEvent(MPSOC_EXE_INVALID_APID, apid, static_cast<uint32_t>(internalState));
    sif::warning << "PlocMPSoCHelper::handleExeApidFailure: Expected execution report "
                 << "but received space packet with apid " << std::hex << apid << std::endl;
  }
}

ReturnValue_t PlocMPSoCHelper::handleTmReception(size_t remainingBytes) {
  ReturnValue_t result = returnvalue::OK;
  size_t readBytes = 0;
  size_t currentBytes = 0;
  for (int retries = 0; retries < RETRIES; retries++) {
    result = receive(tmBuf.data() + readBytes, &currentBytes, remainingBytes);
    if (result != returnvalue::OK) {
      return result;
    }
    readBytes += currentBytes;
    remainingBytes = remainingBytes - currentBytes;
    if (remainingBytes == 0) {
      break;
    }
  }
  if (remainingBytes != 0) {
    sif::warning << "PlocMPSoCHelper::handleTmReception: Failed to receive reply" << std::endl;
    triggerEvent(MPSOC_MISSING_EXE, remainingBytes, static_cast<uint32_t>(internalState));
    return returnvalue::FAILED;
  }
  return result;
}

ReturnValue_t PlocMPSoCHelper::checkReceivedTm(SpTmReader& reader) {
  ReturnValue_t result = reader.checkSize();
  if (result != returnvalue::OK) {
    sif::error << "PlocMPSoCHelper::handleTmReception: Size check on received TM failed"
               << std::endl;
    triggerEvent(MPSOC_TM_SIZE_ERROR);
    return result;
  }
  reader.checkCrc();
  if (result != returnvalue::OK) {
    sif::warning << "PlocMPSoCHelper::handleTmReception: CRC check failed" << std::endl;
    triggerEvent(MPSOC_TM_CRC_MISSMATCH, *sequenceCount);
    return result;
  }
  (*sequenceCount)++;
  uint16_t recvSeqCnt = reader.getSequenceCount();
  if (recvSeqCnt != *sequenceCount) {
    triggerEvent(MPSOC_HELPER_SEQ_CNT_MISMATCH, *sequenceCount, recvSeqCnt);
    *sequenceCount = recvSeqCnt;
  }
  return returnvalue::OK;
}

ReturnValue_t PlocMPSoCHelper::receive(uint8_t* data, size_t* readBytes, size_t requestBytes) {
  ReturnValue_t result = returnvalue::OK;
  uint8_t* buffer = nullptr;
  result = uartComIF->requestReceiveMessage(comCookie, requestBytes);
  if (result != returnvalue::OK) {
    sif::warning << "PlocMPSoCHelper::receive: Failed to request reply" << std::endl;
    triggerEvent(MPSOC_HELPER_REQUESTING_REPLY_FAILED, result,
                 static_cast<uint32_t>(static_cast<uint32_t>(internalState)));
    return returnvalue::FAILED;
  }
  result = uartComIF->readReceivedMessage(comCookie, &buffer, readBytes);
  if (result != returnvalue::OK) {
    sif::warning << "PlocMPSoCHelper::receive: Failed to read received message" << std::endl;
    triggerEvent(MPSOC_HELPER_READING_REPLY_FAILED, result, static_cast<uint32_t>(internalState));
    return returnvalue::FAILED;
  }
  if (*readBytes > 0) {
    std::memcpy(data, buffer, *readBytes);
  }
  return result;
}