/*
 *  QCMA: Cross-platform content manager assistant for the PS Vita
 *
 *  Copyright (C) 2014  Codestation
 *
 *  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 .
 */
#include "cmautils.h"
#include "sqlitedb.h"
#include "sforeader.h"
#ifdef FFMPEG_ENABLED
#include "avdecoder.h"
#endif
#include 
#include 
#include 
#include 
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include 
#else
#include 
#define QStandardPaths QDesktopServices
#define writableLocation storageLocation
#endif
#include 
#include 
static const char create_adjacent[] = "CREATE TABLE IF NOT EXISTS adjacent_objects ("
                                      "parent_id INTEGER NOT NULL REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                      "child_id INTEGER NOT NULL REFERENCES object_node(object_id) ON DELETE CASCADE)";
static const char create_obj_node[] = "CREATE TABLE IF NOT EXISTS object_node ("
                                      "object_id INTEGER PRIMARY KEY AUTOINCREMENT,"
                                      "type INTEGER NOT NULL,"
                                      "data_type INTEGER NOT NULL,"
                                      "title TEXT,"
                                      "child_count INTEGER NOT NULL DEFAULT 0,"
                                      "reference_count INTEGER NOT NULL DEFAULT 0);";
static const char create_sources[] = "CREATE TABLE IF NOT EXISTS sources ("
                                     "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                     "path TEXT NOT NULL CHECK (LENGTH(path) > 0),"
                                     "size INTEGER,"
                                     "date_created TIMESTAMP,"
                                     "date_modified TIMESTAMP)";
static const char create_music[] = "CREATE TABLE IF NOT EXISTS music ("
                                   "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                   "file_format INTEGER,"
                                   "audio_bitrate INTEGER,"
                                   "audio_codec INTEGER,"
                                   "duration INTEGER,"
                                   "genre_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL,"
                                   "track_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL,"
                                   "artist_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL,"
                                   "album_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL,"
                                   "artist TEXT,"
                                   "album TEXT,"
                                   "track_number INTEGER)";
static const char create_apps[] = "CREATE TABLE IF NOT EXISTS application ("
                                  "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                  "title TEXT NOT NULL CHECK (LENGTH(title) > 0),"
                                  "app_type INTEGER)";
static const char create_virtual[] = "CREATE TABLE IF NOT EXISTS virtual_nodes ("
                                     "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                     "app_type INTEGER)";
static const char create_photos[] = "CREATE TABLE IF NOT EXISTS photos ("
                                    "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                    "date_created TIMESTAMP,"
                                    "month_created TEXT,"
                                    "file_format INTEGER,"
                                    "photo_codec INTEGER,"
                                    "width INTEGER,"
                                    "height INTEGER)";
static const char create_videos[] = "CREATE TABLE IF NOT EXISTS videos ("
                                    "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                    "file_format INTEGER,"
                                    "parental_level INTEGER,"
                                    "explanation TEXT,"
                                    "copyright TEXT,"
                                    "width INTEGER,"
                                    "height INTEGER,"
                                    "video_codec INTEGER,"
                                    "video_bitrate INTEGER,"
                                    "audio_codec INTEGER,"
                                    "audio_bitrate INTEGER,"
                                    "duration INTEGER)";
static const char create_savedata[] = "CREATE TABLE IF NOT EXISTS savedata ("
                                      "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
                                      "detail TEXT,"
                                      "dir_name TEXT,"
                                      "title TEXT,"
                                      "date_updated TIMESTAMP)";
static const char create_trigger_node[] = "CREATE TRIGGER IF NOT EXISTS trg_objnode_deletechilds BEFORE DELETE ON object_node "
        "FOR EACH ROW BEGIN "
        "DELETE FROM object_node WHERE object_id IN "
        "(SELECT child_id FROM adjacent_objects WHERE parent_id == OLD.object_id);"
        "END";
static const char create_trigger_adjins[] = "CREATE TRIGGER IF NOT EXISTS trg_adjacentobjects_ins AFTER INSERT ON adjacent_objects "
        "FOR EACH ROW BEGIN "
        "UPDATE object_node SET child_count = child_count + 1 WHERE object_id = NEW.parent_id;"
        "UPDATE object_node SET reference_count = reference_count + 1 WHERE object_id = NEW.child_id;"
        "END";
static const char create_trigger_adjdel[] = "CREATE TRIGGER IF NOT EXISTS trg_adjacentobjects_del AFTER DELETE ON adjacent_objects "
        "FOR EACH ROW BEGIN "
        "UPDATE object_node SET child_count = child_count - 1 WHERE object_id = OLD.parent_id;"
        "UPDATE object_node SET reference_count = reference_count - 1 WHERE object_id = OLD.child_id;"
        "DELETE FROM object_node WHERE object_id = OLD.parent_id AND child_count <= 0;"
        "DELETE FROM object_node WHERE object_id = OLD.child_id AND reference_count <= 0;"
        "END";
static const char *table_list[] = {
    create_adjacent, create_obj_node, create_sources,
    create_music, create_photos, create_videos, create_savedata, create_apps, create_virtual
};
static const char *trigger_list[] = {
    create_trigger_node, create_trigger_adjins, create_trigger_adjdel
};
static const int ohfi_array[] = { VITA_OHFI_MUSIC, VITA_OHFI_PHOTO, VITA_OHFI_VIDEO,
                                  VITA_OHFI_BACKUP, VITA_OHFI_VITAAPP, VITA_OHFI_PSPAPP,
                                  VITA_OHFI_PSPSAVE, VITA_OHFI_PSXAPP, VITA_OHFI_PSMAPP
                                };
SQLiteDB::SQLiteDB(QObject *obj_parent) :
    Database(obj_parent)
{
    m_uuid = QSettings().value("lastAccountId", "ffffffffffffffff").toString();
    thread = new QThread();
    moveToThread(thread);
    timer = new QTimer();
    thread->start();
    timer->setInterval(0);
    timer->setSingleShot(true);
    connect(timer, SIGNAL(timeout()), this, SLOT(process()));
    // fetch a configured database path if it exists
    QString db_path = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
    db_path = QSettings().value("databasePath", db_path).toString();
    QDir(QDir::root()).mkpath(db_path);
    db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(db_path + QDir::separator() + "qcma.sqlite");
}
SQLiteDB::~SQLiteDB()
{
    db.close();
    timer->stop();
    delete timer;
    thread->quit();
    thread->wait();
    delete thread;
}
void SQLiteDB::setUUID(const QString &uuid)
{
    m_uuid = uuid;
    QSettings().setValue("lastAccountId", uuid);
}
bool SQLiteDB::load()
{
    bool success = false;
    if(db.open()) {
        QSqlQuery query;
        query.exec("PRAGMA foreign_keys = ON");
        query.exec("PRAGMA synchronous = OFF");
        query.exec("PRAGMA user_version = 1");
        query.exec("PRAGMA journal_mode = MEMORY");
        query.exec("PRAGMA recursive_triggers = true");
        query.exec("PRAGMA vacuum_db.synchronous = OFF");
        qDebug() << "Database created/registered";
        success = !initialize();
    } else {
        const QSqlError error = db.lastError();
        qWarning() << "Error opening connection to the database:" << error.text();
    }
    return success;
}
bool SQLiteDB::rescan()
{
    if(mutex.tryLock(1000)) {
        if(m_uuid != "ffffffffffffffff") {
            timer->start();
            return true;
        } else {
            mutex.unlock();
            return false;
        }
    }
    return false;
}
void SQLiteDB::clear()
{
    db.close();
    //QSqlDatabase::removeDatabase("QSQLITE");
    QString db_path = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
    db_path = QSettings().value("databasePath", db_path).toString();
    QFile(db_path + QDir::separator() + "qcma.sqlite").remove();
    load();
}
bool SQLiteDB::initialize()
{
    QSqlQuery query;
    for(unsigned int i = 0; i < sizeof(table_list) / sizeof(const char *); i++) {
        if(!query.exec(table_list[i])) {
            qDebug() << query.lastError();
            return false;
        }
    }
    for(unsigned int i = 0; i < sizeof(trigger_list) / sizeof(const char *); i++) {
        if(!query.exec(trigger_list[i])) {
            qDebug() << query.lastError();
            return false;
        }
    }
    // force object_id to start at 256
    if(query.exec("INSERT INTO object_node (object_id, data_type, type) VALUES (255, 0, 0)")) {
        query.exec("DELETE FROM object_node WHERE object_id = 255");
    }
    return true;
}
QSqlError SQLiteDB::getLastError()
{
    return db.lastError();
}
int SQLiteDB::create()
{
    int total_objects = 0;
    db.transaction();
    if(!insertVirtualEntries()) {
        db.rollback();
        return -1;
    }
    for(int i = 0, max = sizeof(ohfi_array) / sizeof(int); i < max; i++) {
        QString base_path = getBasePath(ohfi_array[i]);
        int dir_count = recursiveScanRootDirectory(base_path, NULL, ohfi_array[i], ohfi_array[i]);
        if(dir_count < 0) {
            db.rollback();
            return -1;
        }
        //qDebug("Added %i objects for OHFI %#02X", dir_count, ohfi_array[i]);
        total_objects += dir_count;
    }
    db.commit();
    return total_objects;
}
QString SQLiteDB::getBasePath(int root_ohfi)
{
    QString base_path;
    QSettings settings;
    switch(root_ohfi) {
    case VITA_OHFI_MUSIC:
        base_path = settings.value("musicPath").toString();
        break;
    case VITA_OHFI_VIDEO:
        base_path = settings.value("videoPath").toString();
        break;
    case VITA_OHFI_PHOTO:
        base_path = settings.value("photoPath").toString();
        break;
    case VITA_OHFI_BACKUP:
        base_path = settings.value("appsPath").toString() + "/SYSTEM/" + m_uuid;
        break;
    case VITA_OHFI_VITAAPP:
        base_path = settings.value("appsPath").toString() + "/APP/" + m_uuid;
        break;
    case VITA_OHFI_PSPAPP:
        base_path = settings.value("appsPath").toString() + "/PGAME/" + m_uuid;
        break;
    case VITA_OHFI_PSPSAVE:
        base_path = settings.value("appsPath").toString() + "/PSAVEDATA/" + m_uuid;
        break;
    case VITA_OHFI_PSXAPP:
        base_path = settings.value("appsPath").toString() + "/PSGAME/" + m_uuid;
        break;
    case VITA_OHFI_PSMAPP:
        base_path = settings.value("appsPath").toString() + "/PSM/" + m_uuid;
        break;
    }
    return base_path;
}
int SQLiteDB::recursiveScanRootDirectory(const QString &base_path, const QString &rel_path, int parent_ohfi, int root_ohfi)
{
    int total_objects = 0;
    QDir dir(base_path + "/" + rel_path);
    dir.setSorting(QDir::Name);
    QFileInfoList qsl = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
    foreach(const QFileInfo &info, qsl) {
        if(!continueOperation()) {
            return -1;
        }
        //qDebug() << "Processing " << info.fileName();
        QString rel_name = rel_path.isNull() ? info.fileName() : rel_path + "/" + info.fileName();
        int ohfi = insertObjectEntryInternal(base_path, rel_name, parent_ohfi, root_ohfi);
        if(ohfi > 0) {
            // update progress dialog
            if(info.isDir()) {
                emit directoryAdded(base_path + "/" + rel_name);
                int inserted = recursiveScanRootDirectory(base_path, rel_name, ohfi, root_ohfi);
                if(inserted < 0) {
                    return -1;
                }
                total_objects += inserted;
                qint64 dirsize = getChildenTotalSize(ohfi);
                setObjectSize(ohfi, dirsize);
            } else if(info.isFile()) {
                emit fileAdded(info.fileName());
                total_objects++;
            }
        }
    }
    return total_objects;
}
int SQLiteDB::insertObjectEntry(const QString &path, const QString &name, int parent_ohfi)
{
    int ohfi;
    int type;
    if((type = getObjectType(parent_ohfi)) == 0) {
        return 0;
    }
    db.transaction();
    if((ohfi = insertObjectEntryInternal(path, name, parent_ohfi, type)) == 0) {
        db.rollback();
    }
    db.commit();
    return ohfi;
}
int SQLiteDB::insertObjectEntryInternal(const QString &path, const QString &name, int parent_ohfi, int root_ohfi)
{
    int ohfi = 0;
    QFileInfo info(path, name);
    if(info.isDir()) {
        ohfi = insertDefaultEntry(path, name, info.fileName(), parent_ohfi, Folder);
        switch(parent_ohfi) {
        case VITA_OHFI_VITAAPP:
        case VITA_OHFI_PSPAPP:
        case VITA_OHFI_PSXAPP:
        case VITA_OHFI_PSMAPP:
        case VITA_OHFI_BACKUP:
            insertApplicationEntry(name, ohfi, parent_ohfi);
        }
    } else {
        switch(root_ohfi) {
        case VITA_OHFI_MUSIC:
            ohfi = insertMusicEntry(path, name, parent_ohfi, File | Music);
            break;
        case VITA_OHFI_PHOTO:
            ohfi = insertPhotoEntry(path, name, parent_ohfi, File | Photo);
            break;
        case VITA_OHFI_VIDEO:
            ohfi = insertVideoEntry(path, name, parent_ohfi, File | Video);
            break;
        case VITA_OHFI_PSPSAVE:
            ohfi = insertSavedataEntry(path, name, parent_ohfi, File | SaveData);
            break;
        case VITA_OHFI_VITAAPP:
        case VITA_OHFI_PSPAPP:
        case VITA_OHFI_PSXAPP:
        case VITA_OHFI_PSMAPP:
        case VITA_OHFI_BACKUP:
            ohfi = insertDefaultEntry(path, name, info.fileName(), parent_ohfi, File | App);
        }
    }
    return ohfi;
}
int SQLiteDB::getPathId(const QString &path)
{
    QSqlQuery query(QString("SELECT object_id from sources WHERE path = %1").arg(path));
    if(query.next()) {
        return query.value(0).toInt();
    } else {
        qDebug() << query.lastError();
        return -1;
    }
}
QString SQLiteDB::getPathFromId(int ohfi)
{
    QSqlQuery query(QString("SELECT path FROM sources WHERE object_id = %1").arg(ohfi));
    if(query.next()) {
        return query.value(0).toString();
    } else {
        qDebug() << query.lastError();
        return QString();
    }
}
bool SQLiteDB::updateSize(int ohfi, quint64 size)
{
    QSqlQuery query(QString("UPDATE sources SET size = %1 WHERE object_id == %2").arg(size).arg(ohfi));
    return query.exec();
}
bool SQLiteDB::deleteEntry(int ohfi)
{
    QSqlQuery query(QString("DELETE FROM object_node WHERE object_id == %1").arg(ohfi));
    bool ret = query.exec();
    if(!ret) {
        qDebug() << query.lastError();
    }
    return ret;
}
bool SQLiteDB::updateAdjacencyList(int ohfi, int id_parent)
{
    QSqlQuery query;
    query.prepare("SELECT * FROM adjacent_objects WHERE parent_id == :parent_id AND child_id == :child_id");
    query.bindValue(0, id_parent);
    query.bindValue(1, ohfi);
    if(query.exec() && query.next()) {
        return true;
    }
    query.prepare("INSERT INTO adjacent_objects (parent_id, child_id)"
                  "VALUES (:parentid, :child_id)");
    query.bindValue(0, id_parent);
    query.bindValue(1, ohfi);
    bool ret = query.exec();
    if(!ret) {
        qDebug() << query.lastError();
    }
    return ret;
}
int SQLiteDB::insertDefaultEntry(const QString &path, const QString &name, const QString &title, int id_parent, int type)
{
    int ohfi = 0;
    if((ohfi = insertNodeEntry(title, VITA_DIR_TYPE_MASK_REGULAR, type)) == 0) {
        return 0;
    }
    if(id_parent >= OHFI_BASE_VALUE && !updateAdjacencyList(ohfi, id_parent)) {
        return 0;
    }
    if(!name.isNull() && !insertSourceEntry(ohfi, path, name)) {
        return 0;
    }
    return ohfi;
}
int SQLiteDB::insertNodeEntry(const QString &title, int type, int data_type)
{
    QSqlQuery query;
    query.prepare("INSERT INTO object_node (type, data_type, title) VALUES (:type, :data_type, :title)");
    query.bindValue(0, type);
    query.bindValue(1, data_type);
    query.bindValue(2, title);
    if(!query.exec() || !query.exec("SELECT last_insert_rowid()") || !query.next()) {
        qDebug() << query.lastError();
        return 0;
    }
    return query.value(0).toInt();
}
bool SQLiteDB::insertSourceEntry(uint object_id, const QString &path, const QString &name)
{
    QVariant size, date_created, date_modified;
    QFileInfo info(path, name);
    if(info.isFile()) {
        size = QVariant(info.size());
        date_created = QVariant(info.created().toUTC().toTime_t());
    } else {
        size = QVariant(QVariant::LongLong);
        date_created = QVariant(QVariant::UInt);
    }
    date_modified = QVariant(info.lastModified().toUTC().toTime_t());
    QSqlQuery query;
    query.prepare("REPLACE INTO sources (object_id, path, size, date_created, date_modified)"
                  "VALUES (:object_id, :path, :size, :date_created, :date_modified)");
    query.bindValue(0, object_id);
    query.bindValue(1, name);
    query.bindValue(2, size);
    query.bindValue(3, date_created);
    query.bindValue(4, date_modified);
    bool ret = query.exec();
    if(!ret) {
        qDebug() << query.lastError();
    }
    return ret;
}
uint SQLiteDB::insertMusicEntry(const QString &path, const QString &name, int id_parent, int type)
{
#ifndef FFMPEG_ENABLED
    Q_UNUSED(path);
    Q_UNUSED(name);
    Q_UNUSED(id_parent);
    Q_UNUSED(type);
    return 0;
#else
    bool ok;
    int ohfi;
    AVDecoder decoder;
    quint64 duration;
    const char *artist, *album, *albumartist, *genre, *track, *title;
    int file_format, audio_codec, audio_bitrate, genre_id, artist_id, track_id, album_id, track_number;
    int cma_file_type = checkFileType(name, VITA_OHFI_MUSIC);
    if(cma_file_type < 0) {
        //qDebug() << "Excluding from database:" << path;
        return 0;
    }
    if(!decoder.open(path + "/" + name)) {
        return 0;
    }
    album = decoder.getMetadataEntry("album");
    genre = decoder.getMetadataEntry("genre");
    artist = decoder.getMetadataEntry("artist");
    albumartist = decoder.getMetadataEntry("album_artist");
    track = decoder.getMetadataEntry("track");
    track_number = QString(track).split("/")[0].toInt(&ok);
    if(!ok) {
        // set track number to 1 by default
        track_number = 1;
    }
    if(decoder.loadCodec(AVDecoder::CODEC_AUDIO)) {
        audio_bitrate = decoder.getBitrate();
    } else {
        audio_bitrate = 0;
    }
    duration = decoder.getDuration();
    file_format = audio_list[cma_file_type].file_format;
    audio_codec = audio_list[cma_file_type].file_codec;
    QByteArray basename = QFileInfo(name).baseName().toUtf8();
    title = decoder.getMetadataEntry("title", basename.constData());
    if((ohfi = insertDefaultEntry(path, name, title, id_parent, type)) == 0) {
        return 0;
    }
    if(genre) {
        genre_id = insertNodeEntry(genre, VITA_DIR_TYPE_MASK_GENRES, type);
        if(!updateAdjacencyList(ohfi, genre_id)) {
            return 0;
        }
    } else {
        genre_id = 0;
    }
    if(artist) {
        track_id = insertNodeEntry(artist, VITA_DIR_TYPE_MASK_ARTISTS, type);
        if(!updateAdjacencyList(ohfi, track_id)) {
            return 0;
        }
    } else {
        track_id = 0;
    }
    if(albumartist) {
        artist_id = insertNodeEntry(albumartist, VITA_DIR_TYPE_MASK_REGULAR, type);
        if(!updateAdjacencyList(ohfi, artist_id)) {
            return 0;
        }
    } else {
        artist_id = 0;
    }
    if(album) {
        album_id = insertNodeEntry(album, VITA_DIR_TYPE_MASK_ALBUMS, type);
        if(track_id && !updateAdjacencyList(ohfi, album_id)) {
            return 0;
        }
        if(track_id && !updateAdjacencyList(album_id, track_id)) {
            return 0;
        }
        if(artist_id && !updateAdjacencyList(album_id, artist_id)) {
            return 0;
        }
    } else {
        album_id = 0;
    }
    QSqlQuery query;
    query.prepare("REPLACE INTO music"
                  "(object_id, file_format, audio_codec, audio_bitrate, duration, genre_id, artist_id, album_id, track_id, artist, album, track_number)"
                  "VALUES (:object_id, :file_format, :audio_codec, :audio_bitrate, :duration, :genre_id, :artist_id, :album_id, :track_id, :artist, :album, :track_number)");
    query.bindValue(0, ohfi);
    query.bindValue(1, file_format);
    query.bindValue(2, audio_codec);
    query.bindValue(3, audio_bitrate);
    query.bindValue(4, duration);
    query.bindValue(5, genre_id ? genre_id : QVariant(QVariant::Int));
    query.bindValue(6, artist_id ? artist_id : QVariant(QVariant::Int));
    query.bindValue(7, album_id ? album_id : QVariant(QVariant::Int));
    query.bindValue(8, track_id ? track_id : QVariant(QVariant::Int));
    query.bindValue(9, artist);
    query.bindValue(10, album);
    query.bindValue(11, track_number);
    if(!query.exec()) {
        return 0;
    }
    return ohfi;
#endif
}
uint SQLiteDB::insertVideoEntry(const QString &path, const QString &name, int id_parent, int type)
{
#ifndef FFMPEG_ENABLED
    Q_UNUSED(path);
    Q_UNUSED(name);
    Q_UNUSED(id_parent);
    Q_UNUSED(type);
    return 0;
#else
    int ohfi;
    AVDecoder decoder;
    quint64 duration;
    int file_format, parental_level, width, height, video_codec, video_bitrate, audio_codec, audio_bitrate;
    const char *explanation, *copyright, *title;
    if(!decoder.open(path + "/" + name) || !decoder.loadCodec(AVDecoder::CODEC_VIDEO)) {
        return 0;
    }
    int cma_file_type = checkFileType(name, VITA_OHFI_VIDEO);
    if(cma_file_type < 0) {
        //qDebug() << "Excluding from database:" << path;
        return 0;
    }
    parental_level = 0;
    explanation = decoder.getMetadataEntry("comments", "");
    copyright = decoder.getMetadataEntry("copyright", "");
    width = decoder.getWidth();
    height = decoder.getHeight();
    duration = decoder.getDuration();
    video_codec = CODEC_TYPE_AVC;
    video_bitrate = decoder.getBitrate();
    file_format = video_list[cma_file_type].file_format;
    if(decoder.loadCodec(AVDecoder::CODEC_AUDIO)) {
        audio_codec = CODEC_TYPE_AAC;
        audio_bitrate = decoder.getBitrate();
    } else {
        audio_codec = 0;
        audio_bitrate = 0;
    }
    QByteArray basename = QFileInfo(name).baseName().toUtf8();
    //title = decoder.getMetadataEntry("title", basename.constData());
    title = basename.constData();
    if((ohfi = insertDefaultEntry(path, name, title, id_parent, type)) == 0) {
        return 0;
    }
    QSqlQuery query;
    query.prepare("REPLACE INTO videos"
                  "(object_id, file_format, parental_level, explanation, copyright, width, height, video_codec, video_bitrate, audio_codec, audio_bitrate, duration)"
                  "VALUES (:object_id, :file_format, :parental_level, :explanation, :copyright, :width, :height, :video_codec, :video_bitrate, :audio_codec, :audio_bitrate, :duration)");
    query.bindValue(0, ohfi);
    query.bindValue(1, file_format);
    query.bindValue(2, parental_level);
    query.bindValue(3, explanation);
    query.bindValue(4, copyright);
    query.bindValue(5, width);
    query.bindValue(6, height);
    query.bindValue(7, video_codec);
    query.bindValue(8, video_bitrate);
    query.bindValue(9, audio_codec);
    query.bindValue(10, audio_bitrate);
    query.bindValue(11, duration);
    if(!query.exec()) {
        qDebug() << query.lastError().text();
        return 0;
    }
    return ohfi;
#endif
}
uint SQLiteDB::insertPhotoEntry(const QString &path, const QString &name, int id_parent, int type)
{
    int ohfi;
    //QImage img;
    uint date_created;
    int width, height, file_format, photo_codec;
    int cma_file_type = checkFileType(name, VITA_OHFI_PHOTO);
    if(cma_file_type < 0) {
        //qDebug() << "Excluding from database:" << path;
        return 0;
    }
    //if(!img.load(path + "/" + name)) {
    //    return 0;
    //}
    QDateTime date = QFileInfo(path + "/" + name).created();
    date_created = date.toUTC().toTime_t();
    QString month_created = date.toString("yyyy/MM");
    width = 0; //img.width();
    height = 0; //img.height();
    file_format = 0; //photo_list[cma_file_type].file_format;
    photo_codec = 0; //photo_list[cma_file_type].file_codec;
    QByteArray basename = QFileInfo(name).baseName().toUtf8();
    if((ohfi = insertDefaultEntry(path, name, basename, id_parent, type)) == 0) {
        return 0;
    }
    QSqlQuery query;
    query.prepare("REPLACE INTO photos"
                  "(object_id, date_created, file_format, photo_codec, width, height, month_created)"
                  "VALUES (:object_id, :date_created, :file_format, :photo_codec, :width, :height, :month_created)");
    query.bindValue(0, ohfi);
    query.bindValue(1, date_created);
    query.bindValue(2, file_format);
    query.bindValue(3, photo_codec);
    query.bindValue(4, width);
    query.bindValue(5, height);
    query.bindValue(6, month_created);
    if(!query.exec()) {
        return 0;
    }
    return ohfi;
}
uint SQLiteDB::insertSavedataEntry(const QString &path, const QString &name, int id_parent, int type)
{
    int ohfi;
    SfoReader reader;
    uint date_updated = 0;
    const char *title = NULL;
    const char *savedata_detail = NULL;
    const char *savedata_directory = NULL;
    QString file_name = QFileInfo(name).fileName();
    QByteArray utf8name = file_name.toUtf8();
    if(file_name.endsWith(".sfo", Qt::CaseInsensitive) && reader.load(path + "/" + name)) {
        title = reader.value("TITLE", utf8name.constData());
        savedata_detail = reader.value("SAVEDATA_DETAIL", "");
        savedata_directory = reader.value("SAVEDATA_DIRECTORY", utf8name.constData());
        date_updated = QFileInfo(path + "/" + name).lastModified().toUTC().toTime_t();
    }
    if((ohfi = insertDefaultEntry(path, name, title, id_parent, type)) == 0) {
        return 0;
    }
    if(!title) {
        return ohfi;
    }
    QSqlQuery query;
    query.prepare("REPLACE INTO savedata"
                  "(object_id, detail, dir_name, title, date_updated)"
                  "VALUES (:object_id, :detail, :dir_name, :title, :updated)");
    query.bindValue(0, id_parent);
    query.bindValue(1, savedata_detail);
    query.bindValue(2, savedata_directory);
    query.bindValue(3, title);
    query.bindValue(4, date_updated);
    if(!query.exec()) {
        return 0;
    }
    return ohfi;
}
bool SQLiteDB::insertApplicationEntry(const QString &name, int ohfi, int app_type)
{
    QString title_id = QFileInfo(name).fileName();
    QSqlQuery query;
    query.prepare("REPLACE INTO application"
                  "(object_id, title, app_type)"
                  "VALUES (:object_id, :title, :app_type)");
    query.bindValue(0, ohfi);
    query.bindValue(1, title_id);
    query.bindValue(2, app_type);
    if(!query.exec()) {
        return false;
    }
    return true;
}
int SQLiteDB::childObjectCount(int parent_ohfi)
{
    QSqlQuery query;
    query.prepare("SELECT count(child_id) FROM adjacent_objects WHERE parent_id = :object_id");
    query.bindValue(0, parent_ohfi);
    if(!query.exec() || !query.next()) {
        qDebug() << query.lastError();
        return -1;
    } else {
        return query.value(0).toInt();
    }
}
bool SQLiteDB::deleteEntry(int ohfi, int root_ohfi)
{
    Q_UNUSED(root_ohfi);
    qDebug("Deleting node: %i", ohfi);
    QSqlQuery query("DELETE FROM object_node WHERE object_id = :object_id");
    query.bindValue(0, ohfi);
    return query.exec();
}
void SQLiteDB::fillMetadata(const QSqlQuery &query, metadata_t &metadata)
{
    metadata.ohfi = query.value(0).toInt(); // ohfi
    metadata.ohfiParent = query.value(1).toInt(); // parent
    metadata.path = strdup(query.value(2).toByteArray().constData()); // path
    metadata.name = strdup(query.value(3).toByteArray().constData()); // name
    metadata.type = VITA_DIR_TYPE_MASK_REGULAR;
    metadata.dataType = (DataType)query.value(5).toInt(); // data_type
    metadata.size = query.value(6).toULongLong(); // size
    metadata.dateTimeCreated = query.value(7).toInt(); // date_created
    metadata.next_metadata = NULL;
    //TODO: fill the rest of the metadata
}
bool SQLiteDB::getObjectMetadata(int ohfi, metadata_t &metadata)
{
    QSqlQuery query(
        "SELECT "
        "t0.object_id as ohfi,"
        "t1.parent_id as parent,"
        "t2.path as path,"
        "t0.title as name,"
        "t0.type as type,"
        "t0.data_type as data_type,"
        "t2.size as size,"
        "t2.date_created as date_created "
        "FROM object_node t0 "
        "JOIN adjacent_objects t1 ON t1.child_id = t0.object_id "
        "JOIN sources t2 ON t2.object_id = t0.object_id "
        "WHERE t0.object_id = :object_id");
    query.bindValue(0, ohfi);
    if(query.exec() && query.next()) {
        fillMetadata(query, metadata);
        return true;
    } else {
        qDebug() << query.lastError();
        return false;
    }
}
int SQLiteDB::getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index, int max_number)
{
    if(metadata == NULL) {
        return childObjectCount(parent_ohfi);
    }
    if(parent_ohfi < OHFI_BASE_VALUE) {
        return getRootItems(parent_ohfi, metadata);
    }
    int count = 0;
    QString query_str(
        "SELECT "
        "t0.child_id as ohfi,"
        "t0.parent_id as parent,"
        "t2.path as path,"
        "t1.title as name,"
        "t1.type as type,"
        "t1.data_type as data_type,"
        "t2.size as size,"
        "t2.date_created as date_created "
        "FROM adjacent_objects t0 "
        "JOIN object_node t1 ON t0.child_id = t1.object_id "
        "JOIN sources t2 ON t0.child_id = t2.object_id "
        "WHERE t0.parent_id = :parent_id ");
    if(max_number > 0) {
        query_str += "LIMIT :limit OFFSET :offset";
    }
    QSqlQuery query(query_str);
    query.bindValue(0, parent_ohfi);
    if(max_number > 0) {
        query.bindValue(1, max_number);
        query.bindValue(2, index);
    }
    if(query.exec()) {
        metadata_t **last = &*metadata;
        while(query.next()) {
            metadata_t *meta = new metadata_t();
            fillMetadata(query, *meta);
            *last = meta;
            last = &meta->next_metadata;
            count++;
        }
    } else {
        qDebug() << query.lastError();
    }
    return count;
}
qint64 SQLiteDB::getObjectSize(int ohfi)
{
    if(ohfi < OHFI_BASE_VALUE) {
        return getChildenTotalSize(ohfi);
    }
    QSqlQuery query;
    query.prepare("SELECT size FROM sources WHERE object_id = :object_id");
    query.bindValue(0, ohfi);
    if(!query.exec() || !query.next()) {
        qDebug() << query.lastError();
        return -1;
    } else {
        return query.value(0).toInt();
    }
}
int SQLiteDB::getPathId(const char *name, int ohfi)
{
    //FIXME: use ohfi to filter by category
    Q_UNUSED(ohfi);
    QSqlQuery query;
    query.prepare("SELECT object_id FROM sources WHERE path = :path");
    query.bindValue(0, name);
    if(!query.exec() || !query.next()) {
        qDebug() << query.lastError();
        return 0;
    } else {
        return query.value(0).toInt();
    }
}
QString SQLiteDB::getAbsolutePath(int ohfi)
{
    int root_ohfi = ohfi < OHFI_BASE_VALUE ? ohfi : getRootId(ohfi);
    QString base_path = getBasePath(root_ohfi);
    QString rel_path = getRelativePath(ohfi);
    return rel_path.isNull() ? base_path : base_path + "/" + rel_path;
}
QString SQLiteDB::getRelativePath(int ohfi)
{
    QSqlQuery query;
    query.prepare("SELECT path FROM sources WHERE object_id = :object_id");
    query.bindValue(0, ohfi);
    if(!query.exec() || !query.next()) {
        qDebug() << query.lastError();
        return NULL;
    } else {
        return query.value(0).toString();
    }
}
bool SQLiteDB::updateObjectPath(int ohfi, const QString &name)
{
    int parent_ohfi = getParentId(ohfi);
    QString parent_path, file;
    if(name.isNull()) {
        QSqlQuery name_query("SELECT title FROM object_node WHERE object_id = :object_id");
        name_query.bindValue(0, ohfi);
        if(!name_query.exec() || !name_query.next()) {
            return false;
        }
        file = name_query.value(0).toString();
    } else {
        file = name;
    }
    if(parent_ohfi >= OHFI_BASE_VALUE) {
        parent_path = getRelativePath(parent_ohfi);
        if(parent_path.isNull()) {
            parent_path = file;
        } else {
            parent_path += "/" + file;
        }
    } else {
        parent_path = file;
    }
    QSqlQuery query("UPDATE sources SET path = :path WHERE object_id = :object_id");
    query.bindValue(0, parent_path);
    query.bindValue(1, ohfi);
    if(!query.exec()) {
        return false;
    }
    DataType type = (DataType)getObjectType(ohfi);
    if(type & Folder) {
        QSqlQuery child_query("SELECT child_id, data_type FROM adjacent_objects "
                              "JOIN object_node ON object_id = child_id"
                              "WHERE parent_id = :parent_id");
        child_query.bindValue(0, ohfi);
        if(query.exec()) {
            while(query.next()) {
                int child_ohfi = query.value(0).toInt();
                if(!updateObjectPath(child_ohfi, NULL)) {
                    return false;
                }
            }
        }
    }
    return true;
}
bool SQLiteDB::renameObject(int ohfi, const QString &name)
{
    QSqlQuery query("UPDATE object_node SET title = :title WHERE object_id = :object_id");
    query.bindValue(0, name);
    query.bindValue(1, ohfi);
    if(!query.exec()) {
        return false;
    }
    return updateObjectPath(ohfi, name);
}
void SQLiteDB::setObjectSize(int ohfi, qint64 size)
{
    QSqlQuery query;
    query.prepare("UPDATE sources SET size = :size WHERE object_id = :object_id");
    query.bindValue(0, size);
    query.bindValue(1, ohfi);
    if(!query.exec()) {
        qDebug() << query.lastError();
    }
}
qint64 SQLiteDB::getChildenTotalSize(int ohfi)
{
    QSqlQuery query;
    query.prepare("SELECT SUM(t0.size) FROM sources t0 "
                  "JOIN adjacent_objects t1 ON t0.object_id = t1.child_id "
                  "where t1.parent_id = :parent_id");
    query.bindValue(0, ohfi);
    if(!query.exec() || !query.next()) {
        qDebug() << query.lastError();
        return -1;
    } else {
        return query.value(0).toLongLong();
    }
}
int SQLiteDB::getRootId(int ohfi)
{
    QSqlQuery query;
    int root_ohfi = ohfi;
    query.prepare("SELECT parent_id FROM adjacent_objects WHERE child_id = :child_id");
    while(root_ohfi >= OHFI_BASE_VALUE) {
        query.bindValue(0, root_ohfi);
        if(!query.exec() || !query.next()) {
            qDebug() << query.lastError();
            root_ohfi = 0;
        } else {
            root_ohfi = query.value(0).toInt();
        }
    }
    return root_ohfi;
}
int SQLiteDB::getObjectType(int ohfi)
{
    QSqlQuery query;
    query.prepare("SELECT type FROM object_node WHERE object_id = :object_id");
    query.bindValue(0, ohfi);
    if(!query.exec() || !query.next()) {
        qDebug() << query.lastError();
        return 0;
    } else {
        return query.value(0).toInt();
    }
}
int SQLiteDB::getParentId(int ohfi)
{
    QSqlQuery query;
    query.prepare("SELECT parent_id FROM adjacent_objects WHERE child_id = :child_id");
    query.bindValue(0, ohfi);
    if(!query.exec() || !query.next()) {
        qDebug() << query.lastError();
        return 0;
    } else {
        return query.value(0).toInt();
    }
}
void SQLiteDB::freeMetadata(metadata_t *metadata)
{
    while(metadata) {
        metadata_t *current = metadata;
        metadata = metadata->next_metadata;
        delete current;
    }
}
int SQLiteDB::getRootItems(int root_ohfi, metadata_t **metadata)
{
    QSqlQuery query;
    int count = 0;
    switch(root_ohfi) {
    case VITA_OHFI_MUSIC:
        //query = QSqlQuery("SELECT * FROM music");
        break;
    case VITA_OHFI_PHOTO:
        //query = QSqlQuery("SELECT * FROM photos");
        break;
    case VITA_OHFI_VIDEO:
        //query = QSqlQuery("SELECT * FROM videos");
        break;
    case VITA_OHFI_PSPSAVE:
        //query = QSqlQuery("SELECT * FROM savedata");
        break;
    case VITA_OHFI_VITAAPP:
    case VITA_OHFI_PSPAPP:
    case VITA_OHFI_PSXAPP:
    case VITA_OHFI_PSMAPP:
    case VITA_OHFI_BACKUP:
        query = QSqlQuery("SELECT "
                          "t0.object_id as ohfi,"
                          ":app_type as parent,"
                          "t3.path as path,"
                          "t0.title as name,"
                          "t1.type as type,"
                          "t1.data_type as data_type,"
                          "t3.size as size,"
                          "t3.date_created as date_created "
                          "FROM application t0 "
                          "JOIN object_node t1 on t0.object_id = t1.object_id "
                          "JOIN sources t3 ON t3.object_id = t0.object_id "
                          "WHERE app_type = :app_type");
        query.bindValue(0, root_ohfi);
        if(query.exec()) {
            metadata_t **last = &*metadata;
            while(query.next()) {
                metadata_t *meta = new metadata_t();
                fillMetadata(query, *meta);
                *last = meta;
                last = &meta->next_metadata;
                count++;
            }
        }
        break;
    default:
        qFatal("Invalid root ohfi type");
    }
    return count;
}
bool SQLiteDB::insertVirtualEntry(int ohfi)
{
    QSqlQuery query;
    query.prepare("REPLACE INTO virtual_nodes (object_id)"
                  "VALUES (:object_id)");
    query.bindValue(0, ohfi);
    bool ret = query.exec();
    if(!ret) {
        qDebug() << query.lastError();
    }
    return ret;
}
bool SQLiteDB::insertVirtualEntries()
{
    int ohfi;
    if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Video)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Video)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Photo)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("Month", VITA_DIR_TYPE_MASK_MONTH, Photo)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Photo)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("Artists", VITA_DIR_TYPE_MASK_ARTISTS, Music)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("Albums", VITA_DIR_TYPE_MASK_ALBUMS, Music)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("Songs", VITA_DIR_TYPE_MASK_SONGS, Music)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("Genres", VITA_DIR_TYPE_MASK_GENRES, Music)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    if((ohfi = insertNodeEntry("Playlists", VITA_DIR_TYPE_MASK_PLAYLISTS, Music)) > 0) {
        insertVirtualEntry(ohfi);
    } else {
        return false;
    }
    return true;
}
bool SQLiteDB::getObjectList(int ohfi, metadata_t **metadata)
{
    Q_UNUSED(ohfi);
    Q_UNUSED(metadata);
    return false;
}