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

  BionxControl
  Copyright © 2025 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>





BCDriverTinyCan::BCDriverTinyCan( QObject* parent )
  : BCDriver(parent )
{

}


void BCDriverTinyCan::onStartDriver()
{
  if( _driverState < DriverState::Opened)
    loadDriver();
  if( _driverState == DriverState::Opened)
    _driverState = connectDriver();
}

void BCDriverTinyCan::loadDriver()
{


  auto callLoadDriver = []() -> DriverStateResult
  {
    if( ::LoadDriver( NULL ) < 0 )
      return std::unexpected(QString("Driver Error: 'LoadDriver'"));
    return DriverState::Loaded;
  };

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

  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'" ));
    }
    return DriverState::Opened;
  };

  auto newDriverState = callLoadDriver()
    .and_then( callInitDriver )
    .and_then( callOpenDevice );

  _driverState = DriverState::Error;
  QString message("Driver Ready.");

  if(newDriverState)
    _driverState = *newDriverState;
  else
      message = newDriverState.error();

  emit driverStateChanged( _driverState, message );
}

// __fix
BCDriver::DriverState BCDriverTinyCan::connectDriver()
{
  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 ... ";

  // Console already in slave mode. good!
  if( readRawByte( console, slaveFlag ) )
    return DriverState::DeviceReady;

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

  unsigned int retry = cTimeOuts;
  emit driverStateChanged( _driverState, "Driver Init: putting Console in slave mode ... " );
  TransmitResult isSlave = 0;
  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;

}


/*
    try
    {       
        initBCDevice::ID::Console();
    }

    catch( std::exception& except )
    {
        ::CanDownDriver();
        ::UnloadDriver();
    }
    */


/**
 * @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
{

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

  if( getDriverState() != DriverState::DeviceReady)
    return std::unexpected(QString("readRawValue error: driver not loaded." ) );

  ::TCanMsg msg;

  // msg verpacken
  msg.MsgFlags = 0L;
  msg.Id = 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" ));

  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( getDriverState() != DriverState::DeviceReady)
    return std::unexpected(QString("writeRawValue 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'
}

