/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2024 UniPro <ugene@unipro.ru>
 * http://ugene.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include "RunFileSystem.h"

#include <QFileInfo>

#include <U2Core/U2OpStatusUtils.h>
#include <U2Core/U2SafePoints.h>

#include <U2Lang/ActorModel.h>
#include <U2Lang/BaseAttributes.h>
#include <U2Lang/SchemaConfig.h>

namespace U2 {

/************************************************************************/
/* FSItem */
/************************************************************************/
FSItem::FSItem()
    : parentItem(nullptr), dir(false) {
}

FSItem::FSItem(const QString& name, bool isDirectory, FSItem* parent)
    : parentItem(parent), itemName(name), dir(isDirectory) {
}

FSItem::~FSItem() {
    qDeleteAll(items);
}

bool FSItem::isDir() const {
    return dir;
}

QString FSItem::name() const {
    return itemName;
}

QVector<FSItem*> FSItem::children() const {
    SAFE_POINT(isDir(), "Files can not have children", QVector<FSItem*>());
    return items;
}

FSItem* FSItem::parent() const {
    return parentItem;
}

FSItem* FSItem::child(int pos) const {
    SAFE_POINT(isDir(), "Files can not have children", nullptr);
    if (pos >= items.size() || pos < 0) {
        return nullptr;
    }
    return items[pos];
}

int FSItem::row() const {
    CHECK(parentItem != nullptr, 0);
    return parentItem->items.indexOf(const_cast<FSItem*>(this));
}

bool FSItem::contains(const QString& name) const {
    SAFE_POINT(isDir(), "Files can not have children", false);
    return (getItem(children(), name) != nullptr);
}

void FSItem::rename(const QString& newName) {
    itemName = newName;
}

void FSItem::addChild(FSItem* item) {
    SAFE_POINT(isDir(), "Files can not have children", );
    SAFE_POINT(!contains(item->name()), "This item already exists: " + item->name(), );

    int pos = posToInsert(item);
    items.insert(pos, item);
    item->parentItem = this;
}

int FSItem::posToInsert(FSItem* item) const {
    int pos = 0;
    while (pos < items.size()) {
        FSItem* i = items[pos];
        if (i->isDir() && !item->isDir()) {
            pos++;
            continue;
        } else if (!i->isDir() && item->isDir()) {
            break;
        }
        if (item->name() < i->name()) {
            break;
        }
        pos++;
    }
    return pos;
}

void FSItem::removeChild(const QString& name, U2OpStatus& os) {
    SAFE_POINT(isDir(), "Files can not have children", );
    FSItem* item = FSItem::getItem(children(), name);
    SAFE_POINT(item != nullptr, "No child with the name " + name, );

    if (item->isDir() && !item->children().isEmpty()) {
        os.setError(item->name() + " is not empty");
        return;
    }
    items.remove(items.indexOf(item));
}

FSItem* FSItem::getItem(const QVector<FSItem*>& items, const QString& name) {
    foreach (FSItem* item, items) {
        if (item->name() == name) {
            return item;
        }
    }
    return nullptr;
}

void FSItem::noChildren() {
    foreach (FSItem* item, items) {
        item->parentItem = nullptr;
    }
    items.clear();
}

/************************************************************************/
/* RunFileSystem */
/************************************************************************/
RunFileSystem::RunFileSystem(QObject* parent)
    : QObject(parent) {
    root = new FSItem(tr("Workflow-run output"), true);
}

RunFileSystem::~RunFileSystem() {
    delete root;
}

bool RunFileSystem::contains(const QString& pathStr) {
    bool found = false;
    find(getPath(pathStr), found);
    return found;
}

bool RunFileSystem::canAdd(const QString& pathStr, bool isDirectory) {
    CHECK(RFSUtils::isCorrectUrl(pathStr), false);
    QStringList parentPath;
    QString name;
    CHECK(getPath(pathStr, parentPath, name), false);

    FSItem* current = root;
    foreach (const QString& dirName, parentPath) {
        CHECK(current->isDir(), false);
        FSItem* item = FSItem::getItem(current->children(), dirName);
        CHECK(item != nullptr, true);
        current = item;
    }

    CHECK(current->contains(name), true);
    FSItem* target = FSItem::getItem(current->children(), name);
    return (target->isDir() == isDirectory);
}

void RunFileSystem::addItem(const QString& pathStr, bool isDirectory, U2OpStatus& os) {
    QStringList path = getPath(pathStr);
    FSItem* dir = createPath(path.mid(0, path.size() - 1), os);
    CHECK_OP(os, );

    if (dir->contains(path.last())) {
        os.setError(pathStr + " is already used");
        return;
    }

    dir->addChild(new FSItem(path.last(), isDirectory));
}

void RunFileSystem::removeItem(const QString& pathStr, U2OpStatus& os) {
    QStringList path = getPath(pathStr);
    QStringList parentPath = path.mid(0, path.size() - 1);
    QString name = path.last();

    bool found = true;
    FSItem* parent = find(parentPath, found);
    if (!found) {
        os.setError(parentPath.join("/") + " does not exist");
        return;
    }

    if (!parent->contains(name)) {
        os.setError(pathStr + " does not exist");
        return;
    }

    parent->removeChild(name, os);
}

void RunFileSystem::reset() {
    qDeleteAll(root->items);
    root->items.clear();
}

FSItem* RunFileSystem::getRoot() {
    return root;
}

FSItem* RunFileSystem::find(const QStringList& path, bool& found) {
    found = true;
    FSItem* current = root;
    foreach (const QString& name, path) {
        if (!current->isDir()) {
            found = false;
            break;
        }

        FSItem* item = FSItem::getItem(current->children(), name);
        if (item == nullptr) {
            found = false;
            break;
        }
        current = item;
    }
    return current;
}

FSItem* RunFileSystem::createPath(const QStringList& path, U2OpStatus& os) {
    FSItem* current = root;
    QString pathStr = root->name();
    foreach (const QString& dirName, path) {
        FSItem* item = FSItem::getItem(current->children(), dirName);
        if (item == nullptr) {
            item = new FSItem(dirName, true);
            current->addChild(item);
        }
        pathStr += "/" + dirName;
        if (!item->isDir()) {
            os.setError(pathStr + " is a file, not a folder");
            return root;
        }
        current = item;
    }
    return current;
}

QStringList RunFileSystem::getPath(const QString& pathStr) const {
    QString correctPath = pathStr;
    correctPath.replace("\\", "/");
    return correctPath.split("/", QString::SkipEmptyParts);
}

bool RunFileSystem::getPath(const QString& pathStr, QStringList& parentPath, QString& name) const {
    QStringList path = getPath(pathStr);
    CHECK(!path.isEmpty(), false);
    parentPath = path.mid(0, path.size() - 1);
    name = path.last();
    return true;
}

void RunFileSystem::test(const QString& file) {
    QFile f(file);
    f.open(QIODevice::WriteOnly);
    f.write(test(*root).join("\n").toLatin1());
    f.write("\n===============================\n");
    f.close();
    return;
}

QStringList RunFileSystem::test(FSItem& root) {
    QStringList result;
    for (QVector<FSItem*>::Iterator i = root.items.begin(); i != root.items.end(); i++) {
        if ((*i)->isDir()) {
            QStringList mid = test(*(*i));
            if (mid.isEmpty()) {
                result << root.name() + "/" + (*i)->name() + "/";
            } else {
                foreach (const QString& m, mid) {
                    result << root.name() + "/" + m;
                }
            }
        } else {
            result << root.name() + "/" + (*i)->name();
        }
    }
    return result;
}

/************************************************************************/
/* RFSUtils */
/************************************************************************/
void RFSUtils::initRFS(RunFileSystem& rfs, const QList<Workflow::Actor*>& actors, SchemaConfig* cfg) {
    rfs.reset();
    {  // add report dir
        U2OpStatus2Log os;
        rfs.addItem("report", true, os);
    }
    for (Workflow::Actor* actor : qAsConst(actors)) {
        foreach (Attribute* attr, actor->getParameters()) {
            bool dir = false;
            if (!isOutUrlAttribute(attr, actor, dir)) {
                continue;
            }

            AttributeInfo attrId = AttributeInfo(actor->getId(), attr->getId());
            QString url = cfg->getAttributeValue(attrId).toString();
            if (!isCorrectUrl(url)) {
                continue;
            }

            if (rfs.contains(url)) {
                continue;
            }

            U2OpStatus2Log os;
            rfs.addItem(url, dir, os);

            if (os.hasError()) {
                cfg->setAttributeValue(attrId, "");
            }
        }
    }
}

bool RFSUtils::isOutUrlAttribute(Attribute* attr, Workflow::Actor* actor, bool& dir) {
    ConfigurationEditor* editor = actor->getEditor();
    CHECK(editor != nullptr, false);

    PropertyDelegate* delegate = editor->getDelegate(attr->getId());
    CHECK(delegate != nullptr, false);

    dir = delegate->type() == PropertyDelegate::OUTPUT_DIR;
    if (dir) {
        return true;
    }
    if (delegate->type() == PropertyDelegate::OUTPUT_FILE) {
        return true;
    }
    return false;
}

bool RFSUtils::isCorrectUrl(const QString& url) {
    if (url.isEmpty()) {
        return false;
    }
    QFileInfo info(url);
    if (info.isAbsolute()) {
        return false;
    }
    return true;
}

}  // namespace U2
