/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "liblocationwrapper_p.h"

using namespace std;

QTM_BEGIN_NAMESPACE

Q_GLOBAL_STATIC(LiblocationWrapper, LocationEngine)
        
LiblocationWrapper *LiblocationWrapper::instance()
{
    return LocationEngine();
}

LiblocationWrapper::~LiblocationWrapper() 
{
    if (locationDevice)
        g_object_unref(locationDevice);
    if (locationControl)
        g_object_unref(locationControl);
}

bool LiblocationWrapper::inited()
{
    int retval = false;
    if (!(locationState & LiblocationWrapper::Inited)) {
        g_type_init();

        locationControl = location_gpsd_control_get_default();

        if (locationControl) {
            g_object_set(G_OBJECT(locationControl),
                         "preferred-method", LOCATION_METHOD_USER_SELECTED,
                         "preferred-interval", LOCATION_INTERVAL_1S,
                         NULL);
            locationDevice = 
                    (LocationGPSDevice*)g_object_new(LOCATION_TYPE_GPS_DEVICE, 
                                                     NULL);
        
            if (locationDevice) {
                errorHandlerId =
                    g_signal_connect(G_OBJECT(locationControl), "error-verbose",
                                     G_CALLBACK(&locationError), 
                                     static_cast<void*>(this));
                posChangedId =
                    g_signal_connect(G_OBJECT(locationDevice), "changed",
                                     G_CALLBACK(&locationChanged), 
                                     static_cast<void*>(this));
                locationState = LiblocationWrapper::Inited;
                retval = true;
                startcounter = 0;
            }
        }
    } else {
        retval = true;
    }
    return retval;
}

void LiblocationWrapper::locationError(LocationGPSDevice *device,
                                       gint errorCode, gpointer data)
{
    Q_UNUSED(device);
    QString locationError;

    switch (errorCode) {
    case LOCATION_ERROR_USER_REJECTED_DIALOG:
        locationError = "User didn't enable requested methods";
        break;
    case LOCATION_ERROR_USER_REJECTED_SETTINGS:
        locationError = "User changed settings, which disabled location.";
        break;
    case LOCATION_ERROR_BT_GPS_NOT_AVAILABLE:
        locationError = "Problems with BT GPS";
        break;
    case LOCATION_ERROR_METHOD_NOT_ALLOWED_IN_OFFLINE_MODE:
        locationError = "Requested method is not allowed in offline mode";
        break;
    case LOCATION_ERROR_SYSTEM:
        locationError = "System error.";
        break;
    default:
        locationError = "Unknown error.";
    }

    qDebug() << "Location error:" << locationError;

    LiblocationWrapper *object;
    object = (LiblocationWrapper *)data;
    emit object->error();
}

void LiblocationWrapper::locationChanged(LocationGPSDevice *device,
                                                 gpointer data)
{
    QGeoPositionInfo posInfo;
    QGeoCoordinate coordinate;
    QGeoSatelliteInfo satInfo;
    int satellitesInUseCount = 0;
    LiblocationWrapper *object;
    
    if (!data || !device) {
        return;
    }
    
    object = (LiblocationWrapper *)data;

    if (device) {
        if (device->fix) {
            if (device->fix->fields & LOCATION_GPS_DEVICE_TIME_SET) {
                posInfo.setTimestamp(QDateTime::fromTime_t(device->fix->time));
            }

            if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET) {
                coordinate.setLatitude(device->fix->latitude);
                coordinate.setLongitude(device->fix->longitude);
                posInfo.setAttribute(QGeoPositionInfo::HorizontalAccuracy,
                                     device->fix->eph);
                posInfo.setAttribute(QGeoPositionInfo::VerticalAccuracy,
                                     device->fix->epv);
            }

            if (device->fix->fields & LOCATION_GPS_DEVICE_ALTITUDE_SET) {
                coordinate.setAltitude(device->fix->altitude);
            }

            if (device->fix->fields & LOCATION_GPS_DEVICE_SPEED_SET) {
                posInfo.setAttribute(QGeoPositionInfo::GroundSpeed,
                                     device->fix->speed);
            }

            if (device->fix->fields & LOCATION_GPS_DEVICE_CLIMB_SET) {
                posInfo.setAttribute(QGeoPositionInfo::VerticalSpeed,
                                     device->fix->climb);
            }
  
            if (device->fix->fields & LOCATION_GPS_DEVICE_TRACK_SET) {
                posInfo.setAttribute(QGeoPositionInfo::Direction,
                                     device->fix->track);
            }
        }
        
        if (device->satellites_in_view) {
            QList<QGeoSatelliteInfo> satsInView;
            QList<QGeoSatelliteInfo> satsInUse;
            unsigned int i;
            for (i=0;i<device->satellites->len;i++) {
                LocationGPSDeviceSatellite *satData =
                    (LocationGPSDeviceSatellite *)g_ptr_array_index(device->satellites,
                                                                    i);
                satInfo.setSignalStrength(satData->signal_strength);
                satInfo.setPrnNumber(satData->prn);
                satInfo.setAttribute(QGeoSatelliteInfo::Elevation, 
                                     satData->elevation);
                satInfo.setAttribute(QGeoSatelliteInfo::Azimuth, 
                                     satData->azimuth);
    
                satsInView.append(satInfo);
                if (satData->in_use) {
                    satellitesInUseCount++;
                    satsInUse.append(satInfo);
                }
            }
            
            if (!satsInView.isEmpty())
                object->satellitesInViewUpdated(satsInView);
            
            if (!satsInUse.isEmpty())
                object->satellitesInUseUpdated(satsInUse);
        }        
    }
       
    posInfo.setCoordinate(coordinate);

    if ((device->fix->fields & LOCATION_GPS_DEVICE_TIME_SET) && 
        ((device->fix->mode == LOCATION_GPS_DEVICE_MODE_3D) || 
         (device->fix->mode == LOCATION_GPS_DEVICE_MODE_2D))) {
        object->setLocation(posInfo, true);
    } else {
        object->setLocation(posInfo, false);
    }
}

void LiblocationWrapper::setLocation(const QGeoPositionInfo &update, 
                                     bool locationValid)
{
    validLastSatUpdate = locationValid;
    lastSatUpdate = update;
}

QGeoPositionInfo LiblocationWrapper::position() {
    return lastSatUpdate;
}

bool LiblocationWrapper::fixIsValid()
{
    return validLastSatUpdate;
}

QGeoPositionInfo LiblocationWrapper::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
{
    QGeoPositionInfo posInfo;
    QGeoCoordinate coordinate;
    double time;
    double latitude;
    double longitude;
    double altitude;
    double speed;
    double track;
    double climb;
    
    GConfItem lastKnownPositionTime("/system/nokia/location/lastknown/time");
    GConfItem lastKnownPositionLatitude("/system/nokia/location/lastknown/latitude");
    GConfItem lastKnownPositionLongitude("/system/nokia/location/lastknown/longitude");
    GConfItem lastKnownPositionAltitude("/system/nokia/location/lastknown/altitude");
    GConfItem lastKnownPositionSpeed("/system/nokia/location/lastknown/speed");
    GConfItem lastKnownPositionTrack("/system/nokia/location/lastknown/track");
    GConfItem lastKnownPositionClimb("/system/nokia/location/lastknown/climb");
    
    if (validLastSatUpdate)
        return lastSatUpdate;

    if (!fromSatellitePositioningMethodsOnly)
        if (validLastUpdate)
            return lastUpdate;
    
    time = lastKnownPositionTime.value().toDouble();
    latitude = lastKnownPositionLatitude.value().toDouble();
    longitude = lastKnownPositionLongitude.value().toDouble();
    altitude = lastKnownPositionAltitude.value().toDouble();
    speed = lastKnownPositionSpeed.value().toDouble();
    track = lastKnownPositionTrack.value().toDouble();
    climb = lastKnownPositionClimb.value().toDouble();
        
    if (longitude && latitude) {
        coordinate.setLongitude(longitude);
        coordinate.setLatitude(latitude);
        if (altitude) {
            coordinate.setAltitude(altitude);
        }
        posInfo.setCoordinate(coordinate);
    }
        
    if (speed) {
        posInfo.setAttribute(QGeoPositionInfo::GroundSpeed, speed);
    }
    
    if (track) {
        posInfo.setAttribute(QGeoPositionInfo::Direction, track);
    }
    
    if (climb) {
        posInfo.setAttribute(QGeoPositionInfo::VerticalSpeed, climb);        
    }

    // Only positions with time (3D) are provided.
    if (time) {
        posInfo.setTimestamp(QDateTime::fromTime_t(time));
        return posInfo;
    }

    return QGeoPositionInfo();
}

void LiblocationWrapper::satellitesInViewUpdated(const QList<QGeoSatelliteInfo> &satellites)
{
    satsInView = satellites;
}

void LiblocationWrapper::satellitesInUseUpdated(const QList<QGeoSatelliteInfo> &satellites)
{
    satsInUse = satellites;
}

QList<QGeoSatelliteInfo> LiblocationWrapper::satellitesInView()
{
    return satsInView;
}

QList<QGeoSatelliteInfo> LiblocationWrapper::satellitesInUse()
{
    return satsInUse;
}

void LiblocationWrapper::start() {
    startcounter++;

    if ((locationState & LiblocationWrapper::Inited) &&
        !(locationState & LiblocationWrapper::Started)) {
        if (!errorHandlerId) {
            errorHandlerId =
                g_signal_connect(G_OBJECT(locationControl), "error-verbose",
                                 G_CALLBACK(&locationError), 
                                 static_cast<void*>(this));
        }

        if (!posChangedId) {
            posChangedId =
                g_signal_connect(G_OBJECT(locationDevice), "changed",
                                 G_CALLBACK(&locationChanged), 
                                 static_cast<void*>(this));
        }

        location_gpsd_control_start(locationControl);
        
        locationState |= LiblocationWrapper::Started;
        locationState &= ~LiblocationWrapper::Stopped;
    }
}

void LiblocationWrapper::stop() {
    startcounter--;

    if (startcounter > 0)
        return;
    
    if ((locationState & (LiblocationWrapper::Started |
                          LiblocationWrapper::Inited)) &&
        !(locationState & LiblocationWrapper::Stopped)) {
        if (errorHandlerId)
            g_signal_handler_disconnect(G_OBJECT(locationControl), 
                                        errorHandlerId);
        if (posChangedId)
            g_signal_handler_disconnect(G_OBJECT(locationDevice), 
                                        posChangedId);
        errorHandlerId = 0;
        posChangedId = 0;
        startcounter = 0;
        location_gpsd_control_stop(locationControl);

        locationState &= ~LiblocationWrapper::Started;
        locationState |= LiblocationWrapper::Stopped;
    }
}

bool LiblocationWrapper::isActive() {
    if (locationState & LiblocationWrapper::Started)
        return true;
    else
        return false;
}

#include "moc_liblocationwrapper_p.cpp"
QTM_END_NAMESPACE

