/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the MsXpertSuite project.
 *
 * The MsXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * *****************************************************************************
 * This specific code is a port to C++ of the Envemind Python code by Radzinski
 * and colleagues of IsoSpec fame (Lacki, Startek and company :-)
 *
 * See https://github.com/PiotrRadzinski/envemind.
 * *****************************************************************************
 *
 * END software license
 */


#include <QDebug>
#include <QtNetwork>

#include "MassDataClient.hpp"

namespace MsXpS
{
namespace libXpertMass
{

/*!
\class MsXpS::libXpertMass::MassDataClient
\inmodule libXpertMass
\ingroup XpertMassUtilities
\inheaderfile MassDataClient.hpp

\brief The MassDataClient class provides a network client.
*/

/*!
\variable MsXpS::libXpertMass::MassDataClient::m_inStream

\brief The data stream in input in this MassDataClient instance.
*/

/*!
\variable MsXpS::libXpertMass::MassDataClient::m_data

\brief The data being transported.
*/

/*!
\variable MsXpS::libXpertMass::MassDataClient::m_ipAddress

\brief The server IP address to which the connection must be made.
*/

/*!
\variable MsXpS::libXpertMass::MassDataClient::m_portNumber

\brief The port number to which to attach during the connection..
*/

/*!
\variable MsXpS::libXpertMass::MassDataClient::m_connectionFrequency

\brief The frequency at which the client connects to the server, in times per
second.

A frequency of 1 means that the connection will be tried once per second. This
is empirically correct.
*/

/*!
\variable MsXpS::libXpertMass::MassDataClient::mpa_tcpSocket

\brief The socket that is used for the connection.
*/

/*!
\brief Constructs a MassDataClient instance.

\list
\li \a ip_address: the server IP address at which the client needs to try a
connection.
\li \a port_number: the port number for the connection.
\li \a connection_frequency: the frequency at which the client tries a
connection to the server.
\li \a parent: the parent QObject.
\endlist
*/
MassDataClient::MassDataClient(const QString &ip_address,
                               int port_number,
                               int connection_frequency,
                               QObject *parent)
  : QObject(parent),
    m_ipAddress(ip_address),
    m_portNumber(port_number),
    m_connectionFrequency(connection_frequency)
{
  mpa_tcpSocket = new QTcpSocket(parent);
  if(mpa_tcpSocket == nullptr)
    qFatal("Failed to allocate a socket.");

  // QString host_name = QHostInfo::localHostName();

  // if(!host_name.isEmpty())
  //{
  // QString domain_name = QHostInfo::localDomainName();

  // if(!domain_name.isEmpty())
  // qDebug() << "address:" << host_name + "/" + domain_name;
  //}

  // if(host_name != QLatin1String("locahost"))
  // qDebug() << "host_name is not localhost.";

  // QList<QHostAddress> ip_addresses_list = QNetworkInterface::allAddresses();

  // for(int iter = 0; iter < ip_addresses_list.size(); ++iter)
  // qDebug() << "New ip address:" << ip_addresses_list.at(iter)
  //<< "is loopback:" << ip_addresses_list.at(iter).isLoopback();

  connect(mpa_tcpSocket, &QTcpSocket::hostFound, [this]() {
    emit hostFoundSignal();
  });

  connect(mpa_tcpSocket, &QTcpSocket::connected, [this]() {
    emit connectedSignal();
  });

  connect(mpa_tcpSocket,
          &QAbstractSocket::errorOccurred,
          this,
          &MassDataClient::reportError);

  m_inStream.setDevice(mpa_tcpSocket);
  m_inStream.setVersion(QDataStream::Qt_5_0);
  connect(
    mpa_tcpSocket, &QIODevice::readyRead, this, &MassDataClient::readData);


  // A time with a timeout of 0 will trigger as soon as all the other events
  // have been processed. This is thus not intrusive.

  QTimer *timer_p = new QTimer(this);
  // This does not work, the interaval is too short and the server can't serve
  // in time, it seems.
  // timer_p->setInterval(0);
  // 1 second is largely enough, which means a frequency of 1, which is the
  // default for the connection frequency parameter!
  timer_p->setInterval(1000 / m_connectionFrequency);
  connect(timer_p, &QTimer::timeout, this, &MassDataClient::requestNewData);

  timer_p->start();
}

/*!
\brief Destructs this MassDataClient instance.
*/
MassDataClient::~MassDataClient()
{
  if(mpa_tcpSocket != nullptr)
  {
    qDebug("Now deleting the socket.");
    delete mpa_tcpSocket;
  }
}

/*!
\brief Tries a connection to the server using the member QTcpSocket.
*/
void
MassDataClient::requestNewData()
{
  qDebug() << "Client requests data at" << QDateTime::currentDateTime();

  QAbstractSocket::SocketState state = mpa_tcpSocket->state();
  if(state != QAbstractSocket::UnconnectedState)
    mpa_tcpSocket->abort();

  mpa_tcpSocket->connectToHost(m_ipAddress, m_portNumber);
}

/*!
\brief Reads the data into the QByteArray m_data member using the QDataStream
m_inStream member.

Emits the newDataSignal() signal with m_data;.
*/
void
MassDataClient::readData()
{
  // qDebug() << "Reading data.";

  // We need to have the same version, FIXME, this should go as a macro
  // somewhere.

  // qDebug() << "The number of bytes available in this readData call:"
  //<< mp_tcpSocket->bytesAvailable() << "at"
  //<< QDateTime::currentDateTime().toString();

  // This version uses the readAll() function.

  // QByteArray byte_array = mp_tcpSocket->readAll();

  // QDataStream stream(byte_array);
  // stream.setVersion(QDataStream::Qt_5_0);
  // stream.startTransaction();
  // stream >> m_data;

  // if(!stream.commitTransaction())
  //{
  // qDebug() << "Failed to commit the data read transaction.";

  // return ;
  //}

  // This version uses the stream operator.
  // Seems to work equivalently to the version above.
  // stream.setVersion(QDataStream::Qt_5_0);
  m_inStream.startTransaction();

  m_inStream >> m_data;

  // qDebug() << "Atomically read" << m_data.size();

  if(!m_inStream.commitTransaction())
    {
      qDebug() << "Could NOT commit the transaction fine.";
      return;
    }
  // else
  // qDebug() << "Could commit the transaction fine.";

  emit newDataSignal(m_data);

  // qDebug() << "Got these data:" << QString(m_data);
}

/*!
\brief Reports the error \a socket_error.
*/
void
MassDataClient::reportError(QAbstractSocket::SocketError socket_error)
{
  switch(socket_error)
    {
      case QAbstractSocket::RemoteHostClosedError:
        // qDebug() << "Error: QAbstractSocket::RemoteHostClosedError.";
        // emit reportErrorSignal("RemoteHostClosedError");
        break;
      case QAbstractSocket::HostNotFoundError:
        // qDebug() << "Error: QAbstractSocket::HostNotFoundError.";
        // emit reportErrorSignal("HostNotFoundError");
        break;
      case QAbstractSocket::ConnectionRefusedError:
        qDebug() << "Error: QAbstractSocket::ConnectionRefusedError.";
        emit reportErrorSignal("ConnectionRefusedError");
        break;
      default:
        // qDebug() << "Error:" << mp_tcpSocket->errorString();
        // emit reportErrorSignal(mp_tcpSocket->errorString());
        break;
    }
}

/*!
\brief Returns a string display the connection details.
*/
QString
MassDataClient::getStatus()
{
  return QString("Doing well, connection should be to IP address: %1, port %2.")
    .arg(m_ipAddress)
    .arg(m_portNumber);
}

} // namespace libXpertMass

} // namespace MsXpS
