/***************************************************************************
**
**  This file is part of waran.
**
**  waran 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.
**
**  waran 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2011-11-23
**  Copyright: 2011-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "DaemonLink.h"
#include "Station.h"

/*!
  \class DaemonLink DaemonLink.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
DaemonLink::DaemonLink(Station * station)
  : QObject(station)
{
  TRACE;
  _station=station;
  _port=0;
  _connectionTimer.setInterval(5000);
  QObject::connect(&_connectionTimer, SIGNAL(timeout()), this, SLOT(connect()));
  QObject::connect(&_socket, SIGNAL(readyRead()), this, SLOT(bytesAvailable()));
  QObject::connect(&_socket, SIGNAL(connected()), this, SLOT(connected()));
  QObject::connect(&_socket, SIGNAL(disconnected()), this, SLOT(hungUp()));
  connect();
  _connectionTimer.start();
}

/*!
  Description of destructor still missing
*/
DaemonLink::~DaemonLink()
{
  TRACE;
  _socket.disconnectFromHost();
}

bool DaemonLink::isAvailable()
{
  TRACE;
  switch(_socket.state()) {
  case QAbstractSocket::UnconnectedState:
    connect();
    _connectionTimer.start();
    return false;
  case QAbstractSocket::HostLookupState:
  case QAbstractSocket::ConnectingState:
  case QAbstractSocket::ClosingState:
    // Transient state, skip for now, try next time
    return false;
  case QAbstractSocket::ConnectedState:
    // Connection available
    break;
  case QAbstractSocket::BoundState:
  case QAbstractSocket::ListeningState:
    // Not possible here (server states only)
    return false;
  }
  return true;
}

void DaemonLink::hungUp()
{
  TRACE;
  _connectionTimer.start();
  emit stateChanged();
}

void DaemonLink::connect()
{
  TRACE;
  if(_socket.state()==QAbstractSocket::UnconnectedState) {
    _socket.connectToHost(_station->address(), _port);
  }
}

void DaemonLink::disconnectFromHost()
{
  TRACE;
  _socket.disconnectFromHost();
}

void DaemonLink::connected()
{
  TRACE;
  _connectionTimer.stop();
  emit stateChanged();
}

void DaemonLink::bytesAvailable()
{
  TRACE;
  QByteArray data=_socket.readAll();
  _buffer.add(data.data(), data.count());
  while(_buffer.availableBytes()>0) {
    int bytesRead=bytesAvailable(_buffer.data(), _buffer.availableBytes());
    if(bytesRead>0) {
      _buffer.release(bytesRead);
    } else {
      break;
    }
  }
}

/*!
  Returns true if \a buffer at \a bytesRead index starts with \a string.
*/
bool DaemonLink::match(const char * buffer, int& bytesRead, int bytesCount, const char * string)
{
  int n=bytesCount-bytesRead;
  int sn=strlen(string);
  if(n>=sn && strncmp(buffer+bytesRead, string, sn)==0) {
    bytesRead+=sn;
    return true;
  } else {
    return false;
  }
}

/*!
  Returns 0 if \a buffer at \a bytesRead index does not contain \a string even partially at the end.
  Returns 1 if \a buffer at \a bytesRead index contains \a string partially at the end.
  Returns 2 if \a buffer at \a bytesRead index contains \a string completely.
*/
int DaemonLink::findMatch(const char * buffer, int& bytesRead, int bytesCount, const char * string)
{
  int si=0;
  int bi=bytesRead;
  while(bi<bytesCount) {
    if(string[si]=='\0') { // Completely matched
      bytesRead=bi;
      return 2;
    }
    if(buffer[bi]==string[si]) {
      si++;
    } else {
      si=0;
    }
    bi++;
  }
  if(si>0) { // Partially matched, do not advance bytesRead
    return 1;
  } else {
    bytesRead=bytesCount;
    return 0;
  }
}

/*!
  Returns -1 if value is truncated and \a bytesRead is not modified.
*/
QByteArray DaemonLink::readString(const char * buffer, int& bytesRead, int bytesCount, bool& ok)
{
  int i;
  QByteArray value;
  for(i=bytesRead; i<bytesCount && !isspace(buffer[i]); i++) {}
  int n=i-bytesRead;
  if(n>0 && i<bytesCount) {
    value=QByteArray(buffer+bytesRead, n);
    bytesRead=i;
    ok=true;
  } else {
    ok=false;
  }
  return value;
}

/*!
  Returns -1 if value is truncated and \a bytesRead is not modified.
*/
int DaemonLink::readInteger(const char * buffer, int& bytesRead, int bytesCount, bool& ok)
{
  int i;
  int value;
  for(i=bytesRead; i<bytesCount && !isspace(buffer[i]); i++) {}
  int n=i-bytesRead;
  if(n>0 && i<bytesCount) {
    char * temp=new char[n+1];
    strncpy(temp, buffer+bytesRead, n);
    temp[n]='\0';
    value=atoi(temp);
    bytesRead=i;
    ok=true;
    delete [] temp;
  } else {
    value=-1;
    ok=false;
  }
  return value;
}

/*!
  Returns -1 if value is truncated and \a bytesRead is not modified.
*/
long int DaemonLink::readLongInteger(const char * buffer, int& bytesRead, int bytesCount, bool& ok)
{
  int i;
  long int value;
  for(i=bytesRead; i<bytesCount && !isspace(buffer[i]); i++) {}
  int n=i-bytesRead;
  if(n>0 && i<bytesCount) {
    char * temp=new char[n+1];
    strncpy(temp, buffer+bytesRead, n);
    temp[n]='\0';
    value=atol(temp);
    bytesRead=i;
    ok=true;
    delete [] temp;
  } else {
    value=-1;
    ok=false;
  }
  return value;
}

/*!
  Returns -1 if value is truncated and \a bytesRead is not modified.
*/
double DaemonLink::readDouble(const char * buffer, int& bytesRead, int bytesCount, bool& ok)
{
  int i;
  double value;
  for(i=bytesRead; i<bytesCount && !isspace(buffer[i]); i++) {}
  int n=i-bytesRead;
  if(n>0 && i<bytesCount) {
    char * temp=new char[n+1];
    strncpy(temp, buffer+bytesRead, n);
    temp[n]='\0';
    value=atof(temp);
    bytesRead=i;
    ok=true;
    delete [] temp;
  } else {
    value=-1;
    ok=false;
  }
  return value;
}

void DaemonLink::send(const char * msg)
{
  TRACE;
  if(_socket.state()==QAbstractSocket::ConnectedState) {
    _socket.write(msg, strlen(msg));
    _socket.flush();
  }
}

void DaemonLink::send(const char * buf, unsigned int len)
{
  TRACE;
  if(_socket.state()==QAbstractSocket::ConnectedState) {
    _socket.write(buf, len);
    _socket.flush();
  }
}

