/***************************************************************************
**
**  This file is part of warangps.
**
**  warangps 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.
**
**  warangps 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: 2009-06-27
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <WaranCore.h>
#include "GpsStation.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
GpsStation::GpsStation()
{
  TRACE;
  _mode=Stack;
  _index=0;
  _fix.reset();
  _longitude.reset();
  _latitude.reset();
  _altitude.reset();
  connect(&_socket, SIGNAL(readyRead()), this, SLOT(bytesAvailable()));
  connect(&_socket, SIGNAL(connected()), this, SLOT(justConnected()));
  connect(&_socket, SIGNAL(disconnected()), this, SIGNAL(modeChanged()));
}

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

bool GpsStation::isAvailable()
{
  TRACE;
  switch(_socket.state()) {
  case QAbstractSocket::UnconnectedState:
    _socket.connectToHost(_hostName, _port);
    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 GpsStation::justConnected()
{
  TRACE;
  startBroadcast();
}

void GpsStation::startBroadcast()
{
  TRACE;
  if(!isAvailable()) return;
  _index=0;
  _fix.reset();
  _socket.write("broadcast=on\n", 13); // Request a broadcast start
  _socket.flush();
}

void GpsStation::stopBroadcast()
{
  TRACE;
  if(!isAvailable()) return;
  _socket.write("broadcast=off\n", 14); // Request a broadcast stop
  _socket.flush();
}

void GpsStation::setMode(Mode m)
{
  TRACE;
  _mode=m;
  if(_mode==Track) {
    _longitude.reset();
    _latitude.reset();
    _altitude.reset();
  }
}

void GpsStation::bytesAvailable()
{
  TRACE;
  QByteArray data=_socket.readAll();
  _socketBuffer.add(data.data(), data.count());
  while(_socketBuffer.availableBytes()>0) {
    int bytesRead=bytesAvailable(_socketBuffer.data(), _socketBuffer.availableBytes());
    if(bytesRead>0) {
      _socketBuffer.release(bytesRead);
    } else {
      break;
    }
  }
}
/*!
  Returns true if \a buffer at \a bytesRead index starts with \a string.
*/
bool GpsStation::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 -1 if value is truncated and \a bytesRead is not modified.
*/
int GpsStation::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;
}

int GpsStation::bytesAvailable(const char * buffer, int bytesCount)
{
  TRACE;
  int bytesRead=0;
  while(bytesRead<bytesCount) {
    // Scan for a line
    int newBytesRead;
    for(newBytesRead=bytesRead; newBytesRead<bytesCount && buffer[newBytesRead]!='\n'; newBytesRead++) {}
    if(newBytesRead==bytesCount) {
      return bytesRead; // partial line
    }
    switch(buffer[bytesRead]) {
    case 'b': {
        int originalBytesRead=bytesRead;
        if(match(buffer, bytesRead, bytesCount, "broadcast=")) {
          bool ok;
          int size=readInteger(buffer, bytesRead, bytesCount, ok);
          if(ok) {
            newBytesRead++;
            if(newBytesRead+size>bytesCount) { // wait for more bytes
              return originalBytesRead;
            }
            if(!parseFix(buffer+newBytesRead, size)) {
              startBroadcast();
              _socketBuffer.clear();
              return 0;
            }
            newBytesRead+=size-1;
          }
        }
      }
      break;
    default:
      break;
    }
    bytesRead=newBytesRead+1;
    // Skip blanks and additionnal end of line characters
    while(bytesRead<bytesCount && isspace(buffer[bytesRead])) {bytesRead++;}
  }
  ASSERT(bytesRead<=bytesCount);
  return bytesRead;
}

/*!
*/
bool GpsStation::parseFix(const char * data, int bytesCount)
{
  TRACE;
  GpsBlock b(data);
  unsigned char index=b.index();
  if(index!=_index) {
    printf("Bad index %hhu(expected %hhu)\n", index, _index);
    return false;
  }
  int s=b.totalSize();
  ASSERT(s==bytesCount);
  _fix.setState(static_cast<GpsFix::State>(b.state()));

  //printf("%i bytes available, block size %i\n", bytesAvailable, s+4);
  //printf("Received index %hhu size %i\n",_index, s+4);
  b.decode(_fix);
  if(_fix.state()==GpsFix::Fix3D) {
    if(_mode==Track) {
      _longitude.reset();
      _latitude.reset();
      _altitude.reset();
    }
    _longitude.add(_fix.longitude()*1e-7);
    _latitude.add(_fix.latitude()*1e-7);
    _altitude.add(_fix.altitude()*1e-3);
  }
  _index++;
  return true;
}

QString GpsStation::solution() const
{
  TRACE;
  switch(_fix.state()) {
  case GpsFix::NoFix:
    return "NoFix";
  case GpsFix::Fix2D:
    return "Fix2D";
  case GpsFix::Fix3D:
    break;
  case GpsFix::TimeFix:
    return "TimeFix";
  }
  return "Fix3D";
}
