/**********************************************************************************************
    Copyright (C) 2007 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., 59 Temple Place - Suite 330, Boston, MA 02111 USA

**********************************************************************************************/
/*
  List of track can be read like that
    select name from track;

  List of point for a track can be selected by name, ordered by time or anything else like that
    select points.* from track, points where track.trackid=points.trackid and track.trackid=2;
    select points.* from track, points where track.trackid=points.trackid and track.name="test";
    select points.* from track, points where track.trackid=points.trackid and track.name="test" order by points.date;

 */


#include "CTrackDB.h"
#include "CCanvas.h"
#include "CMapDB.h"
#include "IGps.h"
#include "CTrack.h"
#include "CCanvas.h"
#include "proj_api.h"
#include "GeoMath.h"
#include "IMap.h"
#include "IUnit.h"
#include "CDlgTrackEdit.h"

#include <QSqlQuery>
#include <QSqlError>
#include <QtGui>

CTrackDB * CTrackDB::m_self = 0;

CTrackDB::CTrackDB(CCanvas * parent)
: QObject(parent)
, state(eStop)
, prevAzimuth(0.0)
, intvl(-1)
, intvlcnt(0)
, ext(false)
, colorIdx(0)
, colorIdx1(0)
{
    m_self = this;
    connect(&CCanvas::self(), SIGNAL(sigNewGps(IGps*)), this, SLOT(slotNewGPS(IGps*)));

    db = QSqlDatabase::database("qlandkarte");

    QSqlQuery query(db);
    if(!query.exec("SELECT version FROM versioninfo")) {
        initDB();
    }
    else if(query.next()) {
        QString version = query.value(0).toString();
        if(version != DB_VERSION) {
            migrateDB(version);
        }
    }
    else {
        initDB();
    }

//    track = 0;

    QSettings cfg;
    intvl   = cfg.value("track/interval", intvl).toInt();
    ext     = cfg.value("track/extended", ext).toBool();

}

CTrackDB::~CTrackDB()
{
}

void CTrackDB::initDB()
{
    QSqlQuery query(db);

    if(!query.exec( "CREATE TABLE track ("
        "trackid        INTEGER PRIMARY KEY AUTOINCREMENT,"
        "key            TEXT NOT NULL UNIQUE,"
        "date           TIMESTAMP NOT NULL,"
        "color          TEXT NOT NULL,"
        "name           TEXT NOT NULL,"
        "comment        TEXT,"
        "distance       TEXT"
    ")")) {
        qDebug() << query.lastError();
    }

    if(!query.exec( "CREATE TABLE trackpoints ("
        "trackid        INTEGER NOT NULL,"
        "idx            INTEGER PRIMARY KEY AUTOINCREMENT,"
        "date           TIMESTAMP NOT NULL,"
        "lat            REAL NOT NULL,"
        "lon            REAL NOT NULL,"
        "ele            REAL DEFAULT 1e+25,"
        "velocity       REAL NOT NULL,"
        "heading        REAL NOT NULL,"
        "vdop           REAL NOT NULL,"
        "hdop           REAL NOT NULL "
        "CONSTRAINT fk_trackid REFERENCES track(trackid)"

    ")")) {
        qDebug() << query.lastError();
    }

}

void CTrackDB::migrateDB(QString &version)
{
    QString errmsg;
    QSqlQuery query(db);
    /*
      Do Db migration here and don't forget to update the initdb function
      To migrate Db just do like that

      // Test initial version
      if (version=="1.0") {
         if(!query.exec( "ALTER TABLE points (.....)")
      }

      if (version=="1.1") {
         if(!query.exec( "ALTER TABLE points (.....)")
      }

      */
      if (version=="1.0") {
         if(!query.exec( "ALTER TABLE track ADD distance TEXT")) {
            errmsg = query.lastError().text();
            qDebug() << "errmsg=" << errmsg;
         }
         if(!query.exec( "ALTER TABLE points RENAME TO trackpoints")) {
            errmsg = query.lastError().text();
            qDebug() << "errmsg=" << errmsg;
         }
      }

}


void CTrackDB::slotNewGPS(IGps * dev)
{

    gps = dev;
    connect(gps, SIGNAL(sigNewData(IGps&)), this, SLOT(slotNewGPSData(IGps&)));

    slotNewGPSData(*gps);
}

#define DEG1    (2*M_PI/360)

void CTrackDB::slotNewGPSData(IGps& gps)
{
    if(state != eRecord) return;

    STrackPoint& trkpt = trackbuffer[trackIdx];
    IGps::fix_e fix =  gps.getTrackPoint(trkpt);
    if(fix == IGps::eNoFix) return;

    trkpt.flags = (fix == IGps::e3DFix) ? CTrack::e3DFix : (fix == IGps::e2DFix) ? CTrack::e2DFix : 0;
    // No more needed as pixel = lon/lat
    // test if track point needs to be recorded.
    if(trackIdx > 0){
        if(intvl == -1){
            STrackPoint& trkpt1 = trackbuffer[trackIdx - 1];
            qreal d = GPS_Math_Distance(trkpt.lon , trkpt.lat , trkpt1.lon , trkpt1.lat );
            //qDebug() << "track=" << trackIdx << " Distance=" << d<< " lon=" << trkpt.u  << " lat=" << trkpt.v << " lon_1=" << trkpt_1.u << " lat_1=" << trkpt_1.v  << " ele=" << trkpt.ele;
            if(d > 5 ) {
                storeTrackPoint(trkpt);
            }
        }
        else{
            if ((intvlcnt % intvl) == 0){
                storeTrackPoint(trkpt);
            }
            ++intvlcnt;
        }
    }
    else{
        storeTrackPoint(trkpt);
        intvlcnt = 1;
    }

    // start new track if index exceeds buffer size
    if(trackIdx == CHUNKSIZE){
        stop();
        //record();
    }

    emit sigProgress(trackIdx);

}

bool CTrackDB::storeTrackPoint(STrackPoint& trkpt)
{
    QString errmsg;
    QSqlQuery query(db);
    ++trackIdx;

    query.prepare("INSERT INTO trackpoints ( trackid, date, lat, lon, ele, velocity, heading, vdop, hdop) VALUES ( :trackid, :date, :lat, :lon, :ele, :velocity, :heading, :vdop, :hdop)");
    //query.bindValue(":idx", trackIdx);
    query.bindValue(":trackid", trackid);
    query.bindValue(":date", trkpt.timestamp);
    query.bindValue(":lat", trkpt.lat * RAD_TO_DEG);
    query.bindValue(":lon", trkpt.lon * RAD_TO_DEG);
    query.bindValue(":ele", trkpt.ele);

//     query.bindValue(":velocity", trkpt.velocity);
//     query.bindValue(":heading", trkpt.heading);
//     query.bindValue(":vdop", trkpt.vdop);
//     query.bindValue(":hdop", trkpt.hdop);

    query.bindValue(":velocity", 0);
    query.bindValue(":heading", 0);
    query.bindValue(":vdop", 0);
    query.bindValue(":hdop", 0);

    if(!query.exec()) {
        errmsg = query.lastError().text();
        qDebug() << "errmsg=" << errmsg;
        return false;
    }
    emit sigChanged();
    return true;
}

void CTrackDB::record()
{
    CTrack track;
    track.name = tr("New Track");
    track.colorIdx = 0;
    CDlgTrackEdit trackEdit(track, &CCanvas::self());
    int res = trackEdit.exec();

    if(res != QDialog::Rejected) return;

    // Start new track
    int timestamp = QDateTime::currentDateTime().toUTC().toTime_t ();
    QString errmsg;
    QSqlQuery query(db);
    QString name("test");
    query.prepare("INSERT INTO track ( key, date, color, name) VALUES ( :key, :date, :color, :name)");
    query.bindValue(":key", QString("%1%2").arg(timestamp).arg(name) );
    query.bindValue(":date", timestamp);
    query.bindValue(":color", track.colorIdx);
    query.bindValue(":name", track.name);
    if(!query.exec()) {
        errmsg = query.lastError().text();
        qDebug() << "errmsg=" << errmsg;
        return;
    }
    // Get coloridx from track
    colorIdx = track.colorIdx;

    if(state == eStop){
        trackIdx = 0;
        emit sigChanged();
    }

    // Now get trackid for track recording
    if(!query.exec("SELECT MAX(trackid) from track")) {
        errmsg = query.lastError().text();
        qDebug() << "errmsg=" << errmsg;
        return;
    }

    while(query.next()){
        trackid = query.value(0).toInt();
    }

    if(!query.exec("BEGIN")) {
        errmsg = query.lastError().text();
        qDebug() << "errmsg=" << errmsg;
        return;
    }

    state = eRecord;
}

void CTrackDB::pause()
{
    QSqlQuery query(db);
    QString errmsg;

    if(state == ePause){
        if(!query.exec("BEGIN")) {
            errmsg = query.lastError().text();
            qDebug() << "errmsg=" << errmsg;
            return;
        }

        state = eRecord;
    }
    else{
        QString dist;
        if(!query.exec("COMMIT")) {
            errmsg = query.lastError().text();
            qDebug() << "errmsg=" << errmsg;
            return;
        }
        state = ePause;

        // Compute track distance for preview
        dist = computeTrackDistance(trackid);
        query.prepare("UPDATE track SET distance=:dist WHERE trackid=:id");
        query.bindValue(":dist", dist );
        query.bindValue(":id", trackid );
        if(!query.exec()) {
            errmsg = query.lastError().text();
            qDebug() << "errmsg=" << errmsg;
            return;
        }
    }
    emit sigChanged();
}

void CTrackDB::stop()
{
    QString dist;
    QSqlQuery query(db);
    QString errmsg;

    state = eStop;

    if(!query.exec("COMMIT")) {
      return;
    }

    // Compute track distance
    dist = computeTrackDistance(trackid);
    query.prepare("UPDATE track SET distance=:dist WHERE trackid=:id");
    query.bindValue(":dist", dist );
    query.bindValue(":id", trackid );
    if(!query.exec()) {
        errmsg = query.lastError().text();
        qDebug() << "errmsg=" << errmsg;
        return;
    }

    emit sigChanged();
    emit sigProgress(0);
}

void CTrackDB::draw(QPainter& p, const QRectF& viewport)
{

    if(trackIdx != 0){
        drawTrack(p,viewport, trackbuffer, trackIdx, CTrack::lineColors[colorIdx], QPixmap(":/icons/bullet_blue.png"));
    }
    if(trackIdx1 != 0){
        drawTrack(p,viewport, trackbuffer1, trackIdx1, CTrack::lineColors[colorIdx1], QPixmap(":/icons/bullet_red.png"));
    }
}

void CTrackDB::drawTrack(QPainter& p, const QRectF& viewport, STrackPoint * track, quint32 idx, const QColor& c, const QPixmap& bullet)
{
    qreal u,v, u1 = 0, v1 = 0, u2 = 0, v2 = 0;
    quint32 i;
    bool visible;

    IMap& map           = CMapDB::self().getMap();
    STrackPoint * trkpt = track;
    if (bullet.isNull())
        qDebug() << " no pixmap";

    visible = viewport.contains(trkpt->lon , trkpt->lat );

    u = trkpt->lon;
    v = trkpt->lat;
    // No more needed as pixel = lon/lat
    map.convertRad2Pt(u,v);

    //p.drawPixmap(u - 5, v - 5, bullet);
    p.setPen(QPen(Qt::white, 3));
    p.drawEllipse(QPoint(u, v),3,3);
    p.setPen(QPen(c, 3));
    p.drawEllipse(QPoint(u, v),2,2);

    u2 = u1 = u;
    v2 = v1 = v;
    ++trkpt;

    p.setPen(QPen(c, 3));

    for(i = 1; i <= idx; ++i, ++trkpt){

        u = trkpt->lon ;
        v = trkpt->lat ;
        // No more needed as pixel = lon/lat
         map.convertRad2Pt(u,v);

        if(viewport.contains(trkpt->lon, trkpt->lat )){

            if((abs(u - u1) + abs(v - v1)) > 10){
                p.setPen(QPen(c, 3));
                p.drawLine(u2, v2, u, v);
                p.setPen(QPen(Qt::white, 3));
                p.drawEllipse(QPoint(u, v),3,3);
                p.setPen(QPen(c, 3));
                p.drawEllipse(QPoint(u, v),2,2);
                //p.drawPixmap(u - 5, v - 5, bullet);
                u2 = u1 = u;
                v2 = v1 = v;
            }
            visible = true;
        }
        else{
            u2 = u;
            v2 = v;
            visible = false;
        }
    }
}

bool CTrackDB::add(CTrack &trk, QString &errmsg)
{
    int trackid;
    QString dist;

    QSqlQuery query(db);

    query.prepare("SELECT trackid FROM track WHERE key = :key");
    query.bindValue(":key", trk.key());
    if(!query.exec()) {
        errmsg = query.lastError().text();
        return false;
    }
    while(query.next()){
        QString errmsg;
        del(query.value(0).toInt(), errmsg);
    }

    query.prepare("INSERT INTO track ( key, date, color, name, comment) VALUES ( :key, :date, :color, :name, :comment)");
    query.bindValue(":key", trk.key());
    query.bindValue(":date", trk.timestamp);
    query.bindValue(":color", trk.colorIdx);
    query.bindValue(":name", trk.name);
    query.bindValue(":comment", trk.comment);
    if(!query.exec()) {
        errmsg = query.lastError().text();
        return false;
    }

    // Now get trackid for track recording
    if(!query.exec("SELECT MAX(trackid) from track")) {
        errmsg = query.lastError().text();
        return false;
    }

    if(!query.next()){
        errmsg = tr("Bad track id.");
        return false;
    }
    trackid = query.value(0).toInt();

    if(state != eRecord){
        query.exec("BEGIN");
    }

    const int size = trk.trkpts.size();
    for(int i = 0; i<size; ++i){
        STrackPoint& trkpt = trk.trkpts[i];
        query.prepare("INSERT INTO trackpoints ( trackid, date, lat, lon, ele, velocity, heading, vdop, hdop) VALUES ( :trackid, :date, :lat, :lon, :ele, :velocity, :heading, :vdop, :hdop)");
        query.bindValue(":trackid", trackid);
        query.bindValue(":date", trkpt.timestamp);
        query.bindValue(":lat", trkpt.lat * RAD_TO_DEG);
        query.bindValue(":lon", trkpt.lon * RAD_TO_DEG);
        query.bindValue(":ele", trkpt.ele);
        query.bindValue(":velocity", 0);
        query.bindValue(":heading", 0);
        query.bindValue(":vdop", 0);
        query.bindValue(":hdop", 0);

        if(!query.exec()) {
            errmsg = query.lastError().text();
            return false;
        }
    }

    if(state != eRecord){
        query.exec("COMMIT");
    }

    // Compute track distance for preview
    dist = computeTrackDistance(trackid);
    query.prepare("UPDATE track SET distance=:dist WHERE trackid=:id");
    query.bindValue(":dist", dist );
    query.bindValue(":id", trackid );
    if(!query.exec()) {
        errmsg = query.lastError().text();
        return false;
    }

    emit sigChanged();
    return true;
}

bool CTrackDB::del(quint32 trackid, QString& errmsg)
{
    QSqlQuery query(db);
    qDebug() << " Track ID="  << trackid;
    query.prepare("DELETE from trackpoints WHERE trackid=:trackid");
    query.bindValue(":trackid", trackid);
    if(!query.exec()) {
        errmsg = query.lastError().text();
        qDebug() << query.lastError().text();
        return false;
    }
    query.prepare("DELETE from track WHERE trackid=:trackid");
    query.bindValue(":trackid", trackid);
    if(!query.exec()) {
        errmsg = query.lastError().text();
        qDebug() << query.lastError().text();
        return false;
    }
    emit sigChanged();
    return true;
}

bool CTrackDB::delAll(QString& errmsg)
{
    QSqlQuery query(db);
    if(!query.exec("DELETE from trackpoints")) {
        errmsg = query.lastError().text();
        qDebug() << query.lastError().text();
        return false;
    }

    if(!query.exec("DELETE from track")) {
        errmsg = query.lastError().text();
        qDebug() << query.lastError().text();
        return false;
    }

    emit sigChanged();
    return true;
}

void CTrackDB::setInterval(int i)
{
    QSettings cfg;

    intvl = i;
    cfg.setValue("track/interval", intvl);
}

void CTrackDB::setExtended(bool on)
{
    QSettings cfg;

    ext = on;
    cfg.setValue("track/extended", ext);
}

bool CTrackDB::list(QStringList& keys, QStringList& names, QStringList& dist, QString& errmsg)
{
    keys.clear();
    names.clear();
    dist.clear();

    QSqlQuery query(db);
    if(!query.exec("SELECT key,name, distance FROM track ORDER BY trackid")) {
        errmsg = query.lastError().text();
        return false;
    }

    while(query.next()){
        keys  << query.value(0).toString();
        names << query.value(1).toString();
        dist << query.value(2).toString();
    }

    return true;
}

bool CTrackDB::get(const QString& key, CTrack& trk, QString& errmsg)
{
    quint32 trackid;
    QSqlQuery query(db);
    query.prepare("SELECT trackid, date, color, name, comment FROM track WHERE key=:key");
    query.bindValue(":key", key);

    if(!query.exec()) {
        errmsg = query.lastError().text();
        return false;
    }

    if(!query.next()){
        errmsg = tr("Track not found.");
        return false;
    }

    trackid         = query.value(0).toInt();
    trk._key_       = key;
    trk.timestamp   = query.value(1).toInt();
    trk.colorIdx    = query.value(2).toInt();
    trk.name        = query.value(3).toString();
    trk.comment     = query.value(4).toString();

    //select points.* from track, points where track.trackid=points.trackid order by points.date;
    query.prepare("SELECT DISTINCT trackpoints.* from track, trackpoints WHERE track.trackid=trackpoints.trackid AND track.trackid=:trackid GROUP BY trackpoints.date");
    query.bindValue(":trackid", trackid);
    if(!query.exec()) {
        errmsg = query.lastError().text();
        return false;
    }

    while(query.next()){
        STrackPoint pt;
        pt.timestamp    = query.value(2).toInt();
        pt.lat          = query.value(3).toDouble() * DEG_TO_RAD;
        pt.lon          = query.value(4).toDouble() * DEG_TO_RAD;
        pt.ele          = query.value(5).toDouble();

        trk << pt;
    }

    return true;
}

QString CTrackDB::computeTrackDistance(int trackid)
{
    qreal pointDist=0;
    qreal lon=0;
    qreal lat=0;
    qreal ele=0;
    qreal lon1=0;
    qreal lat1=0;
    qreal ele1=0;
    QSqlQuery query(db);
    QString errmsg;
    query.prepare("SELECT trackpoints.lon, trackpoints.lat, trackpoints.ele FROM track, trackpoints WHERE track.trackid=trackpoints.trackid AND track.trackid=:id");
    query.bindValue(":id", trackid);
    if(!query.exec()) {
        errmsg = query.lastError().text();
        qDebug() << "errmsg=" << errmsg;
        return false;
    }

    while(query.next()){
        qreal a1, a2;
        lon = query.value(0).toDouble();
        lat = query.value(1).toDouble();
        ele = query.value(1).toDouble();
        if ((lon1 != 0) && (lat1!=0) ) {
           pointDist += GPS_Math_Distance(lon * DEG_TO_RAD, lat * DEG_TO_RAD, lon1 * DEG_TO_RAD, lat1 * DEG_TO_RAD, a1, a2);
        }

        lat1 = lat;
        lon1 = lon;
        ele1 = ele;
    }
    QString val, unit;
    IUnit::self().meter2distance(pointDist,val,unit);
    return QString("%1 %2").arg(val).arg(unit);
}

bool CTrackDB::getTrackInfo(QString key, CTrack &track, QString& errmsg)
{
    QSqlQuery query(db);
    query.prepare("SELECT trackid, name, color, comment, distance, date FROM track WHERE key=:key");
    query.bindValue(":key", key );
    if(!query.exec()) {
        errmsg = query.lastError().text();
        return false;
    }

    while(query.next()){
        track.trackid = query.value(0).toInt();
        track.name = query.value(1).toString();
        track.colorIdx =  query.value(2).toInt();
        track.comment =  query.value(3).toString();
        track.distance = query.value(4).toString();
        track.timestamp = query.value(5).toInt();
    }
    return true;
}

bool CTrackDB::updateTrackInfo(CTrack &track, QString& errmsg)
{
    QSqlQuery query(db);

    if (track.dbKey !="") {
        query.prepare("UPDATE track SET name=:name, color=:color, comment=:comment WHERE key=:key");
        query.bindValue(":key", track.dbKey );
    } else
    {
        query.prepare("UPDATE track SET name=:name, color=:color, comment=:comment WHERE trackid=:trackid");
        query.bindValue(":trackid", track.trackid );
    }
    query.bindValue(":name", track.name );
    query.bindValue(":color", track.colorIdx );
    query.bindValue(":comment", track.comment );
    if(!query.exec()) {
        errmsg = query.lastError().text();
        return false;
    }
    emit sigChanged();
    return true;
}

bool CTrackDB::show(const QString& key, QString& errmsg)
{
    QSqlQuery query(db);
    query.prepare("SELECT track.color, trackpoints.lon, trackpoints.lat FROM track, trackpoints WHERE track.trackid=trackpoints.trackid AND track.key=:key ORDER BY trackpoints.idx");
    query.bindValue(":key", key);
    if(!query.exec()) {
        errmsg = query.lastError().text();
        qDebug() << "errmsg=" << errmsg;
        return false;
    }
    trackIdx1 = 0;
    while(query.next()){
        STrackPoint& trkpt = trackbuffer1[trackIdx1];
        colorIdx1 = query.value(0).toInt();
        trkpt.lon = query.value(1).toDouble() * DEG_TO_RAD;
        trkpt.lat = query.value(2).toDouble() * DEG_TO_RAD;
        ++trackIdx1;
    }
    emit sigChanged();
    return true;
}
