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

  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 <QFile>
#include <QFileDialog>
#include <QTableView>
#include <QPushButton>
#include <QMessageBox>
#include <QStatusBar>
#include <QApplication>
#include <QElapsedTimer>

#include <bcdatamanager.h>
#include <bcvaluetype.h>

using namespace Qt::StringLiterals;



BCDataManager::BCDataManager(QObject *parent)
  : QObject(parent)
{

  //qRegisterMetaType<BCDataValue*>("BCDataValue*");
  qRegisterMetaType<BCDataValue*>();

  _transmitter.moveToThread(&_worker);

  connect(this, &BCDataManager::sendValueCommand, &_transmitter, &BCTransmitter::enqueueValueOp);

  // B) Ergebnisse empfangen (Runner -> Manager)
  //connect(&_transmitter, &BCTransmitter::commandFinished, this, &BCDataManager::onCommandFinished);
  //connect(&_transmitter, &BCTransmitter::messageLogged, this, &BCDataManager::onRunnerMessage);

  // C) Aufräumen: Wenn Thread endet, lösche den Runner
  connect(&_worker, &QThread::finished, &_transmitter, &QObject::deleteLater);

  // 5. Thread starten
  _worker.start();
}

BCDataManager::~BCDataManager()
{
  // nothing to do here for now,
  // our models are autokilled.

  _worker.quit();       // Event Loop stoppen
  _worker.wait();       // Warten bis Thread wirklich fertig ist
}


void BCDataManager::onCommandFinished(int id, bool success)
{
  qDebug() << "[Manager] Command" << id << "finished. Success:" << success;
}

void BCDataManager::onRunnerMessage(const QString &msg)
{
  qDebug() << "[Worker says]:" << msg;
}



void BCDataManager::onSyncFromDevice()
{
  qDebug() << " ---Syncing";
  if( _currentDeviceID != BCDevice::ID::Invalid )
  {
    if( _valueModels.contains(_currentDeviceID) )
    {

      BCDataModel* model = _valueModels[_currentDeviceID];
      BCDataList& currentList = model->getValueList();

      //BCDataValue& value = currentList[4];

      for( const BCDataValue& value : currentList )
      {
        qDebug() << " --- value: " << value.label;

        // statt '_transmitter.enqueueValueCommand( value )' entkoppeln
        // wir das eleganter über emit sendValueCommand()

        //_transmitter.enqueueValueCommand( value );
        emit sendValueCommand( BC::OpID::ReadValue, &value);

        emit valueTouched( value.rowInModel );

        bc::processEventsFor(500);

        //QApplication::processEvents();
        // Thread schlafen lassen (Simulation einer blockierenden Operation)
        //QThread::msleep(500);

      }
    } // if contains
  }
}

std::optional<BCDataModel*> BCDataManager::getModel(BCDevice::ID deviceID )
{
  if( _valueModels.contains( deviceID) )
    return _valueModels[deviceID];
  return std::nullopt;
}

BCTransmitter* BCDataManager::getTransmitter()
{
  return &_transmitter;
};

void BCDataManager::loadXmlBikeData()
{
  auto printAttrs = [](const QXmlStreamReader& xml)
  {
    QStringList parts;
    for (const auto &attr : xml.attributes()) {
      parts << attr.name().toString() + "=\"" + attr.value().toString() + "\"";
    }
    qDebug().noquote() << parts.join(" ");
  };
  /*
  QString fileName = QFileDialog::getOpenFileName(this, "XML öffnen", "", "XML Files (*.xml)");
  if (fileName.isEmpty())
    return;
  */

  QFile file(":/bikeinfo.xml");
  if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
  {
    // __fix throw
    QMessageBox::warning(nullptr, "Fehler", "Datei konnte nicht geöffnet werden.");
    return;
  }

  _xml.setDevice(&file);

  if (_xml.readNextStartElement())
  {
    if (_xml.name() != "Bike"_L1 )
      // fix throw
      _xml.raiseError(QObject::tr("The file is not an 'Bike' file."));
  }
  // ??
  Q_ASSERT(_xml.isStartElement() && _xml.name() == "Bike"_L1);

  while (!_xml.atEnd() && !_xml.hasError())
  {
    QXmlStreamReader::TokenType token = _xml.readNext();
    if (token == QXmlStreamReader::StartElement)
    {
      QString deviceType = _xml.attributes().value("Type"_L1).toString();
      printAttrs (_xml);
      const char* deviceKey = _xml.attributes().value("Type"_L1).toLatin1().constData();
      auto deviceID = _bcDeviceEnum.keyToValue64(deviceKey);
      //_currentDeviceID = BCDevice::ID( deviceID.value_or( BCDevice::ID::Invalid ) );
      if(deviceID.has_value())
      {
        qDebug() << " --- Device: " << _xml.name() << ": " << deviceType << " : " << deviceID;

        _currentDeviceID = BCDevice::ID( deviceID.value() );
        BCDataList parsedValues;
        loadXmlBikeDeviceData( parsedValues );
        if( parsedValues.count() )
        {
          BCDataModel* valueModel = new BCDataModel( this );
          valueModel->setValueList(parsedValues);
          _valueModels.insert( _currentDeviceID, valueModel );
        }
      }

    }

    _currentDeviceID = BCDevice::ID::Console;
  }

  /*
  if (xml.hasError())
  {
    QMessageBox::critical(nullptr, "Parsing Fehler", xml.errorString());
  }
  else
  {
    m_model->setDevices(parsedValues);
  }
  */

  // create & add new model to the model map

}

void BCDataManager::loadXmlBikeDeviceData( BCDataList& parsedValues )
{
  auto printAttrs = [](const QXmlStreamReader& xml)
  {
    QStringList parts;
    for (const auto &attr : xml.attributes()) {
      parts << attr.name().toString() + "=\"" + attr.value().toString() + "\"";
    }
    qDebug().noquote() << parts.join(" ");
  };

  printAttrs (_xml);
  Q_ASSERT(_xml.isStartElement() && _xml.name() == "Device"_L1);
  qDebug() << " ---------------";


  //while (!_xml.atEnd() && !_xml.hasError())
  while( _xml.readNextStartElement() )
  {
    if( _xml.attributes().hasAttribute(BCTags::ID) )
    {
      //qDebug() << " --- found: " << _xml.name() << " : " << _xml.attributes().value(BCTags::ID);

      QString id = _xml.attributes().value(BCTags::ID).toString();
      BCDataParams params
      {
        .ID       = id,
        .Label    = _xml.attributes().value(BCTags::Label).toString(),
        .Default  = _xml.attributes().value(BCTags::Default).toString(),
        .UnitType = _xml.attributes().value(BCTags::UnitType).toString(),
      };

      // __fix! können ungültige werte erzeugt werden ?
      //BCDataValue newValue = BCData::makeDataValue( _currentDeviceID, params );
      //if(newValue)
      //  parsedValues.push_back( newValue );
      std::optional<BCDataValue> newValue = makeDataValue( _currentDeviceID, params );
      if(newValue)
        parsedValues.push_back( *newValue );
    }

    //printAttrs (_xml);
   _xml.skipCurrentElement();
  }
}

std::optional<BCDataValue> BCDataManager::makeDataValue( BCDevice::ID deviceID, const BCDataParams& params )
{

  /*
  auto setIfExists = [&]( QStringView source, optDouble& target )
  {
    if( !source.isEmpty() )
    {
      bool ok;
      double testVal = source.toDouble(&ok);
      if (ok)
        target = testVal;
    }
  };
  */

  static QMetaEnum s_bcValueEnum{QMetaEnum::fromType<BC::ID>()};

  static BCValueTypeMap s_bcDataTypes
  {
    { "Byte",  new BCValueTypeWord( "", 1.5625F) },
    { "Word",  new BCValueTypeWord( "", 1.5625F) },

    { "Float",  new BCValueTypeWord( "", 1.5625F) },
    { "Percent",new BCValueTypeWord( "%", 1.5625 ) },
    { "KWh",    new BCValueTypeWord( "kwh", 1.5625 ) },
    { "Watt",   new BCValueTypeWord( "w", 1.5625 ) },
    { "Km",     new BCValueTypeWord( "km", 1.5625 ) },
    { "Kmh",    new BCValueTypeWord( "km/h", 0.1 ) },
    { "Mm",     new BCValueTypeWord( "mm", 1.5625 ) },
    { "Sec",    new BCValueTypeWord( "s", 1.5625 ) },
    { "SoC",    new BCValueTypeWord( "%", 1.5625 ) },
    { "Odo",    new BCValueTypeWord( "km", 1.5625 ) },
    { "Assist", new BCValueTypeWord( "", 0 ,4 ) },
    { "Assist", new BCValueTypeWord( "%" ) },
  };


  /*
  _dataTypes.insert( "Float",  new Fitz{{  "", 1.5625F}} );
  _dataTypes.insert( "Percent",new Fatz{{  "%", 1.5625 }} );
  _dataTypes.insert( "KWh",    new Fatz{{  "kwh", 1.5625 }} );
  _dataTypes.insert( "Watt",   new Long{{  "w", 1.5625 }} );
  _dataTypes.insert( "Km",     new BCValueTypeWord{{  "km", 1.5625 }} );
  _dataTypes.insert( "Kmh",    new BCValueTypeWord{{ "km/h", 0.1 }} );
  _dataTypes.insert( "Mm",     new BCValueTypeWord{{ "mm", 1.5625 }} );
  _dataTypes.insert( "Sec",    new BCValueTypeWord{{  "s", 1.5625 }} );
  _dataTypes.insert( "SoC",    new Long{{ "%", 1.5625 }} );
  _dataTypes.insert( "Odo",    new Fitz{{ "km", 1.5625 }} );
  _dataTypes.insert( "Assist", new Fatz{{ "", 0 ,4 }} );
  _dataTypes.insert( "Assist", new Fatz{{ "%" }} );
  //_dataTypes.insert( "Date",   { BCValueType::TypeID::Date } );
  */

  /*
    Wir brauchen:
    - einen gültige ID String um die enum ID herauszufinden.
    - einen gültige UnitType String um den ValueType herauszufinden.

  */

  std::optional<BCDataValue> newValue;

  std::optional<quint64> IDVal = s_bcValueEnum.keyToValue64( params.ID.toLatin1().constData() );
  qDebug() << " --- should create: " <<  params.Label  << ": " << params.UnitType;
  if( IDVal.has_value() )
  {

    if( s_bcDataTypes.contains( params.UnitType ) )
    {

      const BCValueType* valueType = s_bcDataTypes[params.UnitType];
      newValue = BCDataValue( valueType, deviceID, static_cast<BC::ID>(IDVal.value()) );

      /*
      setIfExists( params.Factor, newValue.factor );
      setIfExists( params.Min, newValue.min );
      setIfExists( params.Max, newValue.max );
      */
      newValue->label = params.Label;
      newValue->defaultValue = params.Default;

      qDebug() << " --- created: " <<  params.Label;

    }
  }

  return newValue;
}

       // --- NEU: Speichern mit QXmlStreamWriter ---
void BCDataManager::saveBikeData()
{
  /*
  QString fileName = QFileDialog::getSaveFileName(this, "XML speichern", "", "XML Files (*.xml)");
  if (fileName.isEmpty()) return;

  QFile file(fileName);
  if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
      QMessageBox::warning(this, "Fehler", "Datei konnte nicht zum Schreiben geöffnet werden.");
      return;
    }

  QXmlStreamWriter xml(&file);
  xml.setAutoFormatting(true); // Sorgt für schöne Einrückungen (Tabs/Spaces)
  xml.writeStartDocument();
  xml.writeStartElement("devices");

         // Daten vom Model holen
  const QList<Device> &devices = m_model->getDevices();

  for (const Device &d : devices) {
      xml.writeStartElement("device");
      xml.writeAttribute("name", d.name);
      xml.writeAttribute("ip", d.ip);
      xml.writeEndElement(); // </device>
    }

  xml.writeEndElement(); // </devices>
  xml.writeEndDocument();

         // Prüfen ob alles geschrieben wurde
  if (file.error() != QFile::NoError) {
      QMessageBox::critical(this, "Fehler", "Fehler beim Schreiben der Datei.");
    } else {
      statusBar()->showMessage("Datei erfolgreich gespeichert!", 3000);
    }
 */
}



