/**
 * @file example.cpp
 * @brief I3C CCC API Example with Binho Supernova (Blocking C++ API)
 *
 * This example demonstrates Common Command Code (CCC) operations over I3C using:
 * - Binho Supernova
 * - Binho I3C Target Board
 * - STMicroelectronics STEVAL-MKI239A
 * - Bosch Sensortec BMI323 Shuttle Board 3.0
 *
 * Workflow:
 * 1. Connect to the adapter and print host device information.
 * 2. Set I3C VTARG, initialize the controller, and initialize the I3C bus.
 * 3. Retrieve target table and execute representative CCC commands:
 *    GETPID/GETBCR/GETDCR/GETCAPS, GET/SET MRL, GET/SET MWL,
 *    ENEC/DISEC, RSTACT, target reset pattern, and HDR exit pattern.
 * 4. Print returned data from CCC reads.
 */

#include <iomanip>
#include <iostream>
#include <vector>

#include "CosmicSDK.hpp"

void printTargetDeviceTable(std::vector<I3cTargetInfo> targetTable){
  for (uint8_t i = 0; i < targetTable.size(); i++) {
    std::cout << "\tStatic address:" << (int)targetTable[i].staticAddress << std::dec << std::endl;
    std::cout << "\tDynamic address: " << (int)targetTable[i].dynamicAddress << std::endl;

    std::cout << "\tPID: ";
    for (uint8_t j = 0; j < 6; ++j) {
        std::cout << (int)targetTable[i].pid[j] << " ";
    }
    std::cout << std::dec << std::endl;

    std::cout << "\tBCR: " << (int)targetTable[i].bcr.byte << std::endl;
    std::cout << "\tDCR: " << (int)targetTable[i].dcr << std::endl;
    std::cout << "\tMWL: " << (int)targetTable[i].mwl << std::endl;
    std::cout << "\tMRL: " << (int)targetTable[i].mrl << std::endl;
    std::cout << "\tMax IBI payload length: " << (int)targetTable[i].maxIbiPayloadLength << std::endl;

    std::cout << "\tTarget configuration:" << std::endl;
    std::cout << "\t\tTarget type: " << (targetTable[i].configuration.bits.targetType ? "I2C" : "I3C") << std::endl;
    std::cout << "\t\tInterrupt request: " << (targetTable[i].configuration.bits.IBIRequest ? "ACCEPT IBI" : "REJECT IBI") << std::endl;
    std::cout << "\t\tController Role request: " << (targetTable[i].configuration.bits.CRRequest ? "ACCEPT CCR" : "REJECT CCR") << std::endl;
    std::cout << "\t\tSETDASSA: " << (targetTable[i].configuration.bits.daaUseSETDASA ? "USE SETDASA" : "DO NOT USE SETDASA") << std::endl;
    std::cout << "\t\tSETDAASA: " << (targetTable[i].configuration.bits.daaUseSETAASA ? "USE SETAASA" : "DO NOT USE SETAASA") << std::endl;
    std::cout << "\t\tENTDAA: " << (targetTable[i].configuration.bits.daaUseENTDAA ? "USE ENTDAA" : "DO NOT USE ENTDAA") << std::endl;
    std::cout << "\t\tIBI Timestamp: " << (targetTable[i].configuration.bits.ibiTimestampEnable ? "ENABLE" : "DISABLE") << std::endl;
    std::cout << "\t\tPending Read capability: " << (targetTable[i].configuration.bits.pendingReadCapability ? "ENABLE" : "DISABLE") << std::endl;
    std::cout << "\t-----------------------------------------------" << std::endl;
  }
}

int main() {
  std::cout << "Starting main code" << std::endl;

  // Initialize the CosmicSDK device
  std::unique_ptr<CosmicSDK> device = std::make_unique<CosmicSDK>(2000);

  std::cout << "------------------------------------------------------------------------" << std::endl;
  std::cout << "------------------ Test connection and initialization ------------------" << std::endl;
  std::cout << "---------------------- Retrieve device information ---------------------" << std::endl;
  std::cout << "------------------------------------------------------------------------" << std::endl;

  // Connect to the device
  if (!device->connect()) {
    std::cerr << "Failed to connect to a Binho Device!" << std::endl;
    exit(1);
  }

  // Variable to hold the retrieved device information
  CosmicSDK::DeviceInfo deviceInformation;
  // Read device information
  if (device->getDeviceInfo(deviceInformation) != SUCCESS) {
    std::cerr << "Failed to read device information!" << std::endl;
    exit(1);
  }

  std::cerr << "Manufacturer: " << deviceInformation.manufacturer << std::endl;
  std::cerr << "Product name: " << deviceInformation.productName << std::endl;
  std::cerr << "Serial number: " << deviceInformation.serialNumber << std::endl;
  std::cerr << "FW version: " << deviceInformation.fwVersion << std::endl;
  std::cerr << "HW version: " << deviceInformation.hwVersion << std::endl;
  std::cout << "\n" << std::endl;

  // Set I3C bus voltage
  std::cout << "Setting voltage to 3.3v: ";
  uint16_t result_code = device->i3cSetBusVoltage(3300);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to set I3C bus voltage! ERROR CODE:" << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Init I3C peripheral
  std::cout << "Initializing I3C controller: ";
  result_code = device->i3cControllerInit(I3cPushPullRate::PUSH_PULL_10_MHz_DC_40, I3cOpenDrainTransferRate::OPEN_DRAIN_400_KHz, I2cTransferRate::FAST_MODE, I3cDriveStrength::FAST);
  if (result_code != SUCCESS && result_code != FW_INTERFACE_ALREADY_INITIALIZED) {
    std::cerr << "Failed to init the I3C peripheral! ERROR CODE:" << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Get I3C target devices table
  static std::vector<I3cTargetInfo> targetTable;
  std::cout << "Getting I3C target devices table: ";
  result_code = device->i3cGetDeviceTable(targetTable);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to get the I3C target devices table! ERROR CODE:" << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  printTargetDeviceTable(targetTable);
  
  // Init I3C bus
  std::cout << "Initializing I3C bus: ";
  result_code = device->i3cInitBus();
  if (result_code != SUCCESS) {
    std::cerr << "Failed to init the I3C bus! ERROR CODE:" << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  std::cout << "-------------------------------------------------------------------------" << std::endl;
  std::cout << "----------------------------- CCC Examples ------------------------------" << std::endl;
  std::cout << "-------------------------------------------------------------------------" << std::endl;

  // Get PID
  std::cout << "Getting PID: ";
  static std::vector<uint8_t> pid;
  result_code = device->i3cCccGETPID(0x09, pid);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain PID! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tPID: ";
  for (uint8_t i = 0; i < pid.size(); ++i) {
      std::cout << (int)pid[i] << " ";
  }
  std::cout << std::dec << std::endl;

  // Get BCR
  std::cout << "Getting BCR: ";
  uint8_t bcr;
  result_code = device->i3cCccGETBCR(0x09, bcr);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain BCR! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tBCR: ";
  std::cout << (int)bcr << std::endl;

  // Get DCR
  std::cout << "Getting DCR: ";
  uint8_t dcr;
  result_code = device->i3cCccGETDCR(0x09, dcr);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain DCR! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tDCR: ";
  std::cout << (int)dcr << std::endl;

  // Get CAPS
  std::cout << "Getting CAPS: ";
  static std::vector<uint8_t> caps;
  result_code = device->i3cCccGETCAPS(0x08, caps);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain CAPS! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tCAPS: ";
  for (uint8_t i = 0; i < caps.size(); ++i) {
      std::cout << (int)caps[i] << " ";
  }
  std::cout << std::dec << std::endl;

  // Get MRL
  std::cout << "Getting MRL 0x09: ";
  uint16_t mrl;
  std::optional<uint8_t> ibiLength;
  result_code = device->i3cCccGETMRL(0x09, mrl, ibiLength);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MRL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  printf("OK \n");
  std::cout << "\tMRL: " << (int)mrl << std::endl;
  std::cout << "\tIBI Length: " << (ibiLength.has_value() ? std::to_string((int)*ibiLength) : "None") << std::endl;

  // Set broadcast MRL
  std::cout << "Setting broadcast MRL=0x0F03: ";
  mrl = 0x0F03;
  ibiLength = 0x03;
  result_code = device->i3cCccSETMRLBroadcast(mrl, ibiLength);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to set MRL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  printf("OK \n");

  // Get MRL
  std::cout << "Getting MRL 0x08: ";
  result_code = device->i3cCccGETMRL(0x08, mrl, ibiLength);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MRL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMRL: " << (int)mrl << std::endl;
  std::cout << "\tIBI Length: " << (ibiLength.has_value() ? std::to_string((int)*ibiLength) : "None") << std::endl;
  // Get MRL
  std::cout << "Getting MRL 0x09: ";
  result_code = device->i3cCccGETMRL(0x09, mrl, ibiLength);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MRL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMRL: " << (int)mrl << std::endl;
  std::cout << "\tIBI Length: " << (ibiLength.has_value() ? std::to_string((int)*ibiLength) : "None") << std::endl;

  // Set direct MRL
  std::cout << "Setting direct MRL=0x0200 to 0x09: ";
  mrl = 0x0200;
  result_code = device->i3cCccSETMRLDirect(0x09, mrl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to set MRL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

    // Get MRL
  std::cout << "Getting MRL 0x08: ";
  result_code = device->i3cCccGETMRL(0x08, mrl, ibiLength);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MRL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMRL: " << (int)mrl << std::endl;
  std::cout << "\tIBI Length: " << (ibiLength.has_value() ? std::to_string((int)*ibiLength) : "None") << std::endl;
  // Get MRL
  std::cout << "Getting MRL 0x09: ";
  result_code = device->i3cCccGETMRL(0x09, mrl, ibiLength);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MRL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMRL: " << (int)mrl << std::endl;
  std::cout << "\tIBI Length: " << (ibiLength.has_value() ? std::to_string((int)*ibiLength) : "None") << std::endl;
  

  // Get MWL
  std::cout << "Getting MWL 0x09: ";
  uint16_t mwl;
  result_code = device->i3cCccGETMWL(0x09, mwl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MWL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMWL: " << (int)mwl << std::endl;

  // Set broadcast MWL
  std::cout << "Setting broadcast MWL=0x0F03: ";
  mwl = 0x0F03;
  result_code = device->i3cCccSETMWLBroadcast(mwl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to set MWL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Get MWL
  std::cout << "Getting MWL 0x08: ";
  result_code = device->i3cCccGETMWL(0x08, mwl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MWL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMWL: " << (int)mwl << std::endl;
  // Get MWL
  std::cout << "Getting MWL 0x09: ";
  result_code = device->i3cCccGETMWL(0x09, mwl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MWL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMWL: " << (int)mwl << std::endl;

  // Set direct MWL
  std::cout << "Setting direct MWL=0x0200 to 0x08: ";
  mwl = 0x0200;
  result_code = device->i3cCccSETMWLDirect(0x08, mwl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to set MWL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

    // Get MWL
  std::cout << "Getting MWL 0x08: ";
  result_code = device->i3cCccGETMWL(0x08, mwl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MWL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMWL: " << (int)mwl << std::endl;
  // Get MWL
  std::cout << "Getting MWL 0x09: ";
  result_code = device->i3cCccGETMWL(0x09, mwl);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to obtain MWL! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tMWL: " << (int)mwl << std::endl;  

  // Broadcast DISEC
  std::cout << "Sending CCC Broadcast DISEC: ";
  result_code = device->i3cCccDISECBroadcast(IBI | CR | HJ);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to perform Broadcast DISEC! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Direct DISEC
  std::cout << "Sending CCC Direct DISEC: ";
  result_code = device->i3cCccDISECDirect(0x09,  CR | HJ);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to perform Direct DISEC! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Broadcast ENEC
  std::cout << "Sending CCC Broadcast ENEC: ";
  result_code = device->i3cCccENECBroadcast(IBI | CR | HJ);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to perform Broadcast ENEC! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Direct ENEC
  std::cout << "Sending CCC Direct ENEC: ";
  result_code = device->i3cCccENECDirect(0x09, CR | HJ);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to perform Direct ENEC! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Direct RSTACT
  std::cout << "Sending CCC Direct RSTACT: ";
  uint8_t outValue;
  result_code = device->i3cCccRSTACTDirect(0x08, I3cTargetResetDefByte::NO_RESET, I3cTransferDirection::READ, &outValue);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to perform Direct RSTACT! ERROR CODE: " << result_code << std::endl;
    exit(1);;
  }
  std::cout << "OK" << std::endl;
  std::cout << "\tData: " << (int)outValue << std::endl;
    
  // Broadcast RSTACT
  std::cout << "Sending CCC Broadcast RSTACT: ";
  result_code = device->i3cCccRSTACTBroadcast(I3cTargetResetDefByte::NO_RESET);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to perform Broadcast RSTACT! ERROR CODE: " << result_code << std::endl;
    exit(1);;
  }
  std::cout << "OK" << std::endl;

  // Direct RSTACT
  std::cout << "Sending CCC Direct RSTACT: ";
  result_code = device->i3cCccRSTACTDirect(0x09, I3cTargetResetDefByte::RESET_WHOLE_TARGET, I3cTransferDirection::WRITE, &outValue);
  if (result_code != SUCCESS) {
    std::cerr << "Failed to perform Direct RSTACT! ERROR CODE: " << result_code << std::endl;
    exit(1);;
  }
  std::cout << "OK" << std::endl;

  // Send Reset Pattern
  std::cout << "Sending Reset Pattern: ";
  result_code = device->i3cTargetResetPattern();
  if (result_code != SUCCESS) {
    std::cerr << "Failed to reset pattern! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Send Exit Pattern
  std::cout << "Sending Exit Pattern: ";
  result_code = device->i3cHdrExitPattern();
  if (result_code != SUCCESS) {
    std::cerr << "Failed to exit pattern! ERROR CODE: " << result_code << std::endl;
    exit(1);
  }
  std::cout << "OK" << std::endl;

  // Disconnect the device
  device->disconnect();
  
  std::cout << "Finished example!" << std::endl;
}
