/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2023 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 "SelectionUtils.h"

#include <U2Core/DocumentModel.h>
#include <U2Core/GObjectUtils.h>
#include <U2Core/SelectionModel.h>

#include "DocumentSelection.h"
#include "GObjectSelection.h"
#include "SelectionTypes.h"

namespace U2 {

// Warn: works only for pointer type
template<class T>
static void removeDuplicatesPointersFromList(QList<T*>& list) {
    QSet<const T*> tracked;
    for (int i = 0, n = list.size(); i < n; i++) {
        const T* val = list.at(i);
        if (tracked.contains(val)) {
            list[i] = nullptr;
        } else {
            tracked.insert(val);
        }
    }
    list.removeAll(nullptr);
}

QList<GObject*> SelectionUtils::findObjects(GObjectType t, const MultiGSelection* ms, UnloadedObjectFilter f) {
    QList<GObject*> res;
    foreach (const GSelection* s, ms->getSelections()) {
        QList<GObject*> tmp = findObjects(t, s, f);
        res += tmp;
    }

    removeDuplicatesPointersFromList(res);
    return res;
}

QList<GObject*> SelectionUtils::findObjectsKeepOrder(GObjectType t, const MultiGSelection* ms, UnloadedObjectFilter f) {
    QList<GObject*> res;
    foreach (const GSelection* s, ms->getSelections()) {
        QList<GObject*> tmp = findObjectsKeepOrder(t, s, f);
        res += tmp;
    }

    // now remove duplicates from list
    removeDuplicatesPointersFromList<GObject>(res);

    return res;
}

QList<GObject*> SelectionUtils::findObjects(GObjectType t, const GSelection* s, UnloadedObjectFilter f) {
    QList<GObject*> res = findObjectsKeepOrder(t, s, f);
    return res;
}

QList<GObject*> SelectionUtils::findObjectsKeepOrder(GObjectType t, const GSelection* s, UnloadedObjectFilter f) {
    QList<GObject*> res;
    GSelectionType stype = s->getSelectionType();
    if (stype == GSelectionTypes::DOCUMENTS) {
        auto ds = qobject_cast<const DocumentSelection*>(s);
        foreach (Document* d, ds->getSelectedDocuments()) {
            const QList<GObject*>& objs = d->getObjects();
            QList<GObject*> tmp = GObjectUtils::select(objs, t, f);
            res += tmp;
        }
    } else if (stype == GSelectionTypes::GOBJECTS) {
        auto os = qobject_cast<const GObjectSelection*>(s);
        const QList<GObject*>& objs = os->getSelectedObjects();
        res = GObjectUtils::select(objs, t, f);
    }
    return res;
}

QSet<Document*> SelectionUtils::findDocumentsWithObjects(GObjectType t, const MultiGSelection* ms, UnloadedObjectFilter f, bool deriveDocsFromObjectSelection) {
    QSet<Document*> res;
    foreach (const GSelection* s, ms->getSelections()) {
        res += findDocumentsWithObjects(t, s, f, deriveDocsFromObjectSelection);
    }
    return res;
}

QSet<Document*> SelectionUtils::findDocumentsWithObjects(GObjectType t, const GSelection* s, UnloadedObjectFilter f, bool deriveDocsFromObjectSelection) {
    QSet<Document*> res;
    GSelectionType st = s->getSelectionType();
    if (st == GSelectionTypes::DOCUMENTS) {
        DocumentFormatConstraints c;
        c.supportedObjectTypes += t;
        auto ds = qobject_cast<const DocumentSelection*>(s);
        const QList<Document*>& docs = ds->getSelectedDocuments();
        foreach (Document* d, docs) {
            if (!d->getObjects().isEmpty()) {
                QList<GObject*> objs = d->findGObjectByType(t, f);
                if (!objs.isEmpty()) {
                    res += d;
                }
            } else if (f == UOF_LoadedAndUnloaded && !d->isLoaded()) {  // document is unloaded
                DocumentFormat* df = d->getDocumentFormat();
                if (df->checkConstraints(c)) {
                    res += d;
                }
            }
        }
    } else if (st == GSelectionTypes::GOBJECTS && deriveDocsFromObjectSelection) {
        QList<GObject*> objects = findObjects(t, s, f);
        foreach (GObject* o, objects) {
            res += o->getDocument();
        }
    }
    return res;
}

bool SelectionUtils::isDocumentInSelection(const Document* doc, const MultiGSelection& ms, bool deriveDocsFromObjectSelection) {
    foreach (const GSelection* s, ms.getSelections()) {
        GSelectionType st = s->getSelectionType();
        if (st == GSelectionTypes::DOCUMENTS) {
            auto ds = qobject_cast<const DocumentSelection*>(s);
            const QList<Document*>& docs = ds->getSelectedDocuments();
            if (docs.contains((Document* const&)doc)) {  // TODO? why cast
                return true;
            }
        } else if (st == GSelectionTypes::GOBJECTS && deriveDocsFromObjectSelection) {
            auto os = qobject_cast<const GObjectSelection*>(s);
            const QList<GObject*>& objects = os->getSelectedObjects();
            for (GObject* o : qAsConst(objects)) {
                if (o->getDocument() == doc) {
                    return true;
                }
            }
        }
    }
    return false;
}

QList<Document*> SelectionUtils::getSelectedDocs(const MultiGSelection& ms) {
    foreach (const GSelection* s, ms.getSelections()) {
        GSelectionType st = s->getSelectionType();
        if (st == GSelectionTypes::DOCUMENTS) {
            auto ds = qobject_cast<const DocumentSelection*>(s);
            return ds->getSelectedDocuments();
        }
    }
    return QList<Document*>();
}

QList<GObject*> SelectionUtils::getSelectedObjects(const MultiGSelection& ms) {
    foreach (const GSelection* s, ms.getSelections()) {
        GSelectionType st = s->getSelectionType();
        if (st == GSelectionTypes::GOBJECTS) {
            auto os = qobject_cast<const GObjectSelection*>(s);
            return os->getSelectedObjects();
        }
    }
    return QList<GObject*>();
}

U2Region SelectionUtils::normalizeRegionBy3(U2Region reg, int seqLen, bool direct) {
    assert(reg.length > 0);
    if (seqLen < 3) {
        return reg;
    }
    int d = reg.length % 3;
    if (d == 1) {
        if (direct) {
            reg.length -= 1;
        } else {
            reg.startPos += 1;
            reg.length -= 1;
        }
    } else if (d == 2) {
        if (direct) {
            reg.length += (reg.length + 1 < seqLen) ? +1 : -2;
        } else {
            int prevStart = reg.startPos;
            reg.startPos += (reg.startPos > 0) ? -1 : +2;
            reg.length += prevStart - reg.startPos;
        }
    }
    return reg;
}

}  // namespace U2
