/**********************************************************************************************
    Copyright (C) 2014 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 3 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, see <http://www.gnu.org/licenses/>.

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

#include "qlgt/CQlb.h"
#include "qlgt/CQlgtDb.h"
#include "qlgt/CQlgtFolder.h"
#include "qlgt/CQlgtRoute.h"
#include "qlgt/CQlgtTrack.h"
#include "qlgt/CQlgtWpt.h"
#include "qlgt/CQmsDb.h"
#include "qlgt/IQlgtOverlay.h"
#include "tool/CImportDatabase.h"

#include "gis/db/macros.h"
#include "gis/ovl/CGisItemOvlArea.h"
#include "gis/rte/CGisItemRte.h"
#include "gis/trk/CGisItemTrk.h"
#include "gis/wpt/CGisItemWpt.h"
#include "helpers/CProgressDialog.h"
#include "helpers/CWptIconManager.h"

#include "CMainWindow.h"

#include <QtSql>
#include <QtWidgets>

#define DB_QLGT_VERSION 9

CQlgtDb::CQlgtDb(const QString &filename, CImportDatabase *parent)
    : gui(parent)
    , nItems(0)
    , nFolders(0)
    , nWpt(0)
    , nTrk(0)
    , nRte(0)
    , nOvl(0)
    , nDiary(0)
{
    db = QSqlDatabase::addDatabase("QSQLITE", "qlandkarte");
    db.setDatabaseName(filename);
    db.open();

    QFileInfo fi(filename);
    path = fi.absoluteDir();
    name = fi.fileName();

    QSqlQuery query(db);

    if(!query.exec("PRAGMA locking_mode=EXCLUSIVE"))
    {
        return;
    }

    if(!query.exec("PRAGMA synchronous=OFF"))
    {
        return;
    }

    if(!query.exec("PRAGMA temp_store=MEMORY"))
    {
        return;
    }

    if(!query.exec("PRAGMA default_cache_size=1000"))
    {
        return;
    }

    if(!query.exec("PRAGMA page_size=8192"))
    {
        return;
    }

    if(!query.exec("SELECT version FROM versioninfo"))
    {
        initDB();
    }
    else if(query.next())
    {
        int version = query.value(0).toInt();
        if(version != DB_QLGT_VERSION)
        {
            migrateDB(version);
        }
    }
    else
    {
        initDB();
    }

    printStatistic();
}

CQlgtDb::~CQlgtDb()
{
}

void CQlgtDb::initDB()
{
    qDebug() << "void CGeoDB::initDB()";
    QSqlQuery query(db);

    if(query.exec( "CREATE TABLE versioninfo ( version TEXT )"))
    {
        query.prepare( "INSERT INTO versioninfo (version) VALUES(:version)");
        query.bindValue(":version", DB_QLGT_VERSION);
        QUERY_EXEC();
    }

    if(!query.exec( "CREATE TABLE folders ("
                    "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "type           INTEGER,"
                    "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
                    "icon           TEXT NOT NULL,"
                    "name           TEXT NOT NULL,"
                    "comment        TEXT,"
                    "locked       BOOLEAN DEFAULT FALSE"
                    ")"))
    {
        qDebug() << query.lastQuery();
        qDebug() << query.lastError();
    }

    if(!query.exec( "CREATE TABLE items ("
                    "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "type           INTEGER,"
                    "key            TEXT NOT NULL,"
                    "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
                    "icon           TEXT NOT NULL,"
                    "name           TEXT NOT NULL,"
                    "comment        TEXT,"
                    "data           BLOB NOT NULL"
                    ")"))
    {
        qDebug() << query.lastQuery();
        qDebug() << query.lastError();
    }

    if(!query.exec( "CREATE TABLE workspace ("
                    "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "type           INTEGER NOT NULL,"
                    "changed        BOOLEAN DEFAULT FALSE,"
                    "data           BLOB NOT NULL,"
                    "key            TEXT NOT NULL"
                    ")"))
    {
        qDebug() << query.lastQuery();
        qDebug() << query.lastError();
    }

    if(!query.exec("INSERT INTO folders (icon, name, comment) VALUES ('', 'database', '')"))
    {
        qDebug() << query.lastQuery();
        qDebug() << query.lastError();
    }

    if(!query.exec( "CREATE TABLE folder2folder ("
                    "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "parent         INTEGER NOT NULL,"
                    "child          INTEGER NOT NULL,"
                    "FOREIGN KEY(parent) REFERENCES folders(id),"
                    "FOREIGN KEY(child) REFERENCES folders(id)"
                    ")"))
    {
        qDebug() << query.lastQuery();
        qDebug() << query.lastError();
    }

    if(!query.exec( "CREATE TABLE folder2item ("
                    "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "parent         INTEGER NOT NULL,"
                    "child          INTEGER NOT NULL,"
                    "FOREIGN KEY(parent) REFERENCES folders(id),"
                    "FOREIGN KEY(child) REFERENCES items(id)"
                    ")"))
    {
        qDebug() << query.lastQuery();
        qDebug() << query.lastError();
    }

    if(!query.exec( "CREATE TABLE diarys ("
                    "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "parent         INTEGER NOT NULL,"
                    "key            TEXT NOT NULL,"
                    "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
                    "data           BLOB NOT NULL,"
                    "FOREIGN KEY(parent) REFERENCES folders(id)"
                    ")"))
    {
        qDebug() << query.lastQuery();
        qDebug() << query.lastError();
    }
}


void CQlgtDb::migrateDB(int version)
{
    qDebug() << "void CGeoDB::migrateDB(int version)" << version;
    QSqlQuery query(db);

    for(version++; version <= DB_QLGT_VERSION; version++)
    {
        switch(version)
        {
        case 1:
            break;

        case 2:
        {
            if(!query.exec( "CREATE TABLE workspace ("
                            "id             INTEGER PRIMARY KEY NOT NULL,"
                            "changed        BOOLEAN DEFAULT FALSE,"
                            "data           BLOB NOT NULL"
                            ")"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            break;
        }

        case 3:
        {
            if(!query.exec("ALTER TABLE workspace ADD COLUMN key TEXT"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }
            if(!query.exec("ALTER TABLE workspace ADD COLUMN type INTEGER"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }
            break;
        }

        case 4:
        {
            if(!query.exec("ALTER TABLE folders ADD COLUMN type INTEGER"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            query.prepare("UPDATE folders SET type=:type WHERE icon=:icon");
            query.bindValue("type", eFolder1);
            query.bindValue("icon", ":/icons/iconFolderBlue16x16.png");
            if(!query.exec())
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            query.prepare("UPDATE folders SET type=:type WHERE icon=:icon");
            query.bindValue(":type", eFolder2);
            query.bindValue(":icon", ":/icons/iconFolderGreen16x16.png");
            if(!query.exec())
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            break;
        }

        case 5:
        {
            if (QFile::exists(path.absoluteFilePath("qlgt_save_v4.db")))
            {
                QFile::remove(path.absoluteFilePath("qlgt_save_v4.db"));
            }
            QFile f(path.absoluteFilePath(name));
            f.copy(path.absoluteFilePath("qlgt_save_v4.db"));

            QSqlQuery query2(db);

            if(!query.exec("SELECT id, type, icon FROM items"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            const int total = query.size();
            quint32 progCnt = 0;
            PROGRESS_SETUP(tr("Migrating database from version 4 to 5."), 0, total, CMainWindow::getBestWidgetForParent());

            while(query.next())
            {
                QPixmap pixmap;

                if(query.value(1).toInt() == eWpt || query.value(1).toInt() == eRte)
                {
                    QPointF focus;
                    pixmap = CWptIconManager::self().getWptIconByName(query.value(2).toString(), focus);
                }
                else if(query.value(1).toInt() == eTrk)
                {
                    pixmap = QPixmap(16, 16);
                    pixmap.fill(query.value(2).toString());
                }
                else
                {
                    pixmap = QPixmap(query.value(2).toString());
                }

                QByteArray bytes;
                QBuffer buffer(&bytes);
                buffer.open(QIODevice::WriteOnly);
                pixmap.save(&buffer, "XPM");

                query2.prepare("UPDATE items SET icon=:icon WHERE id=:id");
                query2.bindValue(":id", query.value(0).toULongLong());
                query2.bindValue(":icon", bytes);
                if(!query2.exec())
                {
                    qDebug() << query.lastQuery();
                    qDebug() << query.lastError();
                    return;
                }

                PROGRESS(progCnt++, continue);
            }
            break;
        }

        case 6:
        {
            QSqlQuery query2(db);

            if(!query.exec("SELECT id, data, type FROM items"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            const int total = query.size();
            quint32 progCnt = 0;
            PROGRESS_SETUP(tr("Migrating database from version 5 to 6."), 0, total, CMainWindow::getBestWidgetForParent());

            while(query.next())
            {
                QByteArray array = query.value(1).toByteArray();
                QBuffer buffer(&array);
                CQlb qlb(this);
                qlb.load(&buffer);

                switch(query.value(2).toInt())
                {
                case eWpt:
                    array = qlb.waypoints();
                    break;

                case eTrk:
                    array = qlb.tracks();
                    break;

                case eRte:
                    array = qlb.routes();
                    break;

                case eOvl:
                    array = qlb.overlays();
                    break;
                }

                query2.prepare("UPDATE items SET data=:data WHERE id=:id");
                query2.bindValue(":data", array);
                query2.bindValue(":id", query.value(0));

                if(!query2.exec())
                {
                    qDebug() << query.lastQuery();
                    qDebug() << query.lastError();
                    return;
                }

                PROGRESS(progCnt++, continue);
            }

            break;
        }

        case 7:
        {
            QSqlQuery query2(db);

            if(!query.exec("SELECT id, data, type FROM items"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            const int total = query.size();
            quint32 progCnt = 0;
            PROGRESS_SETUP(tr("Migrating database from version 6 to 7."), 0, total, CMainWindow::getBestWidgetForParent());

            while(query.next())
            {
                QString comment;
                QByteArray array = query.value(1).toByteArray();
                QDataStream stream(&array, QIODevice::ReadOnly);
                stream.setVersion(QDataStream::Qt_4_5);

                quint64 id = query.value(0).toULongLong();

                switch(query.value(2).toInt())
                {
                case eWpt:
                {
                    CQlgtWpt wpt(id, nullptr);
                    stream >> wpt;
                    comment = wpt.getInfo();
                }
                break;

                case eTrk:
                {
                    CQlgtTrack trk(id, nullptr);
                    stream >> trk;
                    comment = trk.getInfo();
                }
                break;

                case eRte:
                {
                    CQlgtRoute rte(id, nullptr);
                    stream >> rte;
                    comment = rte.getInfo();
                }
                break;

                case eOvl:
                {
                    //                            IOverlay ovl(0);
                    //                            stream >> ovl;
                    //                            comment = ovl.getInfo();
                    continue;
                }
                break;
                }

                query2.prepare("UPDATE items SET comment=:comment WHERE id=:id");
                query2.bindValue(":comment", comment);
                query2.bindValue(":id", query.value(0));

                if(!query2.exec())
                {
                    qDebug() << query.lastQuery();
                    qDebug() << query.lastError();
                    return;
                }

                PROGRESS(progCnt++, continue);
            }

            break;
        }

        case 8:
        {
            PROGRESS_SETUP(tr("Migrating database from version 7 to 8."), 0, 1, CMainWindow::getBestWidgetForParent());

            if(!query.exec( "CREATE TABLE diarys ("
                            "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
                            "parent         INTEGER NOT NULL,"
                            "key            TEXT NOT NULL,"
                            "date           DATETIME DEFAULT CURRENT_TIMESTAMP,"
                            "data           BLOB NOT NULL,"
                            "FOREIGN KEY(parent) REFERENCES folders(id)"
                            ")"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            PROGRESS(1, return );
            break;
        }

        case 9:
        {
            if (QFile::exists(path.absoluteFilePath(name)))
            {
                QFile::remove(path.absoluteFilePath("qlgt_save_v8.db"));
            }
            QFile f(path.absoluteFilePath(name));
            f.copy(path.absoluteFilePath("qlgt_save_v4.db"));

            PROGRESS_SETUP(tr("Migrating database from version 8 to 9."), 0, 1, CMainWindow::getBestWidgetForParent());

            if(!query.exec("ALTER TABLE folders ADD COLUMN locked BOOLEAN DEFAULT FALSE"))
            {
                qDebug() << query.lastQuery();
                qDebug() << query.lastError();
                return;
            }

            PROGRESS(1, return );
            break;
        }
        }
    }
    query.prepare( "UPDATE versioninfo set version=:version");
    query.bindValue(":version", version - 1);
    QUERY_EXEC();
}

void CQlgtDb::printStatistic()
{
    QSqlQuery query(db);

    gui->stdOut(tr("Open database: %1").arg(db.databaseName()));

    nItems = 0;

    query.prepare("SELECT COUNT() FROM folders");
    QUERY_EXEC();
    if(query.next())
    {
        nFolders = query.value(0).toInt();
        gui->stdOut(tr("Folders:          %1").arg(nFolders));
    }

    query.prepare("SELECT COUNT() FROM items WHERE type=:type");
    query.bindValue(":type", eTrk);
    QUERY_EXEC();
    if(query.next())
    {
        nItems += query.value(0).toInt();
        gui->stdOut(tr("Tracks:           %1").arg(query.value(0).toInt()));
    }
    query.prepare("SELECT COUNT() FROM items WHERE type=:type");
    query.bindValue(":type", eRte);
    QUERY_EXEC();
    if(query.next())
    {
        nItems += query.value(0).toInt();
        gui->stdErr(tr("Routes:           %1 (Only the basic route will be copied)").arg(query.value(0).toInt()));
    }
    query.prepare("SELECT COUNT() FROM items WHERE type=:type");
    query.bindValue(":type", eWpt);
    QUERY_EXEC();
    if(query.next())
    {
        nItems += query.value(0).toInt();
        gui->stdOut(tr("Waypoints:        %1").arg(query.value(0).toInt()));
    }
    query.prepare("SELECT COUNT() FROM items WHERE type=:type");
    query.bindValue(":type", eOvl);
    QUERY_EXEC();
    if(query.next())
    {
        nItems += query.value(0).toInt();
        gui->stdErr(tr("Overlays:         %1 (areas will be converted as areas, distance lines will be converted to tracks, all other overlay items will be lost)").arg(query.value(0).toInt()));
    }
    query.prepare("SELECT COUNT() FROM diarys");
    query.bindValue(":type", eDry);
    QUERY_EXEC();
    if(query.next())
    {
        gui->stdOut(tr("Diaries:          %1").arg(query.value(0).toInt()));
    }
    query.prepare("SELECT COUNT() FROM items WHERE type=:type");
    query.bindValue(":type", eMap);
    QUERY_EXEC();
    if(query.next())
    {
        gui->stdErr(tr("Map selections:   %1 (can't be converted to QMapShack)").arg(query.value(0).toInt()));
    }
}

void CQlgtDb::start(const QString& filename)
{
    gui->stdOut(tr("------ Start to convert database to %1------").arg(filename));
    dbQms = new CQmsDb(filename, gui);
    if(!dbQms->isValid())
    {
        gui->stdErr(tr("Failed to create target database."));
        gui->stdOut(tr("------ Abort ------"));
        return;
    }


    xferItems();
    xferFolders();

    QSqlQuery query(db);
    query.prepare("Select parent, child FROM folder2folder");
    QUERY_EXEC(return );
    while(query.next())
    {
        quint64 idParent    = query.value(0).toULongLong();
        quint64 idChild     = query.value(1).toULongLong();
        dbQms->addFolder2FolderRelation(idParent, idChild);
    }

    query.prepare("Select parent, child FROM folder2item");
    QUERY_EXEC(return );
    while(query.next())
    {
        quint64 idParent    = query.value(0).toULongLong();
        quint64 idChild     = query.value(1).toULongLong();
        dbQms->addFolder2ItemRelation(idParent, idChild);
    }

    delete dbQms;
    gui->stdOut(tr("------ Done ------"));
}

void CQlgtDb::xferFolders()
{
    nDiary = 0;

    quint32 cnt = 1;
    PROGRESS_SETUP(tr("Restore folders..."), 0, nFolders, gui);

    QSqlQuery query(db);
    query.prepare("SELECT id FROM folders");
    QUERY_EXEC(return );
    while(query.next())
    {
        PROGRESS(cnt++, break);

        quint64 idFolder = query.value(0).toULongLong();

        CQlgtFolder folder1(idFolder, db);
        if(folder1.diary)
        {
            nDiary++;
        }

        dbQms->addFolder(folder1);
    }
    progress.setValue(100);
    gui->stdOut(tr("Imported %1 folders and %2 diaries").arg(nFolders).arg(nDiary));
}

void CQlgtDb::xferItems()
{
    quint32 cnt = 1;
    PROGRESS_SETUP(tr("Copy items..."), 0, nItems, gui);

    nWpt = 0;
    nTrk = 0;
    nRte = 0;
    nOvl = 0;


    QSqlQuery query(db);
    query.prepare("SELECT id FROM items");
    QUERY_EXEC(return );
    while(query.next())
    {
        PROGRESS(cnt++, break);
        xferItem(query.value(0).toULongLong());
    }
    progress.setValue(100);

    gui->stdOut(tr("Imported %1 tracks, %2 waypoints, %3 routes, %4 areas").arg(nTrk).arg(nWpt).arg(nRte).arg(nOvl));
    gui->stdOut(tr("Import folders..."));

    query.prepare("SELECT id FROM folders");
    QUERY_EXEC(return );
}

void CQlgtDb::xferItem(quint64 id)
{
    QSqlQuery query(db);
    query.prepare("SELECT type, data FROM items WHERE id=:id");
    query.bindValue(":id", id);
    QUERY_EXEC(return );

    if(query.next())
    {
        QByteArray data = query.value(1).toByteArray();
        QDataStream stream(&data, QIODevice::ReadOnly);
        stream.setVersion(QDataStream::Qt_4_5);


        switch(query.value(0).toInt())
        {
        case eWpt:
        {
            CQlgtWpt wpt1(id, 0);
            stream >> wpt1;
            dbQms->addWpt(wpt1);
            nWpt++;
            break;
        }

        case eTrk:
        {
            CQlgtTrack trk1(id, 0);
            stream >> trk1;
            dbQms->addTrk(trk1);
            nTrk++;
            break;
        }

        case eRte:
        {
            CQlgtRoute rte1(id, 0);
            stream >> rte1;
            dbQms->addRte(rte1);
            nRte++;
            break;
        }

        case eOvl:
        {
            IQlgtOverlay ovl1(id, 0);
            stream >> ovl1;
            if(ovl1.type == "Area")
            {
                dbQms->addArea(ovl1);
                nOvl++;
            }
            else if(ovl1.type == "Distance")
            {
                dbQms->addTrk(ovl1);
                nTrk++;
            }
            else
            {
                gui->stdErr(tr("Overlay of type '%1' cant be converted").arg(ovl1.type));
                nOvl++;
                break;
            }


            break;
        }
        }
    }
}
