/*
 * Copyright 2014 Mihai Niculescu <q.quark@gmail.com>
 *
 * This file is part of EqualX Project (https://launchpad.net/equalx/)
 *
 * EqualX 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.
 *
 * EqualX 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 <QDebug>

#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QSqlDriver>
#include <QSqlError>
#include <QSqlQuery>
#include <QVariant>

#include "File.h"
#include "Library/Library.h"

#define DB_NAME "library.db"
#define LIB_DIR_NAME ".equalx"
#define BOOKMARKS_DIR "bookmarks"
#define HISTORY_DIR "history"
#define BOOKMARKSTOOLBAR_DIR "bookmarks-toolbar"

LibraryManager* LibraryManager::mInstance=NULL;

LibraryManager* LibraryManager::Instance()
{
    if(!mInstance) mInstance = new LibraryManager;

    return mInstance;
}

LibraryManager::LibraryManager(QObject *parent) :
    QObject(parent)
{
    mDB = QSqlDatabase::addDatabase("QSQLITE");

    if(!mDB.driver()->hasFeature(QSqlDriver::QuerySize)){
        qDebug() << "SQL Driver doesn't support QuerySize";
    }

    if(!mDB.driver()->hasFeature(QSqlDriver::Transactions)){
        qDebug() << "SQL Driver doesn't support transactions";
    }

    setup();
}

LibraryManager::~LibraryManager()
{
    mDB.close();
}

inline QString LibraryManager::path() const
{
    QString libDirPath(QDir::homePath());

    libDirPath.append(QDir::separator()).append(LIB_DIR_NAME);

    return QDir::toNativeSeparators(libDirPath);
}

bool LibraryManager::setup()
{
    mBookmarksDirStr = path().append(QDir::separator()).append(BOOKMARKS_DIR).append(QDir::separator());
    mHistoryDirStr = path().append(QDir::separator()).append(HISTORY_DIR).append(QDir::separator());

    QString libDirPath(path());

    QDir libDir(libDirPath);

    if(!libDir.exists()) createLibrary(libDirPath);

    // check existance and permissions of library database
    libDirPath.append(QDir::separator()).append(DB_NAME);
    QFileInfo lib(libDirPath);

    mDB.setDatabaseName(lib.filePath());
    if(!mDB.open())
    {
        qDebug() << "Couldn't Open the Database. Report:" << mDB.lastError().text();
    }

    QSqlQuery query;
    if(!query.exec("PRAGMA foreign_keys=ON;")){
        qDebug() << "SQL Driver Failed Trying set FOREIGN KEYS=ON";
    }
    if(!query.exec("PRAGMA foreign_keys;")){
        qDebug() << "SQL Driver Failed Trying get FOREIGN KEYS status";
    }

    query.next();
    qDebug() << "FOREIGN KEYS="<<query.value(0).toInt();

    if(mDB.tables().isEmpty()) createDatabaseTablesFromFile("://resources/createLibraryTables.sql");
    if(!lib.isReadable() && !lib.isWritable()){
        mErrStr = tr("EqualX can not read or write library database. Make sure you have the correct permissions on file: ").append(lib.absolutePath());
        return false;
    }

    return mDB.isOpen();

}

bool LibraryManager::createLibrary(const QString &path)
{
    QDir libDir(path);
    libDir.mkpath(path); // create all directories for library
    libDir.cd(path);

    libDir.mkdir(BOOKMARKS_DIR); // create a subdirectory holding all equations added to the library
    libDir.mkdir(HISTORY_DIR); // create a subdirectory to hold all history
    libDir.mkdir(BOOKMARKSTOOLBAR_DIR); // create a subdirectory to hold all equations from bookmarks toolbar

    return true;
}

bool LibraryManager::createDatabaseTablesFromFile(const QString &sqlFile)
{
    bool status = false;

    mDB.open();

    QSqlQuery query;
    QFile file(sqlFile);
    file.open(QIODevice::ReadOnly);

    char buf[2048];
    while(file.readLine(buf, sizeof(buf))!=-1){
        status = query.exec(buf);

        if(!status) qDebug() << "CreateTables() Reports:"<< query.lastError().text();
    }
    file.close();

    return status;
}

int LibraryManager::addBookmark(const QString &title, const QString &description, const QString &fromFile, int parentId)
{
    Bookmark newBookmark;
    newBookmark.idparent= parentId;
    newBookmark.title= title;
    newBookmark.description=description;
    newBookmark.filePath=fromFile;

    return addBookmark(newBookmark, parentId);
}

int LibraryManager::addBookmark(const Bookmark &bookmark, int parentId)
{
    int lastId=0;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot add bookmark because the database is closed.");
        return lastId;
    }

    if(parentId<1) parentId=1;

    QSqlQuery query;
    QString parentPath; // a null path means its parent is the root node

    if(parentId>1){ // parent is a folder
        query.prepare("SELECT dirPath FROM bookmarks_folders WHERE id=?");
        query.addBindValue(parentId);

        if(!query.exec()){
            mErrStr = tr("Cannot add equation to Bookmarks because I can not find its parent path. Database Reports: ").arg(query.lastError().text());
            return lastId;
        }
        // parent found
        query.next();
        parentPath = QString("%1/%2").arg(query.value(0).toString()).arg(parentId);
    }

    QFileInfo fromFileInfo(bookmark.filePath);
    QString fileType = fromFileInfo.completeSuffix();
    qint64 created=QDateTime::currentMSecsSinceEpoch();
    qint64 modified=created;

    query.clear();
    query.prepare("INSERT INTO bookmarks (idparent, title, description, fileType, dirPath, created, modified) "
                  "values(?,?,?,?,?,?,?)");
    query.addBindValue(parentId);
    query.addBindValue(bookmark.title);
    query.addBindValue(bookmark.description);
    query.addBindValue(fileType);
    query.addBindValue(parentPath);
    query.addBindValue(created);
    query.addBindValue(modified);

    if(!query.exec()){
        mErrStr = tr("Cannot add equation to Bookmarks. Database Reports: ").arg(query.lastError().text());
        return lastId;
    }

    // the query was successful, now copy the file to the bookmarks folder
    QString toFile = QString("%1/%2.%3").arg(mBookmarksDirStr, query.lastInsertId().toString(),fileType); // each file copyied is named after its index in the table

    qDebug() << "Copying [" << bookmark.filePath <<"] -> [" << toFile <<"] ";
    QFile::copy(bookmark.filePath, toFile);

    lastId = query.lastInsertId().toInt();

    EqualX::FileInfo eqInfo;
    EqualX::File::fetchInfo(bookmark.filePath, &eqInfo);

    QString eqLatexContent = eqInfo.equation().simplified();
    eqLatexContent=eqLatexContent.replace(" ","");

    query.clear();
    query.prepare("INSERT INTO bookmarks_content (refId, latexContent, created) "
                  "values(?,?,?)");
    query.addBindValue(lastId);
    query.addBindValue(eqLatexContent);
    query.addBindValue(created);

    if(!query.exec()){
        mErrStr = tr("Cannot add equation content to Bookmarks. Database Reports: ").arg(query.lastError().text());
        return -1;
    }

    emit bookmarkFolderChanged(parentPath);

    return lastId;
}

int LibraryManager::addBookmarkFolder(const QString &name, const QString& description, int parentId)
{
    BookmarkFolder folder;
    folder.idparent=parentId;
    folder.name=name;
    folder.description=description;

    return addBookmarkFolder(folder, parentId);
}

int LibraryManager::addBookmarkFolder(const BookmarkFolder &folder, int parentId)
{
    qDebug() << "LibraryManager::addBookmarkFolder() "<<folder.name<<" with parentId:"<< parentId;

    int lastId=-1;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot add folder in Bookmarks because the database is closed.");
        qDebug() <<  mErrStr;
        return lastId;
    }
    if(parentId<1) parentId=1;

    QSqlQuery query;
    QString parentPath;

    if(parentId>1){ // parent is an existing folder (and not the ROOT)
        query.prepare("SELECT dirPath FROM bookmarks_folders WHERE id=?");
        query.addBindValue(parentId);

        if(!query.exec()){
            mErrStr = tr("Cannot add folder to Bookmarks because I can not find its parent path. Database Reports: ").arg(query.lastError().text());
            qDebug() <<  mErrStr;
            return lastId;
        }
        // parent found
        query.next();
        parentPath = QString("%1/%2").arg(query.value(0).toString()).arg(parentId);
    }

    query.clear();
    query.prepare("INSERT INTO bookmarks_folders (idparent,name,description,dirPath,created, modified) "
                  "VALUES (?,?,?,?,?,?)");
    query.addBindValue(parentId);
    query.addBindValue(folder.name);
    query.addBindValue(folder.description);
    query.addBindValue(parentPath);
    query.addBindValue(QDateTime::currentMSecsSinceEpoch());
    query.addBindValue(QDateTime::currentMSecsSinceEpoch());

    if(!query.exec()){
        mErrStr = tr("Cannot add folder to Bookmarks. Database Reports: %1").arg(query.lastError().text());
        qDebug() <<  mErrStr;
        return lastId;
    }

    lastId = query.lastInsertId().toInt();

    qDebug() << "LibraryManager::addBookmarkFolder() lastInsertedId=" << lastId;

    emit bookmarkFolderChanged(parentPath);

    return lastId;
}

int LibraryManager::addHistoryItem(const QString &title, const QString &fromFile)
{   int lastId = -1;

    if(!mDB.isOpen()){
        mErrStr = tr("Can not add equation to History because the database is closed.");
        qDebug() <<  mErrStr;
        return lastId;
    }

    QSqlQuery query;
    query.prepare("INSERT INTO history (title, created) values(?,?)");
    query.addBindValue(title);
    query.addBindValue(QDateTime::currentMSecsSinceEpoch());

    if(!query.exec()){
        mErrStr = query.lastError().text();
        qDebug() <<  mErrStr;
        return lastId;
    }

    // the query was successful, than copy the file to the history folder
    QString toFile(mHistoryDirStr);
    toFile.append(query.lastInsertId().toString()); // each file copyied is named after its index in the table

    qDebug() << "Copying [" << fromFile <<"] -> [" << toFile <<"] ";
    QFile::copy(fromFile, toFile);

    HistoryRow row;
    row.id = query.lastInsertId().toInt();
    row.title=title;
    row.filePath = toFile;
    row.created = QDateTime::currentMSecsSinceEpoch();

    emit historyChanged();
    emit historyChanged(row);

    lastId = query.lastInsertId().toInt();
    return lastId;
}

LibraryRowsList LibraryManager::getAllHistory()
{
    LibraryRowsList rows;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access equations from History because the database is closed.");
        qDebug() <<  mErrStr;
        return rows;
    }

    QSqlQuery query;
    if(!query.exec("SELECT id,created FROM history ORDER BY id DESC")){
        qDebug() << "Cannot access History. Error: " << query.lastError().text();
        return rows;
    }

    while(query.next()){
        LibraryModelData allocated;
        allocated.id = query.value(0).toInt();
        allocated.name.append(mHistoryDirStr).append(query.value(0).toString());
        allocated.created = query.value(1).toULongLong();
        allocated.type = LibraryModelData::TypeHistory;

        rows.append(allocated);
    }

    return rows;

}

const HistoryRow LibraryManager::getHistoryRow(int id)
{
    if(id<1) id=1;

    HistoryRow allocated;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access History because the database is closed.");
        qDebug() <<  mErrStr;
        return allocated;
    }


    QSqlQuery query;
    if(!query.exec(QString("SELECT id,title,created FROM history WHERE id=%1").arg(id))){

        qDebug() << "Cannot access History. Error: " << query.lastError().text();
        return allocated;
    }

    if(query.next()){
        allocated.id = query.value(0).toInt();
        allocated.title = query.value(1).toString();
        allocated.filePath.append(mHistoryDirStr).append(query.value(0).toString());
        allocated.created = query.value(2).toULongLong();
    }

    return allocated;
}

bool LibraryManager::clearHistory()
{
    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access History because the database is closed.");
        qDebug() <<  mErrStr;
        return false;
    }

    /* Remove db entries */
    QSqlQuery query;
    if(query.exec("DELETE FROM history")){
        query.clear();
        query.exec("UPDATE sqlite_sequence SET seq=0 WHERE name='history' ");

        /* Remove files from history dir */

        //save current working directory
        QString saveDir = QDir::currentPath();

        QDir histDir(mHistoryDirStr);
        QDir::setCurrent(mHistoryDirStr);
        QStringList filesInHistory = histDir.entryList();

        foreach(QString file, filesInHistory){
            histDir.remove(file);
        }

        // restore working dir
        QDir::setCurrent(saveDir);

        emit historyChanged();

        return true;
    }

    mErrStr = query.lastError().text();
    qDebug() <<  mErrStr;

    return false;
}

Bookmark LibraryManager::getBookmark(int id)
{
    if(id<1) id=1;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Bookmarks because the database is closed.");
        qDebug() << mErrStr;
        return Bookmark();
    }

    QSqlQuery query;
    query.prepare("SELECT idparent,title,description,fileType,dirPath,created,modified FROM bookmarks where id=?");
    query.addBindValue(id);

    if(!query.exec()){
        mErrStr = "Cannot query Bookmarks. Error: " + query.lastError().text();
        qDebug() << mErrStr;
        return Bookmark();
    }

    if(!query.next()){
        mErrStr = "Cannot get bookmark from database. Error: " + query.lastError().text();
        qDebug() << mErrStr;
        return Bookmark();
    }

    Bookmark b;
    b.id = id;
    b.idparent = query.value(0).toInt();
    b.title = query.value(1).toString();
    b.description=query.value(2).toString();
    b.filePath= QDir::toNativeSeparators(QString("%1%2.%3").arg(mBookmarksDirStr).arg(id).arg(query.value(3).toString()));
    b.dirPath=query.value(4).toString();
    b.created = query.value(5).toULongLong();
    b.modified = query.value(6).toULongLong();

    return b;
}

LibraryRowsList LibraryManager::getChildren(int idparent)
{
    LibraryRowsList rows;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Bookmarks because the database is closed.");
        qDebug() <<  mErrStr;
        return rows;
    }

    if(idparent<1) idparent=1;

    /** First, retrieve Bookmarks folders **/
    QSqlQuery query;
    query.prepare("SELECT id,name,created FROM bookmarks_folders WHERE idparent=? ORDER BY name ASC");
    query.addBindValue(idparent);

    if(!query.exec()){
        mErrStr = tr("Cannot query Bookmarks Folders. Error: ") + query.lastError().text();
        qDebug() << mErrStr;
        return rows;
    }

    while(query.next()){
        LibraryModelData allocated;
        allocated.id = query.value(0).toInt();
        allocated.name=query.value(1).toString();
        allocated.created = query.value(2).toULongLong();
        allocated.type = LibraryModelData::TypeFolder;

        rows.append(allocated);
    }

    /** Finally, retrieve Bookmarks **/
    query.clear();
    if(!query.exec(QString("SELECT id,created FROM bookmarks WHERE idparent=%1 ORDER BY created ASC").arg(idparent) )){
        mErrStr = "Cannot access Bookmarks. Error: " + query.lastError().text();
        qDebug() << mErrStr;
        return rows;
    }

    while(query.next()){
        LibraryModelData allocated;
        allocated.id = query.value(0).toInt();
        allocated.name.append(mBookmarksDirStr).append(query.value(0).toString());
        allocated.created = query.value(1).toULongLong();
        allocated.type = LibraryModelData::TypeBookmark;

        rows.append(allocated);
    }



    return rows;
}

LibraryRowsList LibraryManager::getAllBookmarkedFolders()
{
    LibraryRowsList rows;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Bookmarks because the database is closed.");
        qDebug() <<  mErrStr;
        return rows;
    }

    /** First, retrieve Bookmarks folders **/
    QSqlQuery query;
    if(!query.exec("SELECT id,name,created FROM bookmarks_folders WHERE id>1 ORDER BY created DESC")){
        mErrStr = tr("Cannot query Bookmarks Folders. Error: ") + query.lastError().text();
        qDebug() << mErrStr;
        return rows;
    }

    while(query.next()){
        LibraryModelData allocated;
        allocated.id = query.value(0).toInt();
        allocated.name=query.value(1).toString();
        allocated.created = query.value(2).toULongLong();
        allocated.type = LibraryModelData::TypeFolder;

        rows.append(allocated);
    }

    return rows;
}

BookmarkFolder LibraryManager::getBookmarkFolder(int id)
{
    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Bookmarks because the database is closed.");
        qDebug() << mErrStr;
        return BookmarkFolder();
    }

    QSqlQuery query;
    query.prepare("SELECT idparent,name,description,dirPath,created,modified FROM bookmarks_folders where id=?");
    query.addBindValue(id);

    if(!query.exec()){
        mErrStr = tr("Cannot query Bookmarks. Error: ") + query.lastError().text();
        qDebug() << mErrStr;
        return BookmarkFolder();
    }

    if(!query.next()){
        mErrStr = tr("Cannot get Folder. Error: ") + query.lastError().text();
        qDebug() << mErrStr;
        return BookmarkFolder();
    }

    BookmarkFolder f;
    f.id = id;
    f.idparent=query.value(0).toInt();
    f.name = query.value(1).toString();
    f.description=query.value(2).toString();
    f.dirPath=query.value(3).toString();
    f.created = query.value(4).toULongLong();
    f.modified = query.value(5).toULongLong();

    return f;
}

int LibraryManager::childrenCountInFolder(int id)
{
    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Bookmarked equations because the database is closed.");
        qDebug() << mErrStr;
        return false;
    }

    if(id<1) id=1;

    int nChildren=0;

    /** Count Bookmarks folders **/
    QSqlQuery query;
    query.prepare("SELECT COUNT(id) FROM bookmarks_folders WHERE idparent=?");
    query.addBindValue(id);

    if(!query.exec()){
        mErrStr = tr("Couldnt count subfolders.");
        // qDebug() << mErrStr;
    } else {
        query.next();
        nChildren = query.value(0).toInt();
    }

    /** Count Bookmarks **/
    query.clear();
    query.prepare("SELECT COUNT(id) FROM bookmarks WHERE idparent=?");
    query.addBindValue(id);

    if(!query.exec()){
        mErrStr = tr("Couldnt count bookmarks in this folder.");
        // qDebug() << mErrStr;
    } else {
        query.next();
        nChildren += query.value(0).toInt();
    }

   // qDebug() << "Folder:"<<id<<" has "<< nChildren << " children";

    return nChildren;
}

int LibraryManager::historySize()
{
    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access History because the database is closed.");
        qDebug() << mErrStr;
        return false;
    }

    int nChildren=0;

    QSqlQuery query;
    if(!query.exec("SELECT COUNT(id) FROM history")){
        mErrStr = tr("Couldnt count history.");
        // qDebug() << mErrStr;
    } else {
        query.next();
        nChildren = query.value(0).toInt();
    }

    return nChildren;
}

Bookmark LibraryManager::copyBookmark(int id, int idparent)
{
    qDebug() << "LibraryManager::copyBookmark("<<id<<") => Folder:"<<idparent;

    Bookmark bookmark = getBookmark(id);
    //BookmarkFolder folder = getBookmarkFolder(idparent);

    if(bookmark.isValid()){ // we have valid objects in database
        int newId = addBookmark(bookmark, idparent);

        // the query was successful, now copy the file to the bookmarks folder
        QString toFile = QString("%1/%2.%3").arg(mBookmarksDirStr).arg(newId).arg(bookmark.fileExt()); // each file copyied is named after its index in the table

        qDebug() << "Copying [" << bookmark.filePath <<"] -> [" << toFile <<"] ";
        QFile::copy(bookmark.filePath, toFile);

        // no need since addBookmark() above will emit the signal
        //emit bookmarkFolderChanged(parentDirPath);

        return getBookmark(newId);
    }

    return Bookmark();

}

BookmarkFolder LibraryManager::copyBookmarkFolder(int id, int idparent)
{
    qDebug() << "LibraryManager::copyBookmarkFolder("<<id<<" "<< idparent<<")";

    BookmarkFolder folder = getBookmarkFolder(id);
    BookmarkFolder toParentfolder = getBookmarkFolder(idparent);

    if(folder.isValid() && toParentfolder.isValid()){ // we have valid objects in database
        int newid = addBookmarkFolder(folder, idparent);

        copyChildren(id, newid);

        // no need since addBookmarkFolder() above will emit the signal
        //emit bookmarkFolderChanged(parentDirPath);

        return getBookmarkFolder(newid);
    }

    return BookmarkFolder();
}

void LibraryManager::copyChildren(int ofFolderId, int toNewFolderId)
{
    qDebug() << "LibraryManager::copyChildren("<<ofFolderId<<" "<< toNewFolderId<<")";

    if(ofFolderId<=1 || toNewFolderId<1) return;

    // copy children
    foreach(LibraryModelData row, getChildren(ofFolderId)){
        if(row.isFolder()){
            qDebug() << "\t Has Subfolder: " << row.name << " id:"<<row.id;
            BookmarkFolder folder = getBookmarkFolder(row.id);
            // copy the folder
            qDebug() << "\t Adding Subfolder: " << row.name << " id:"<<row.id;
            int newId = addBookmarkFolder(folder, toNewFolderId);
            qDebug() << "\t New Subfolder has id:"<<newId;
            if(newId>0){
                copyChildren(row.id, newId);
            }
        }
        else if(row.isBookmark()){
            qDebug() << "\t Has Bookmark: " << row.name;
            copyBookmark(row.id, toNewFolderId);
        }


    }

}

bool LibraryManager::moveData(const LibraryModelData &data, int newParentId)
{
    bool status = false;
    if(data.isValid()){ // if the object is already in database
        if(data.isFolder())
            status =  moveBookmarkFolder(data.id, newParentId);
        else if(data.isBookmark())
            status =  moveBookmark(data.id, newParentId);
    }
    else {
        if(data.isFolder())
            status =  addBookmarkFolder(BookmarkFolder(data), newParentId);
        else if(data.isBookmark())
            status =  addBookmark(Bookmark(data), newParentId);
    }

    return status;
}

bool LibraryManager::moveBookmark(int id, int newParentId)
{
    /****************************************************************************************/
    /* We want to re-parent the bookmark                                                    */
    /*--------------------------------------------------------------------------------------*/
    /* - we must also change the its field 'dirPath' in database                            */
    /*--------------------------------------------------------------------------------------*/

    // First lets find the current dirPath
    QSqlQuery query;
    QString oldDirPath;


    query.prepare("SELECT dirPath FROM bookmarks WHERE id=?");
    query.addBindValue(id);

    if(!query.exec()){
        qDebug() << "Cannot find bookmark with id="<< id <<" . Error: " << query.lastError().text();
        return false;
    }

    if(!query.next()){
        qDebug() << "Cannot fetch dirPath of bookmark. Error: " << query.lastError().text();
        return false;
    }

    oldDirPath = query.value(0).toString();

    QString newDirPath;
    if(newParentId>1){
        // Now, find its new dirPath
        query.prepare("SELECT dirPath FROM bookmarks_folders WHERE id=?");
        query.addBindValue(newParentId);

        if(!query.exec()){
            qDebug() << "Cannot find Folder in Bookmarks. Error: " << query.lastError().text();
            return false;
        }

        if(!query.next()){
            qDebug() << "Cannot fetch dirPath of new parent Folder in Bookmarks. Error: " << query.lastError().text();
            return false;
        }

        newDirPath = QString("%1/%2").arg(query.value(0).toString()).arg(newParentId);
    }
    // now update bookmark to the new parent ID and new dirPath
    query.clear();
    query.prepare("UPDATE bookmarks SET idparent=?,dirPath=?,modified=? WHERE id=?");
    query.addBindValue(newParentId);
    query.addBindValue(newDirPath);
    query.addBindValue(QDateTime::currentMSecsSinceEpoch());
    query.addBindValue(id);

    if(!query.exec()){
        qDebug() << "Cannot reparent bookmark . Error: " << query.lastError().text();
        return false;
    }

    emit bookmarkFolderChanged(oldDirPath);
    emit bookmarkFolderChanged(newDirPath);

    return true;
}

bool LibraryManager::moveBookmarkFolder(int id, int newParentId)
{
    /****************************************************************************************/
    /* We want to re-parent the folder                                                      */
    /*--------------------------------------------------------------------------------------*/
    /* - we must also change the field 'dirPath' in database of its subfolders and bookmarks*/
    /*--------------------------------------------------------------------------------------*/

    // First lets find the current folders 'dirPath'
    QSqlQuery query;
    query.prepare("SELECT dirPath FROM bookmarks_folders WHERE id=?");
    query.addBindValue(id);

    if(!query.exec()){
        qDebug() << "Cannot find Folder in Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    query.next();
    QString oldDirPath = QString("%1/%2").arg(query.value(0).toString()).arg(id);

    QString newDirPath;
    if(newParentId>1){
        // then lets find the new parents 'dirPath'
        query.clear();
        query.prepare("SELECT dirPath FROM bookmarks_folders WHERE id=?");
        query.addBindValue(newParentId);

        if(!query.exec()){
            qDebug() << "Cannot find parent of Folder from Bookmarks. Error: " << query.lastError().text();
            return false;
        }

        query.next();
        newDirPath = QString("%1/%2").arg(query.value(0).toString()).arg(newParentId);
    }

    // now update folder to the new parent ID and new dirPath
    query.clear();
    query.prepare("UPDATE bookmarks_folders SET idparent=?,dirPath=? WHERE id=?");
    query.addBindValue(newParentId);
    query.addBindValue(newDirPath);
    query.addBindValue(id);

    if(!query.exec()){
        qDebug() << "Cannot reparent Folder from Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    // now update all subfolders to the new dirPath
    newDirPath = QString("%1/%2").arg(newDirPath).arg(id);
    query.clear();
    query.prepare("UPDATE bookmarks_folders SET dirPath=replace(dirPath,?,?)  WHERE dirPath LIKE ? || '%' ");
    query.addBindValue(oldDirPath);
    query.addBindValue(newDirPath);
    query.addBindValue(oldDirPath);

    if(!query.exec()){
        qDebug() << "Cannot reparent subfolders of this Folder from Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    // now update all bookmarks from this folder and its subfolders to the new dirPath
    query.clear();
    query.prepare("UPDATE bookmarks SET dirPath=replace(dirPath,?,?) WHERE dirPath LIKE ? || '%' ");
    query.addBindValue(oldDirPath);
    query.addBindValue(newDirPath);
    query.addBindValue(oldDirPath);

    if(!query.exec()){
        qDebug() << "Cannot reparent subfolders of this Folder from Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    emit bookmarkFolderChanged(oldDirPath);
    emit bookmarkFolderChanged(newDirPath);

    return true;
}


bool LibraryManager::updateData(const LibraryModelData &data)
{
    bool status = false;
    if(data.isValid()){ // if the object is already in database
        if(data.isFolder())
            status =  updateBookmarkFolder(BookmarkFolder(data));
        else if(data.isBookmark())
            status =  updateBookmark(Bookmark(data));
    }

    return status;
}

bool LibraryManager::updateBookmark(const Bookmark &bookmark)
{
    qDebug() << "updateing Bookmark id:" << bookmark.id << " parentid=" << bookmark.idparent;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Bookmarks because the database is closed.");
        return false;
    }

    // we dont want to re-parent the bookmark, we simply want to update its own fields
    QSqlQuery query;
    query.prepare("UPDATE bookmarks SET title=?,description=?,modified=? WHERE id=?");
    query.addBindValue(bookmark.title);
    query.addBindValue(bookmark.description);
    query.addBindValue(QVariant(QDateTime::currentMSecsSinceEpoch()));
    query.addBindValue(bookmark.id);

    if(!query.exec()){
        qDebug() << "Cannot update Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    return true;
}

bool LibraryManager::updateBookmarkFolder(const BookmarkFolder &folder)
{
    qDebug() << "updateing Folder in id:" << folder.id << " with parentid=" << folder.idparent;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Bookmarks because the database is closed.");
        return false;
    }

    // we dont want to re-parent the folder, we simply want to update its own fields
    QSqlQuery query;
    query.prepare("UPDATE bookmarks_folders SET name=?,description=?,modified=? WHERE id=?");
    query.addBindValue(QVariant(folder.name));
    query.addBindValue(QVariant(folder.description));
    query.addBindValue(QVariant(QDateTime::currentMSecsSinceEpoch()));
    query.addBindValue(folder.id);

    if(!query.exec()){
        qDebug() << "Cannot update Folders from Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    emit bookmarkFolderChanged(folder.dirPath);

    return true;
}

bool LibraryManager::removeBookmark(int id)
{
    qDebug() << "LibraryManager::removeBookmark("<< id <<")";

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Library because the database is closed.");
        return false;
    }

    QString parentDirPath;

    QSqlQuery query;
    query.prepare("SELECT fileType,dirPath FROM bookmarks WHERE id=?");
    query.addBindValue(id);

    if(!query.exec()){
        mErrStr = tr("Cannot find bookmark. Error: ") + query.lastError().text();
        qDebug() << mErrStr;

        return false;
    }
    if(!query.next()){
        mErrStr = tr("Cannot get bookmark. Error: ") + query.lastError().text();
        qDebug() << mErrStr;

        return false;
    }

    QString fileType = query.value(0).toString();
    parentDirPath = query.value(1).toString();

    query.clear();
    query.prepare("DELETE FROM bookmarks WHERE id=?");
    query.addBindValue(id);

    if(!query.exec()){
        mErrStr = tr("Cannot delete bookmark. Error: ") + query.lastError().text();
        qDebug() << mErrStr;

        return false;
    }

    emit bookmarkFolderChanged(parentDirPath);

    return removeBookmarkFromFS(id, fileType);

}

bool LibraryManager::removeBookmarkFolder(int id)
{
    qDebug() <<  "LibraryManager::removeBookmarkFolder("<< id <<")";

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot access Library because the database is closed.");
        return false;
    }

    if(id<=1)
        return false;

    // fetch the dirPath of the folder
    QString dirPath;
    QString parentDirPath;

    //mDB.transaction();

    QSqlQuery query;
    query.prepare("SELECT dirPath FROM bookmarks_folders WHERE id=?");
    query.addBindValue(id);

    if(!query.exec()){
        qDebug() <<  "Cannot select Folder from Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    query.next();
    parentDirPath = query.value(0).toString();
    dirPath = QString("%1/%2").arg(parentDirPath).arg(id);

    //! before deleteing the bookmarks, we must remove the bookmarks files from file system
    // find all bookmarks in this folder and its subfolders
    query.clear();
    query.prepare("SELECT id,fileType FROM bookmarks WHERE dirPath LIKE ? || '%' ");
    query.addBindValue(dirPath);
    if(!query.exec()){
        qDebug() << "Cannot find bookmarks in this Folder in Library. Error: " << query.lastError().text();
        return false;
    }

    int fileId;
    QString fileType;
    while(query.next()){
        fileId = query.value(0).toInt();
        fileType = query.value(1).toString();

        removeBookmarkFromFS(fileId, fileType);
    }

    /****************************************/
    /* delete the folder from the database */
    /****************************************/
    query.clear();
    query.prepare("DELETE FROM bookmarks_folders WHERE id=?");
    query.addBindValue(id);

    if(!query.exec()){
        qDebug() <<  "Cannot delete Folder from Bookmarks. Error: " << query.lastError().text();
        return false;
    }

    // mDB.commit();

    emit bookmarkFolderChanged(parentDirPath);

    return true;
}


bool LibraryManager::removeSystemDir(const QString &dirName)
{
    bool result = true;
    QDir dir(dirName);

    if (dir.exists(dirName)) {
        foreach(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden  | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
            if (info.isDir()) {
                result = removeSystemDir(info.absoluteFilePath());
            }
            else {
                result = QFile::remove(info.absoluteFilePath());
            }

            if (!result) {
                return result;
            }
        }
        result = dir.rmdir(dirName);
    }
    return result;
}

inline bool LibraryManager::removeBookmarkFromFS(int id, const QString &filetype)
{
    QString filePath = QDir::toNativeSeparators(QString("%1/%2.%3").arg(mBookmarksDirStr).arg(id).arg(filetype));

    return QFile::remove(filePath);
}

LibraryRowsList LibraryManager::searchBookmarks(QString searchText, SEARCH_MODE searchType)
{
    qDebug() <<  "LibraryManager::searchBookmarks("<< searchText <<") Search Mode="<<searchType;

    LibraryRowsList rows;

    if(!mDB.isOpen()){
        mErrStr = tr("Cannot search Bookmarks because the database is closed.");
        return rows;
    }

    QSqlQuery q;
    QString searchQuery;

    switch (searchType) {
    case SEARCH_BY_TITLE:
        searchQuery = "SELECT id,created FROM bookmarks WHERE title LIKE '%' || ? || '%' ORDER BY created DESC";
        break;
    case SEARCH_BY_DESCRIPTION:
        searchQuery = "SELECT id,created FROM bookmarks WHERE description LIKE '%' || ? || '%' ORDER BY created DESC";
        break;
    case SEARCH_BY_CONTENT:
        searchQuery = "SELECT refid,created FROM bookmarks_content WHERE latexContent LIKE '%' || ? || '%' ORDER BY created DESC";
        searchText=searchText.simplified().replace(" ","");
        break;
    default: // Title and Description
        searchQuery = "SELECT id,created FROM bookmarks WHERE (title LIKE '%' || ? || '%') | (description LIKE '%' || ? || '%') ORDER BY created DESC";
        break;
    }

    q.prepare(searchQuery);
    q.addBindValue(searchText);
    if(searchType==SEARCH_BY_TITLE_DESCRIPTION) q.addBindValue(searchText);

    if(!q.exec() ){
        mErrStr = "Cannot access Bookmarks. Error: " + q.lastError().text();
        qDebug() << mErrStr;
        return rows;
    }

    while(q.next()){
        LibraryModelData allocated;
        allocated.id = q.value(0).toInt();
        allocated.name.append(mBookmarksDirStr).append(q.value(0).toString());
        allocated.created = q.value(1).toULongLong();
        allocated.type = LibraryModelData::TypeBookmark;

        rows.append(allocated);
    }

    return rows;
}
