/**
 * 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 "AutoAnnotationUtils.h"

#include <U2Core/AnnotationTableObject.h>
#include <U2Core/AppContext.h>
#include <U2Core/AutoAnnotationsSupport.h>
#include <U2Core/DNASequenceObject.h>
#include <U2Core/U1AnnotationUtils.h>
#include <U2Core/U2SafePoints.h>

#include <U2Gui/MainWindow.h>
#include <U2Gui/ObjectViewModel.h>

#include <U2View/ADVAnnotationCreation.h>

#include "ADVSequenceObjectContext.h"
#include "AnnotatedDNAView.h"

namespace U2 {

const QString AutoAnnotationsADVAction::ACTION_NAME("AutoAnnotationUpdateAction");

#define AUTO_ANNOTATION_GROUP_NAME "AutoAnnotatationGroupName"

AutoAnnotationsADVAction::AutoAnnotationsADVAction(ADVSequenceWidget* v,
                                                   AutoAnnotationObject* obj)
    : ADVSequenceWidgetAction(ACTION_NAME, tr("Automatic annotations highlighting")), aaObj(obj), updatesCount(0) {
    seqWidget = v;
    addToBar = true;

    menu = new QMenu();
    setIcon(QIcon(":core/images/predefined_annotation_groups.png"));
    setMenu(menu);

    connect(aaObj, SIGNAL(si_updateStarted()), SLOT(sl_autoAnnotationUpdateStarted()));
    connect(aaObj, SIGNAL(si_updateFinished()), SLOT(sl_autoAnnotationUpdateFinished()));

    selectAllAction = new QAction(tr("Select all"), this);
    connect(selectAllAction, SIGNAL(triggered()), SLOT(sl_onSelectAll()));

    deselectAllAction = new QAction(tr("Deselect all"), this);
    connect(deselectAllAction, SIGNAL(triggered()), SLOT(sl_onDeselectAll()));

    updateMenu();

    aaObj->updateAll();
}

#define MAX_SEQ_SIZE_TO_ENABLE_AUTO_ANNOTATIONS 10000

void AutoAnnotationsADVAction::updateMenu() {
    AutoAnnotationConstraints constraints;
    if (seqWidget->getSequenceContexts().count() > 0) {
        constraints.alphabet = seqWidget->getSequenceContexts().first()->getAlphabet();
    }

    // Auto annotations should not be enabled by default for very large sequences
    // or when there are many small sequences. This flag controls such behavior.
    bool largeSequence = false;

    if (seqWidget->getSequenceObjects().count() > 0) {
        constraints.hints = seqWidget->getSequenceObjects().first()->getGHints();

        int totalLen = 0;

        AnnotatedDNAView* view = seqWidget->getAnnotatedDNAView();
        if (view != nullptr) {
            const QList<ADVSequenceObjectContext*>& ctxList = view->getSequenceContexts();
            foreach (const ADVSequenceObjectContext* ctx, ctxList) {
                totalLen += ctx->getSequenceLength();
            }
        }
        largeSequence = totalLen > MAX_SEQ_SIZE_TO_ENABLE_AUTO_ANNOTATIONS;
    }

    QList<AutoAnnotationsUpdater*> updaters = AppContext::getAutoAnnotationsSupport()->getAutoAnnotationUpdaters();
    if (updaters.count() == 0) {
        setEnabled(false);
        return;
    }
    foreach (AutoAnnotationsUpdater* updater, updaters) {
        QAction* toggleAction = new QAction(updater->getName(), this);
        toggleAction->setObjectName(updater->getName());
        toggleAction->setProperty(AUTO_ANNOTATION_GROUP_NAME, updater->getGroupName());
        bool enabled = updater->checkConstraints(constraints);
        toggleAction->setEnabled(enabled);
        toggleAction->setCheckable(true);
        bool checked = updater->isEnabledByDefault() && !largeSequence;
        toggleAction->setChecked(checked);
        aaObj->setGroupEnabled(updater->getGroupName(), checked);
        connect(toggleAction, SIGNAL(toggled(bool)), SLOT(sl_toggle(bool)));
        menu->addAction(toggleAction);
    }

    menu->update();
}

void AutoAnnotationsADVAction::sl_toggle(bool toggled) {
    auto action = qobject_cast<QAction*>(sender());
    if (action == nullptr) {
        return;
    }
    AutoAnnotationsUpdater* updater = AppContext::getAutoAnnotationsSupport()->findUpdaterByName(action->text());
    if (updater != nullptr) {
        QString groupName = updater->getGroupName();
        aaObj->setGroupEnabled(groupName, toggled);
        aaObj->updateGroup(groupName);
        updater->setEnabledByDefault(toggled);
    }
}

void AutoAnnotationsADVAction::sl_onSelectAll() {
    QList<QAction*> actions = getToggleActions();
    foreach (QAction* action, actions) {
        if (!action->isChecked()) {
            action->trigger();
        }
    }
}

void AutoAnnotationsADVAction::sl_onDeselectAll() {
    QList<QAction*> actions = getToggleActions();
    foreach (QAction* action, actions) {
        if (action->isChecked()) {
            action->trigger();
        }
    }
}

AutoAnnotationsADVAction::~AutoAnnotationsADVAction() {
    menu->clear();
    delete menu;
    menu = nullptr;
}

QList<QAction*> AutoAnnotationsADVAction::getToggleActions() {
    return menu->actions();
}

QAction* AutoAnnotationsADVAction::findToggleAction(const QString& groupName) {
    QList<QAction*> toggleActions = menu->actions();
    foreach (QAction* tAction, toggleActions) {
        if (tAction->property(AUTO_ANNOTATION_GROUP_NAME) == groupName) {
            return tAction;
        }
    }
    return nullptr;
}

void AutoAnnotationsADVAction::addUpdaterToMenu(AutoAnnotationsUpdater* updater) {
    AutoAnnotationConstraints constraints;
    if (seqWidget->getSequenceContexts().count() > 0) {
        constraints.alphabet = seqWidget->getSequenceContexts().first()->getAlphabet();
    }
    if (seqWidget->getSequenceObjects().count() > 0) {
        constraints.hints = seqWidget->getSequenceObjects().first()->getGHints();
    }

    QAction* toggleAction = new QAction(updater->getName(), this);
    toggleAction->setProperty(AUTO_ANNOTATION_GROUP_NAME, updater->getGroupName());
    bool enabled = updater->checkConstraints(constraints);
    toggleAction->setEnabled(enabled);
    toggleAction->setCheckable(true);
    bool checked = updater->isEnabledByDefault();
    toggleAction->setChecked(checked);
    aaObj->setGroupEnabled(updater->getGroupName(), checked);
    connect(toggleAction, SIGNAL(toggled(bool)), SLOT(sl_toggle(bool)));
    menu->addAction(toggleAction);

    menu->update();
}

void AutoAnnotationsADVAction::sl_autoAnnotationUpdateStarted() {
    setEnabled(false);
    updatesCount++;
}

void AutoAnnotationsADVAction::sl_autoAnnotationUpdateFinished() {
    updatesCount--;
    if (updatesCount == 0) {
        setEnabled(true);
    }
}

//////////////////////////////////////////////////////////////////////////

QAction* AutoAnnotationUtils::findAutoAnnotationsToggleAction(ADVSequenceObjectContext* ctx, const QString& groupName) {
    foreach (ADVSequenceWidget* w, ctx->getSequenceWidgets()) {
        ADVSequenceWidgetAction* advAction = w->getADVSequenceWidgetAction(AutoAnnotationsADVAction::ACTION_NAME);
        if (advAction == nullptr) {
            continue;
        }
        auto aaAction = qobject_cast<AutoAnnotationsADVAction*>(advAction);
        assert(aaAction != nullptr);
        QList<QAction*> toggleActions = aaAction->getToggleActions();
        for (QAction* tAction : qAsConst(toggleActions)) {
            if (tAction->property(AUTO_ANNOTATION_GROUP_NAME) == groupName) {
                return tAction;
            }
        }
    }

    return nullptr;
}

void AutoAnnotationUtils::triggerAutoAnnotationsUpdate(ADVSequenceObjectContext* ctx, const QString& aaGroupName) {
    AutoAnnotationsADVAction* aaAction = findAutoAnnotationADVAction(ctx);

    if (aaAction != nullptr && !aaAction->isEnabled()) {
        return;
    }

    assert(aaAction != nullptr);
    if (aaAction) {
        QAction* updateAction = aaAction->findToggleAction(aaGroupName);
        assert(updateAction != nullptr);

        if (!updateAction) {
            return;
        }

        if (!updateAction->isChecked()) {
            updateAction->trigger();
        } else {
            AutoAnnotationsUpdater* updater = AppContext::getAutoAnnotationsSupport()->findUpdaterByGroupName(aaGroupName);
            if (updater != nullptr) {
                aaAction->getAAObj()->updateGroup(aaGroupName);
            }
        }
    }
}

AutoAnnotationsADVAction* AutoAnnotationUtils::findAutoAnnotationADVAction(ADVSequenceObjectContext* ctx) {
    foreach (ADVSequenceWidget* w, ctx->getSequenceWidgets()) {
        ADVSequenceWidgetAction* advAction = w->getADVSequenceWidgetAction(AutoAnnotationsADVAction::ACTION_NAME);
        if (advAction == nullptr) {
            continue;
        } else {
            return qobject_cast<AutoAnnotationsADVAction*>(advAction);
        }
    }

    return nullptr;
}

QList<QAction*> AutoAnnotationUtils::getAutoAnnotationToggleActions(ADVSequenceObjectContext* ctx) {
    QList<QAction*> res;

    foreach (ADVSequenceWidget* w, ctx->getSequenceWidgets()) {
        ADVSequenceWidgetAction* advAction = w->getADVSequenceWidgetAction(AutoAnnotationsADVAction::ACTION_NAME);
        if (advAction == nullptr) {
            continue;
        }
        auto aaAction = qobject_cast<AutoAnnotationsADVAction*>(advAction);
        assert(aaAction != nullptr);
        res = aaAction->getToggleActions();

        int selectedCount = 0;
        for (QAction* a : qAsConst(res)) {
            if (a->isChecked()) {
                selectedCount += 1;
            }
        }

        if (selectedCount == res.size()) {
            res.append(aaAction->getDeselectAllAction());
        } else {
            res.append(aaAction->getSelectAllAction());
        }
    }

    return res;
}

//////////////////////////////////////////////////////////////////////////

ExportAutoAnnotationsGroupTask::ExportAutoAnnotationsGroupTask(AnnotationGroup* ag,
                                                               GObjectReference& ref,
                                                               ADVSequenceObjectContext* ctx,
                                                               const QString& annDescription)
    : Task("ExportAutoAnnotationsGroupTask", TaskFlags_NR_FOSCOE),
      aGroup(ag),
      aRef(ref),
      seqCtx(ctx),
      createTask(nullptr),
      annDescription(annDescription) {
    SAFE_POINT_EXT(ag != nullptr, stateInfo.setError("Invalid annotation group provided"), );
}

void ExportAutoAnnotationsGroupTask::prepare() {
    QList<Annotation*> annotationList;
    aGroup->findAllAnnotationsInGroupSubTree(annotationList);

    QList<SharedAnnotationData> newAnnotationDataList;
    for (const Annotation* annotation : qAsConst(annotationList)) {
        SharedAnnotationData data(new AnnotationData(*(annotation->getData())));
        U1AnnotationUtils::addDescriptionQualifier(data, annDescription);
        newAnnotationDataList.append(data);
    }

    CHECK(!newAnnotationDataList.isEmpty(), );

    bool selectNewAnnotations = newAnnotationDataList.size() < 100;
    createTask = new ADVCreateAnnotationsTask(seqCtx->getAnnotatedDNAView(), aRef, aGroup->getName(), newAnnotationDataList, selectNewAnnotations);
    addSubTask(createTask);
}

QList<Task*> ExportAutoAnnotationsGroupTask::onSubTaskFinished(Task* subTask) {
    QList<Task*> res;

    if (!subTask->isFinished() || subTask->hasError() || subTask->isCanceled()) {
        return res;
    }

    if (subTask == createTask) {
        QAction* toggleAction = AutoAnnotationUtils::findAutoAnnotationsToggleAction(seqCtx, aGroup->getName());
        if (toggleAction != nullptr && toggleAction->isChecked()) {
            toggleAction->trigger();
        }
    }

    return res;
}

}  // namespace U2
