/**********************************************************************************************
    Copyright (C) 2009 Oliver Eichler oliver.eichler@gmx.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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

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

#include "CGpsd.h"
#include <QtGui>
#include <proj_api.h>

#define MAX_WAIT    5000
#define MAX_AGE     1000

CGpsd::CGpsd(const QString& device, int rate, QObject * parent)
: IGps(parent)
, haveSeenData(false)
{
    m_hGPS_Device = NULL;
    m_hNewLocationData = NULL;
    watchdog = new QTimer(this);
    connect(watchdog, SIGNAL(timeout()), this, SLOT(slotWatchdog()));

    m_hNewLocationData = CreateEvent(NULL, FALSE, FALSE, NULL);
    if ( m_hNewLocationData ) {
        gpsdThread = new Win_GpsThread(this);
    }
    connect(gpsdThread, SIGNAL(sigGpsRead()), this, SLOT(slotGpsRead()) );

    watchdog->start(5000);
    memset(satellites,0,sizeof(satellites));
    sysError = tr("Start GPSD0: device.");
}


CGpsd::~CGpsd()
{
    if (gpsdThread->isRunning()) {
        gpsdThread->stop();
        if (QThread::currentThread() != gpsdThread)
            gpsdThread->wait();
    }

    if( m_hGPS_Device ) {
        DWORD dwRet = GPSCloseDevice(m_hGPS_Device);
        m_hGPS_Device = NULL;
    }
    if (m_hNewLocationData) {
        CloseHandle(m_hNewLocationData);
        m_hNewLocationData = NULL;
    }
}


void CGpsd::decode(const QString& line)
{
}


void CGpsd::encode(const QString& line)
{
}


void CGpsd::slotGpsRead()
{
    GPS_POSITION gps_position = gpsdThread->gps_Position;

    // Prevent Wince to go to suspend mode while gps data incoming
    SystemIdleTimerReset();

    if ( (gps_position.dwValidFields & GPS_VALID_UTC_TIME == 0) ||
       (gps_position.dwValidFields & GPS_VALID_LATITUDE == 0)   ||
       (gps_position.dwValidFields & GPS_VALID_LONGITUDE == 0) ) {
       return;
    }
    //Elevation
    ele= gps_position.flAltitudeWRTSeaLevel;
    // Fix type
    fix= gps_position.FixType == 2 ? e3DFix : gps_position.FixType == 1 ? e2DFix : eNoFix;
    //HDOP
    hdop= gps_position.flHorizontalDilutionOfPrecision;
    //VDOP
    vdop= gps_position.flVerticalDilutionOfPrecision;

    idxSat = 0;
    while(idxSat < N_OF_SATELLITES) {
        if (gps_position.rgdwSatellitesInViewPRNs[idxSat] != 0) {
            satellites[idxSat].prn = gps_position.rgdwSatellitesInViewPRNs[idxSat];
            satellites[idxSat].ele = gps_position.rgdwSatellitesInViewElevation[idxSat];
            satellites[idxSat].azi = gps_position.rgdwSatellitesInViewAzimuth[idxSat];
            satellites[idxSat].snr = gps_position.rgdwSatellitesInViewSignalToNoiseRatio[idxSat];
        }
        ++idxSat;
    }

    SYSTEMTIME gpstime = gps_position.stUTCTime;
    datetime.setDate( QDate( gpstime.wYear , gpstime.wMonth, gpstime.wDay) );
    datetime.setTime( QTime( gpstime.wHour, gpstime.wMinute, gpstime.wSecond, gpstime.wMilliseconds) ); 
    datetime.setTimeSpec(Qt::UTC);
    timestamp = datetime.toTime_t();

    lat = gps_position.dblLatitude * DEG_TO_RAD;
    lon = gps_position.dblLongitude * DEG_TO_RAD;

    heading = gps_position.flHeading;
    velocity = gps_position.flSpeed;

    haveSeenData = true;
    emit sigNewData(*this);

    portChanged();
}


void CGpsd::slotWatchdog()
{
    if((m_hGPS_Device != NULL) && haveSeenData) {
        haveSeenData = false;
        return;
    }
    watchdog->stop();

    if((m_hGPS_Device == NULL)) {
        qDebug() << "void CGpsd::slotWatchdog()";
    }

    fix = eNoFix;

    qDebug() << "void CGpsd::slotWatchdog()";

    sysError = tr("Try to start GPS.");
    emit sigNewData(*this);

    qApp->processEvents();

    // Open Gps Device
    m_hGPS_Device = GPSOpenDevice(
        m_hNewLocationData,
        NULL,
        NULL, NULL);

    gpsdThread->thehGPS = m_hGPS_Device;
    gpsdThread->thehNewLocationData = m_hNewLocationData;
    gpsdThread->start();

    sysError = (m_hGPS_Device != NULL) ? "" : tr("Failed to start GPS. Retry...");
    emit sigNewData(*this);

    (m_hGPS_Device != NULL) ? watchdog->start(10000) : watchdog->start(10000);
}


void Win_GpsThread::monitorGpsdEvent()
{
    DWORD dwRet = 0;
    gps_Position;

    HANDLE gpsHandles[1] = {
        thehNewLocationData
    };

    gps_Position.dwSize = sizeof(gps_Position);
    gps_Position.dwVersion = GPS_VERSION_1;

    dwRet = WaitForMultipleObjects(
        1,
        gpsHandles, FALSE, INFINITE);
    //out << "Wait for event\n";
    if (dwRet == WAIT_OBJECT_0) {
        dwRet = GPSGetPosition(
            thehGPS,
            &gps_Position, MAX_AGE, 0);
        //out << "Event trigered!\n";
        emit sigGpsRead();
    }
}


Win_GpsThread::Win_GpsThread(CGpsd * caller):
QThread()
{
    this->caller = caller;
    terminate = false;
}


void Win_GpsThread::stop()
{
    terminate = true;
}


void Win_GpsThread::run()
{
    while (!terminate) {
        monitorGpsdEvent();
    }
    terminate = false;
}
