/* -------------------------------------------------------------------------
 * BKG NTRIP Server
 * -------------------------------------------------------------------------
 *
 * Class:      bncUploadCaster
 *
 * Purpose:    Connection to NTRIP Caster
 *
 * Author:     L. Mervart
 *
 * Created:    29-Mar-2011
 *
 * Changes:
 *
 * -----------------------------------------------------------------------*/

#include <math.h>
#include "bncuploadcaster.h"
#include "bncversion.h"
#include "bnccore.h"
#include "bnctableitem.h"
#include "bncsettings.h"

using namespace std;

// Constructor
////////////////////////////////////////////////////////////////////////////
bncUploadCaster::bncUploadCaster(const QString& mountpoint,
                                 const QString& outHost, int outPort,
                                 const QString& ntripVersion,
                                 const QString& userName, const QString& password,
                                 int iRow,
                                 int rate) {
  _mountpoint    = mountpoint;
  _casterOutHost = outHost;
  _casterOutPort = outPort;
  _ntripVersion  = ntripVersion;
  (_ntripVersion == "2s") ?  _secure = true : _secure = false;
  _postExtension = "";
  _userName      = userName;
  _password      = password;
  _outSocket     = 0;
  _sOpenTrial    = 0;
  _iRow          = iRow;
  _rate          = rate;

  if      (_rate < 0) {
    _rate = 0;
  }
  else if (_rate > 60) {
    _rate = 60;
  }
  _isToBeDeleted = false;

  if (!QSslSocket::supportsSsl()) {
    BNC_CORE->slotMessage("No SSL support, install OpenSSL run-time libraries", true);
    deleteSafely();
  }

  if (_ntripVersion != "1") {
    bncSettings settings;
    _proxyHost = settings.value("proxyHost").toString();
    _proxyPort = settings.value("proxyPort").toInt();
    _sslIgnoreErrors = (Qt::CheckState(settings.value("sslIgnoreErrors").toInt()) == Qt::Checked);
    if (_secure) {
      _casterOutPort = 443;
      _postExtension = QString("https://%1:%2").arg(_casterOutHost).arg(_casterOutPort);
    }
    else {
      _postExtension = QString("http://%1:%2").arg(_casterOutHost).arg(_casterOutPort);
    }
  }

  connect(this, SIGNAL(newMessage(QByteArray,bool)),
          BNC_CORE, SLOT(slotMessage(const QByteArray,bool)));

  if (BNC_CORE->_uploadTableItems.find(_iRow) != BNC_CORE->_uploadTableItems.end()){
    connect(this, SIGNAL(newBytes(QByteArray,double)),
            BNC_CORE->_uploadTableItems.value(iRow),
            SLOT(slotNewBytes(const QByteArray,double)));
  }
  if (BNC_CORE->_uploadEphTableItems.find(_iRow) != BNC_CORE->_uploadEphTableItems.end()){
    connect(this, SIGNAL(newBytes(QByteArray,double)),
            BNC_CORE->_uploadEphTableItems.value(iRow),
            SLOT(slotNewBytes(const QByteArray,double)));
  }
}

// Safe Desctructor
////////////////////////////////////////////////////////////////////////////
void bncUploadCaster::deleteSafely() {
  _isToBeDeleted = true;
  if (!isRunning()) {
    delete this;
  }
}

// Destructor
////////////////////////////////////////////////////////////////////////////
bncUploadCaster::~bncUploadCaster() {
  if (isRunning()) {
    wait();
  }
  if (_outSocket) {
    disconnect(_outSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)),
               this, SLOT(slotProxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)));
    delete _outSocket;
  }
}

//
////////////////////////////////////////////////////////////////////////////
void bncUploadCaster::slotProxyAuthenticationRequired(const QNetworkProxy&,
                                                    QAuthenticator*) {
  emit newMessage("slotProxyAuthenticationRequired", true);
}


/* TSL/SSL
////////////////////////////////////////////////////////////////////////////
void bncUploadCaster::slotSslErrors(QList<QSslError> errors) {

  QString msg = "SSL Error\n";
  QSslCertificate cert = _outSocket->sslConfiguration().peerCertificate();
  if (!cert.isNull()) {
    msg += QString("Server Certificate Issued by:\n"
                   "%1\n%2\nCannot be verified\n")
#if QT_VERSION >= 0x050000
           .arg(cert.issuerInfo(QSslCertificate::OrganizationalUnitName).at(0))
           .arg(cert.issuerInfo(QSslCertificate::Organization).at(0));
#else
           .arg(cert.issuerInfo(QSslCertificate::OrganizationalUnitName))
           .arg(cert.issuerInfo(QSslCertificate::Organization));
#endif
  }
  QListIterator<QSslError> it(errors);
  while (it.hasNext()) {
    const QSslError& err = it.next();
    msg += "\n" + err.errorString();
  }

  BNC_CORE->slotMessage(msg.toLatin1(), true);

  if (_sslIgnoreErrors) {
    _outSocket->ignoreSslErrors();
  }
  else {
    deleteSafely();
  }
}
*/

// Endless Loop
////////////////////////////////////////////////////////////////////////////
void bncUploadCaster::run() {
  while (true) {
    if (_isToBeDeleted) {
      QThread::quit();
      deleteLater();
      return;
    }
    open();
    if (_outSocket && _outSocket->state() == QAbstractSocket::ConnectedState) {
      QMutexLocker locker(&_mutex);
      if (_outBuffer.size() > 0) {
        if (_ntripVersion == "1") {
          _outSocket->write(_outBuffer);
          _outSocket->flush();
        }
        else {
          QString chunkSize = QString("%1").arg(_outBuffer.size(),0,16,QLatin1Char('0'));
          QByteArray chunkedData = chunkSize.toLatin1() + "\r\n" + _outBuffer + "\r\n";
          _outSocket->write(chunkedData);
          _outSocket->flush();
        }
        emit newBytes(_mountpoint.toLatin1(), _outBuffer.size());
      }
    }
    if (_rate == 0) {
      {
        QMutexLocker locker(&_mutex);
        _outBuffer.clear();
      }
      msleep(100); //sleep 0.1 sec
    }
    else {
      sleep(_rate);
    }
  }
}

// Start the Communication with NTRIP Caster
////////////////////////////////////////////////////////////////////////////
void bncUploadCaster::open() {

  if (_mountpoint.isEmpty()) {
    return;
  }

  if (_outSocket != 0 &&
      _outSocket->state() == QAbstractSocket::ConnectedState) {
    return;
  }
  
  delete _outSocket; _outSocket = 0;

  double minDt = pow(2.0,_sOpenTrial);
  if (++_sOpenTrial > 4) {
    _sOpenTrial = 4;
  }
  if (_outSocketOpenTime.isValid() &&
      _outSocketOpenTime.secsTo(QDateTime::currentDateTime()) < minDt) {
    return;
  }
  else {
    _outSocketOpenTime = QDateTime::currentDateTime();
  }

  _outSocket = new QSslSocket();
  if (_sslIgnoreErrors) {
    _outSocket->ignoreSslErrors();
  }
  _outSocket->setProxy(QNetworkProxy::NoProxy); // to prevent the usage of system entries
  if (_ntripVersion == "1") {
    _outHost = _casterOutHost;
    _outPort = _casterOutPort;
  }
  else {
    if (!_proxyHost.isEmpty()) {
      _outHost = _proxyHost;
      _outPort = _proxyPort;
      connect(_outSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)),
              this, SLOT(slotProxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)));
    }
  }

  if (!_secure) {
    _outSocket->connectToHost(_outHost, _outPort);
  }
  else {
    _outSocket->connectToHostEncrypted(_outHost, _outPort);/*
    if (!_outSocket->waitForEncrypted()) {
        cout << "waitForEncrypted: " << _outSocket->errorString().toStdString().c_str() << endl;;
        return;
    }*/
  }

  const int timeOut = 5000;  // 5 seconds
  if (!_outSocket->waitForConnected(timeOut)) {
    emit(newMessage("Broadcaster: Connect timeout for " + _mountpoint.toLatin1()
         + " (" + _outHost.toLatin1()+ ":" + QString("%1) ").arg(_outPort).toLatin1()
         + _outSocket->errorString().toLatin1(), true));
    delete _outSocket;
    _outSocket = 0;
    return;
  }

  QByteArray msg;
  if (_ntripVersion == "1") {
    msg = "SOURCE " + _password.toLatin1() + " /" + _mountpoint.toLatin1() + "\r\n" +
          "Source-Agent: NTRIP BNC/" BNCVERSION "\r\n\r\n";
  }
  else {
    msg = "POST " + _postExtension.toLatin1() + "/" + _mountpoint.toLatin1() + " HTTP/1.1\r\n" +
          "Host: " + _casterOutHost.toLatin1() + "\r\n" +
          "Ntrip-Version: Ntrip/2.0\r\n" +
          "Authorization: Basic " + (_userName + ":" + _password).toLatin1().toBase64() + "\r\n" +
          "User-Agent: NTRIP BNC/" BNCVERSION " (" + BNC_OS + ")\r\n" +
          "Connection: close\r\n" +
          "Transfer-Encoding: chunked\r\n\r\n";
  }
  cout << msg.toStdString().c_str();
  _outSocket->write(msg);
  _outSocket->waitForBytesWritten();

  _outSocket->waitForReadyRead();
  QByteArray ans = _outSocket->readLine();cout << ans.toStdString().c_str() << endl;

  if (ans.indexOf("OK") == -1) {
    delete _outSocket;
    _outSocket = 0;
    emit(newMessage("Broadcaster: Connection broken for " + _mountpoint.toLatin1() + ": " + ans.left(ans.length()-2), true));
  }
  else {
    emit(newMessage("Broadcaster: Connection opened for " + _mountpoint.toLatin1(), true));
    _sOpenTrial = 0;
  }
}

