/***************************************************************************

  BionxControl
  © 2025 -2026 christoph holzheuer
  christoph.holzheuer@gmail.com

  Using:

  mhs_can_drv.c
  © 2011 - 2023 by MHS-Elektronik GmbH & Co. KG, Germany
  Klaus Demlehner, klaus@mhs-elektronik.de
  @see www.mhs-elektronik.de

  Based on Bionx data type descriptions from:

  BigXionFlasher USB V 0.2.4 rev. 97
  © 2011-2013 by Thomas Koenig <info@bigxionflasher.org>
  @see www.bigxionflasher.org

  Bionx Bike Info
  © 2018 Thorsten Schmidt (tschmidt@ts-soft.de)
  @see www.ts-soft.de

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 3 of the License, or
  (at your option) any later version.
  @see https://github.com/bikemike/bionx-bikeinfo

 ***************************************************************************/


#include <QDebug>

#include <bcdrivertinycan.h>
#include <can_drv.h>


/**
 * @brief Destruktor. Entlädt den CAN-Bus Treiber wieder.
 */

BCDriverTinyCan::~BCDriverTinyCan()
{

  resetDriver();
}

/**
 * @brief BCDriverTinyCan::loadAndStartDriver
 * @return
 */

BCDriver::DriverStateResult BCDriverTinyCan::loadAndStartDriver()
{
  DriverStateResult result;
  if( _driverState < DriverState::Opened)
    result = loadDriver();
  if( _driverState == DriverState::Opened)
    result = setConsoleSlaveMode();
  return result;
}

/**
 * @brief BCDriverTinyCan::loadDriver
 * @return
 */

BCDriver::DriverStateResult BCDriverTinyCan::loadDriver()
{
  auto callLoadDriver = [&]() -> DriverStateResult
  {
    qDebug() << " -- Hier!";
    if( ::LoadDriver( NULL ) < 0 )
      return std::unexpected(QString("Driver Error: 'LoadDriver'"));
    _driverState = DriverState::Loaded;
    return _driverState;
  };

  auto callInitDriver = [&](DriverState state) -> DriverStateResult
  {
    Q_UNUSED(state)
    if( ::CanInitDriver( NULL ) < 0 )
      return std::unexpected(QString("Driver Error: 'InitDriver'"));
    _driverState =  DriverState::Initialized;
    return _driverState;
  };

  auto callOpenDevice = [&](DriverState state) -> DriverStateResult
  {
    Q_UNUSED(state)
    if( ::CanDeviceOpen( 0, NULL ) < 0 )
      return std::unexpected(QString("Driver Error: 'DeviceOpen'"));

    ::TDeviceStatus deviceStatus;

    ::CanSetSpeed( 0, CAN_125K_BIT );
    ::CanSetMode( 0, OP_CAN_START, CAN_CMD_ALL_CLEAR );
    ::CanGetDeviceStatus( 0, &deviceStatus );

    if( deviceStatus.DrvStatus < DRV_STATUS_CAN_OPEN )
      return std::unexpected(QString("Driver Error: could not open device." ));

    if( deviceStatus.CanStatus == CAN_STATUS_BUS_OFF )
    {
      ::CanSetMode( 0, OP_CAN_RESET, CAN_CMD_NONE );
      return std::unexpected(QString("Driver Error: CAN Status 'BusOff'" ));
    }
    _driverState = DriverState::Opened;
     return _driverState;
  };


  // #1. erstmal komplett zurücksetzen
  resetDriver();
  // #2. Treiber laden, initialisieren und
  //     mit dem tinyCan Interface verbinden.
  auto newDriverState = callLoadDriver()
    .and_then( callInitDriver )
    .and_then( callOpenDevice );

  // success:
  if(newDriverState)
  {
    // return 'DriverState::Opened'
    return _driverState;
  }
  // return driver error message,
  // _driverState ist irgendwo unter DriverState::Opened
  return newDriverState;

}

// __fix
BCDriver::DriverStateResult BCDriverTinyCan::setConsoleSlaveMode()
{

  uint32_t console   = static_cast<uint32_t>(BCDevice::ID::Console);
  uint8_t  slaveFlag = static_cast<uint8_t> (BC::ID::Cons_Status_Slave);

  qDebug() << "XXX BCDriverTinyCan::Driver Init: putting Console in slave mode ... ";

  TransmitResult isSlave = 0;
  // Already slave?
  isSlave = readRawByte( console, slaveFlag );
  if( isSlave.has_value() )
  {
    qDebug() << "Console responded: " << isSlave.value();
    if( isSlave.value() == 1 )
    {
      qDebug() << "Console already in slave mode. good!";
      return DriverState::DeviceReady;
    }
  }

  qDebug() << "BCDriverTinyCan::BCDriverTinyCan::XXX Driver Init: putting Console in slave mode ... ";

  unsigned int retry = cTimeOuts;


  do
  {
    writeRawByte( console, slaveFlag, 1 );
    isSlave = readRawByte( console, slaveFlag );
    bc::delay_millis( 200 );

  } while( retry-- && !(*isSlave) );

  bc::delay_millis( 500 ); // give the Console some time to settle
  //if( !isSlave )
  //emit statusHint( QString("putting Console in slave mode ") + (isSlave ? "done" : "failed") );

  // ist das jetzt irgendwie schlimm, wenn wir keine slave Console haben
  return isSlave ? DriverState::DeviceReady : DriverState::Opened;

}

void BCDriverTinyCan::resetDriver()
{
  if( _driverState > DriverState::NotPresent )
  {
    ::CanDownDriver();
    ::UnloadDriver();
    _driverState = DriverState::NotPresent;
  }
}

/**
 * @brief BCDriverTinyCan::readRawByte
 * Kapselt den Treiberzugiff über die legacy C-Api. Liest ein byte, gibt dieses im std::expected aber
 * als uint32_t zurück, um mit der ReadFunctions der ValueTypes kompatible zu sein.
 * Im Fehlerfall wird ein Fehlerstring zurückgegeben.
 */

TransmitResult BCDriverTinyCan::readRawByte( uint32_t deviceID, uint8_t registerID ) const
{

  qDebug() << " --- CAN Read Byte:  Device: "<< deviceID  << " register: " << registerID << " TRY! ";

  struct TCanMsg msg;
  int err, retry = 20;
  int timeout = 80;

  unsigned char receipient = (unsigned char) deviceID;
  unsigned char reg        = (unsigned char) registerID;

  msg.MsgFlags = 0L;
  msg.Id = receipient;
  msg.MsgLen = 2;
  msg.MsgData[0] = 0x00;
  msg.MsgData[1] = reg;

  CanTransmit(0, &msg, 1);

  while(timeout-- && CanTransmitGetCount(0))
     bc::delay_millis( cTIMEOUT_MS );

  if (timeout == -1)
    qDebug() << "error: could not send value to node ";

retry:

  timeout = 80;
  while(timeout-- && !CanReceiveGetCount(0))
     bc::delay_millis( cTIMEOUT_MS );

  if (timeout == -1)
  {
     qDebug() << "error: no response from node";
    return 0;
  }

  if ((err = CanReceive(0, &msg, 1)) > 0)
  {
    qDebug() << " retry: " << retry << " BIB:" << BC::ID::ID_Bib << " msg.Id: " << msg.Id << " msg.MsgLen: " << msg.MsgLen << " msg.MsgData[1]: " << msg.MsgData[1] << " reg: " << reg;
    if (--retry && (msg.Id != (uint32_t)BC::ID::ID_Bib|| msg.MsgLen != 4 || msg.MsgData[1] != reg))
      goto retry;

    if (!retry)
    {
       qDebug() << "XXX error: no response from node: " << err;
      return 0;
    }
    qDebug() << " --- CAN Read Byte:  Device: "<< deviceID  << " register: " << registerID << " BYTE: " << (uint32_t) msg.MsgData[3];
    return (unsigned int) msg.MsgData[3];
  }
  else
  {
     qDebug() << "Error:" <<err;
  }

  return 0;

  /*
  //TransmitResult
  qDebug() << " --- BCDriverTinyCan::readRawByte DriverState: " << getDriverState();

  if( _driverState <DriverState::Opened )
    return std::unexpected(QString("readRawValue error: driver not loaded." ) );

  unsigned char receipient = (unsigned char ) deviceID;
  ::TCanMsg msg;

  // msg verpacken
  msg.MsgFlags = 0L;
  msg.Id = receipient;//deviceID;
  msg.MsgLen = 2;
  msg.MsgData[0] = 0x00;
  msg.MsgData[1] = registerID;

  // msg verschicken
  ::CanTransmit( 0, &msg, 1 );

  int retries = cRetries; // 5?
  // cTimeOuts (== 20) mal cTIMEOUT_MS (== 10 ms ) Versuche ...
  int timeOuts = cTimeOuts; // 20 ?

  // ... warten bis der Sendepuffer leer ist
  while( timeOuts-- && ::CanTransmitGetCount( 0 ) )
    bc::delay_millis( cTIMEOUT_MS );

  if( timeOuts == -1 )
    return std::unexpected(QString("readRawValue error: could not send value" ));

retry:

  // cTimeOuts (== 20) mal cTIMEOUT_MS (== 10 ms ) Versuche ...
  timeOuts = cTimeOuts;
  // ... warten, bis der Empfangspuffer nicht mehr leer ist
  while( timeOuts-- && !::CanReceiveGetCount( 0 ) )
    bc::delay_millis( cTIMEOUT_MS );

  if( timeOuts == -1 )    
    return std::unexpected(QString("getValue error: no response from node" ));

  // message empfangen
  int err = ::CanReceive( 0, &msg, 1 );
  //qDebug() << "HÄÄ ?" << err << "reg: "<< registerID <<"  timeOuts: " << timeOuts;

  if( err < 0 )
    //throw BCException( "getValue error: could not receive value" );
    qDebug( "getValue error: could not receive value" );

  //qDebug() << "HÄÄ 2" <<msg.Id;
  //qDebug() << "HÄÄ 2" <<msg.MsgLen;
  //qDebug() << "HÄÄ 2" <<msg.MsgData[1];

  //if( err > 0 )
  if( --retries && ( msg.Id != BIB || msg.MsgLen != 4 || msg.MsgData[1] != registerID ) )
    goto retry;

  if( !timeOuts )
    return std::unexpected(QString("CAN response errror: timeout" ));

  qDebug() << " --- CAN Read Byte: " << (uint32_t) msg.MsgData[3] << " Device:: "<< deviceID  << " register: " << registerID;
  return (uint32_t) msg.MsgData[3];
*/
}


// void BCDriverTinyCan::setValue( unsigned char receipient, unsigned char reg, unsigned char value )
TransmitResult BCDriverTinyCan::writeRawByte( uint32_t deviceID, uint8_t registerID, uint8_t value ) const
{

  if( _driverState <DriverState::Opened )
    return std::unexpected(QString("readRawValue error: driver not loaded." ) );

  qDebug() << " --- BCDriverTinyCan writeRawValue: " << value;

  ::TCanMsg msg;
  int timeout_count = cTIMEOUT_COUNT;


  msg.MsgFlags = 0L;
  msg.Id = deviceID;
  msg.MsgLen = 4;
  msg.MsgData[0] = 0x00;
  msg.MsgData[1] = registerID;
  msg.MsgData[2] = 0x00;
  msg.MsgData[3] = value;

  ::CanTransmit( 0, &msg, 1 );

  while( timeout_count-- && ::CanTransmitGetCount( 0 ) )
    bc::delay_millis( cTIMEOUT_MS );

  if( timeout_count == -1 )
    return std::unexpected(QString("error: could not send value to %1" ).arg( deviceID ) );

  return uint32_t(1); // als 'true'
}

