/*
 * Squawk messenger. 
 * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
 * 
 * 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 <QStandardPaths>
#include <QDir>
#include <QDebug>

#include "urlstorage.h"

Core::UrlStorage::UrlStorage(const QString& p_name):
    base(p_name),
    urlToInfo(base.addStorage<QString, UrlInfo>("urlToInfo")),
    pathToUrl(base.addStorage<QString, QString>("pathToUrl"))
{}

Core::UrlStorage::~UrlStorage() {
    close();
}

void Core::UrlStorage::open() {
    base.open();
}

void Core::UrlStorage::close() {
    base.close();
}

void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite) {
    LMDBAL::WriteTransaction txn = base.beginTransaction();
    writeInfo(key, info, txn, overwrite);
    txn.commit();
}

void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, const LMDBAL::WriteTransaction& txn, bool overwrite) {
    if (overwrite)
        urlToInfo->forceRecord(key, info, txn);
    else
        urlToInfo->addRecord(key, info, txn);
    
    if (info.hasPath())
        pathToUrl->forceRecord(info.getPath(), key, txn);
}

void Core::UrlStorage::addFile(const QString& url) {
    addToInfo(url, "", "", "");
}

void Core::UrlStorage::addFile(const QString& url, const QString& path) {
    addToInfo(url, "", "", "", path);
}

void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
    addToInfo(url, account, jid, id);
}

void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
    addToInfo(url, account, jid, id, path);
}

void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
    UrlInfo info (path, msgs);
    writeInfo(url, info, true);
}

QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id){
    return addToInfo(url, account, jid, id).getPath();
}

Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
    const QString& url,
    const QString& account,
    const QString& jid,
    const QString& id,
    const QString& path
) {
    UrlInfo info;
    LMDBAL::WriteTransaction txn = base.beginTransaction();
    
    try {
        urlToInfo->getRecord(url, info, txn);
    } catch (const LMDBAL::NotFound& e) {}
    
    bool pathChange = false;
    bool listChange = false;
    if (path != "-s") {
        if (info.getPath() != path) {
            info.setPath(path);
            pathChange = true;
        }
    }
    
    if (account.size() > 0 && jid.size() > 0 && id.size() > 0)
        listChange = info.addMessage(account, jid, id);

    if (pathChange || listChange) {
        writeInfo(url, info, txn, true);
        txn.commit();
    }
    
    return info;
}

std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path) {
    std::list<Shared::MessageInfo> list;
    LMDBAL::WriteTransaction txn = base.beginTransaction();
    UrlInfo info;
    
    try {
        urlToInfo->getRecord(url, info, txn);
        info.getMessages(list);
    } catch (const LMDBAL::NotFound& e) {}
    
    info.setPath(path);
    writeInfo(url, info, txn, true);
    txn.commit();
    
    return list;
}

std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url) {
    std::list<Shared::MessageInfo> list;
    LMDBAL::WriteTransaction txn = base.beginTransaction();
    UrlInfo info;
    
    urlToInfo->getRecord(url, info, txn);
    urlToInfo->removeRecord(url, txn);
    info.getMessages(list);

    if (info.hasPath())
        pathToUrl->removeRecord(info.getPath(), txn);

    txn.commit();
    
    return list;
}

std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path) {
    std::list<Shared::MessageInfo> list;
    LMDBAL::WriteTransaction txn = base.beginTransaction();
    
    QString url = pathToUrl->getRecord(path, txn);
    pathToUrl->removeRecord(path, txn);

    UrlInfo info = urlToInfo->getRecord(url, txn);
    info.getMessages(list);
    info.setPath(QString());
    urlToInfo->changeRecord(url, info, txn);

    txn.commit();
    
    return list;
}

QString Core::UrlStorage::getUrl(const QString& path) {
    return pathToUrl->getRecord(path);
}

std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url) {
    UrlInfo info = urlToInfo->getRecord(url);
    std::list<Shared::MessageInfo> container;
    info.getMessages(container);
    return std::make_pair(info.getPath(), container);
}

Core::UrlStorage::UrlInfo::UrlInfo():
    localPath(),
    messages() {}

Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
    localPath(path),
    messages() {}
 
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
    localPath(path),
    messages(msgs) {}
 
Core::UrlStorage::UrlInfo::~UrlInfo() {}
 
bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id) {
    for (const Shared::MessageInfo& info : messages) {
        if (info.account == acc && info.jid == jid && info.messageId == id) {
            return false;
        }
    }
    messages.emplace_back(acc, jid, id);
    return true;
}

void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const {
    data << localPath;
    std::list<Shared::MessageInfo>::size_type size = messages.size();
    data << quint32(size);
    for (const Shared::MessageInfo& info : messages) {
        data << info.account;
        data << info.jid;
        data << info.messageId;
    }
}

QDataStream & operator << (QDataStream& in, const Core::UrlStorage::UrlInfo& info) {
    info.serialize(in);
    return in;
}

QDataStream & operator >> (QDataStream& out, Core::UrlStorage::UrlInfo& info) {
    info.deserialize(out);
    return out;
}

void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data) {
    data >> localPath;
    quint32 size;
    data >> size;
    for (quint32 i = 0; i < size; ++i) {
        messages.emplace_back();
        Shared::MessageInfo& info = messages.back();
        data >> info.account;
        data >> info.jid;
        data >> info.messageId;
    }
}

void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const {
    for (const Shared::MessageInfo& info : messages)
        container.emplace_back(info);
}

QString Core::UrlStorage::UrlInfo::getPath() const {
    return localPath;
}

bool Core::UrlStorage::UrlInfo::hasPath() const {
    return localPath.size() > 0;
}

void Core::UrlStorage::UrlInfo::setPath(const QString& path) {
    localPath = path;
}