/**
 * @file Decoder.hpp
 *
 * @brief Implements the Command Responses
 */

#pragma once

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <vector>

#include "CosmicNotifications.h"
#include "definitions.h"

inline void writeUint16LE(std::vector<uint8_t>& buffer, uint16_t value) {
  buffer.push_back(static_cast<uint8_t>(value & 0xFF));         // Low byte
  buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));  // High byte
}

inline void writeUint32LE(std::vector<uint8_t>& buffer, uint32_t value) {
  buffer.push_back(static_cast<uint8_t>(value & 0xFF));          // Byte 0 (least significant byte)
  buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));   // Byte 1
  buffer.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));  // Byte 2
  buffer.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));  // Byte 3 (most significant byte)
}

/**
 * Helper function to serialize an array of bytes as an uint32 value that represents the sub-address
 * value in I2C transfers. In other words, this function sorts the bytes to be sent over I2C in the 
 * same order as defined in the client code.
 *
 * For instance, if the client code defines: const std::vector<uint8_t> subaddress = {0x01, 0x02, 0x03};,
 * then the expected order in the I2C transfers is: S + Addr/W + 0x01 + 0x02 + 0x03
 * @param data The vector to write to
 * @param value The value to write
 */
inline uint32_t convertToUint32(const std::vector<uint8_t>& bytes) {
  uint32_t result = 0;
  for (size_t i = 0; i < bytes.size() && i < 4; ++i) {
    result = (result << 8) | static_cast<uint32_t>(bytes[i]);
  }
  return result;
}

// ---
// Command Responses
// ---

#pragma pack(push, 1)
struct CommandResponseHeader {
  uint16_t id;
  uint16_t code;
  uint16_t result;
};
#pragma pack(pop)

using NotificationHeader = CommandResponseHeader;

#define MAXIMUM_USB_STRING_LENGTH 50

#pragma pack(push, 1)
struct GetUsbStringResponse {
  CommandResponseHeader header;
  uint8_t length;
  uint8_t payload[MAXIMUM_USB_STRING_LENGTH];
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cSetBusVoltageResponse {
  CommandResponseHeader header;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cControllerInitResponse {
  CommandResponseHeader header;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cSetParametersResponse {
  CommandResponseHeader header;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cSetPullUpResistorsResponse {
  CommandResponseHeader header;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cTransferParameters {
  uint8_t bus;
  uint16_t payloadLength;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cTransferResponseHeader {
  CommandResponseHeader header;
  I2cTransferParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cTransferResponse {
  I2cTransferResponseHeader header;
  uint8_t payload[];
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cScanBusParameters {
  uint8_t bus;
  uint8_t count7BitAddresses;
  uint8_t count10BitAddresses;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cScanBusResponseHeader {
  CommandResponseHeader header;
  I2cScanBusParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I2cScanBusResponse {
  I2cScanBusResponseHeader header;
  uint8_t payload[];
};
#pragma pack(pop)

/*****************************************************
 * @section I3C
 ****************************************************/

#pragma pack(push, 1)
struct I3cGetTargetDevicesTableResponseParameters {
  uint8_t numberOfTargets;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cGetTargetDevicesTableResponseHeader {
  CommandResponseHeader header;
  I3cGetTargetDevicesTableResponseParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cGetTargetDevicesTableResponse {
  I3cGetTargetDevicesTableResponseHeader header;
  I3cTargetInfo payload[];
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cTransferResponseParameters {
  uint8_t targetAddress;
  uint8_t direction;
  uint16_t payloadLength;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cTransferResponseHeader {
  CommandResponseHeader header;
  I3cTransferResponseParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cTransferResponse {
  I3cTransferResponseHeader header;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cCCCTransferParameters {
  uint8_t CCC;
  uint8_t targetAddress;
  uint8_t direction;
  uint8_t payloadLength;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cCCCTransferResponseHeader {
  CommandResponseHeader header;
  I3cCCCTransferParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cCCCTransferResponse {
  I3cCCCTransferResponseHeader header;
  uint8_t payload[];
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cHJNotificationParameters {
  uint8_t pid[6];
  uint8_t bcr;
  uint8_t dcr;
  uint8_t dynamicAddress;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cHJNotificationHeader {
  NotificationHeader header;
  I3cHJNotificationParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cHJNotification {
  I3cHJNotificationHeader header;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cIBINotificationParameters {
  uint8_t address;
  uint8_t payloadLength;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cIBINotificationHeader {
  NotificationHeader header;
  I3cIBINotificationParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct I3cIBINotification {
  I3cIBINotificationHeader header;
  uint8_t payload[];
};
#pragma pack(pop)

/*****************************************************
 * @section GPIO
 ****************************************************/

#pragma pack(push, 1)
struct gpioDigitalReadParameters {
  uint8_t logicLevel;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct gpioDigitalReadResponseHeader {
  CommandResponseHeader header;
  gpioDigitalReadParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct gpioDigitalReadResponse {
  gpioDigitalReadResponseHeader header;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct GpioNotificationParameters {
  uint8_t pinNumber;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct GpioNotificationHeader {
  CommandResponseHeader header;
  GpioNotificationParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct GpioNotification {
  GpioNotificationHeader header;
};
#pragma pack(pop)

/*****************************************************
 * @section SPI
 ****************************************************/

#pragma pack(push, 1)
struct SpiTransferResponseParameters {
  uint16_t payloadLength;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct SpiTransferResponseHeader {
  CommandResponseHeader header;
  SpiTransferResponseParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct SpiTransferResponse {
  SpiTransferResponseHeader header;
  uint8_t payload[];
};
#pragma pack(pop)

/*****************************************************
 * @section UART
 ****************************************************/

#pragma pack(push, 1)
struct UartNotificationParameters {
  uint16_t payloadLength;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct UartNotificationHeader {
  NotificationHeader header;
  UartNotificationParameters parameters;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct UartNotification {
  UartNotificationHeader header;
  uint8_t payload[];
};
#pragma pack(pop)

/*****************************************************
 * @section Parsers
 ****************************************************/

/**
 * @brief Extracts the id field form the response.
 */
inline uint16_t parseTransactionId(const std::vector<uint8_t>& response) {
  return reinterpret_cast<const CommandResponseHeader*>(response.data())->id;
}

/**
 * @brief Return the notification type based on the code field of the response.
 */
inline NotificationType parseNotificationType(const std::vector<uint8_t>& response) {
  uint16_t code = reinterpret_cast<const NotificationHeader*>(response.data())->code;
  NotificationType eventType;
  switch (code) {
    case CMD_I3C_CONTROLLER_IBI_REQUEST_NOTIFICATION:
      eventType = NotificationType::NOTIF_I3C_IBI;
      break;
    case CMD_I3C_CONTROLLER_HJ_REQUEST_NOTIFICATION:
      eventType = NotificationType::NOTIF_I3C_HJ;
      break;
    case CMD_GPIO_INTERRUPT_NOTIFICATION:
      eventType = NotificationType::NOTIF_GPIO;
      break;
    case CMD_UART_NOTIFICATION:
      eventType = NotificationType::NOTIF_UART;
      break;
    default:
      eventType = NotificationType::NOTIF_UNKNOWN;
      break;
  }
  return eventType;
}
