/**
 * 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.
 */

#pragma once

#include <QPointer>
#include <QTimer>

#include <U2Algorithm/CDSearchTaskFactory.h>

#include <U2Core/AnnotationData.h>
#include <U2Core/DNATranslation.h>
#include <U2Core/Task.h>

#include "HttpRequest.h"

namespace U2 {

class DNATranslation;
class AnnotationTableObject;

enum SendSelectionAlphabet {
    SendSelectionAlphabet_Any,
    SendSelectionAlphabet_Nucleo,
    SendSelectionAlphabet_Amino
};

enum FilterResult {
    FilterResultByAccession = 1,
    FilterResultByDef = 2,
    FilterResultById = 4
};

struct RemoteBLASTTaskSettings {
    QString dbChoosen;
    QString params;
    int retries;
    DNATranslation* aminoT;
    DNATranslation* complT;
    QByteArray query;
    bool isCircular;
    int filterResult;
    bool useEval;
    RemoteBLASTTaskSettings()
        : retries(0), aminoT(nullptr), complT(nullptr),
          isCircular(false), filterResult(0), useEval(false) {
    }
};

class RemoteBLASTToAnnotationsTask : public Task {
    Q_OBJECT
public:
    RemoteBLASTToAnnotationsTask(const RemoteBLASTTaskSettings& cfg, int qoffs, AnnotationTableObject* ao, const QString& url, const QString& group, const QString& annDescription);

    QList<Task*> onSubTaskFinished(Task* subTask);

private:
    int offsInGlobalSeq;
    QPointer<AnnotationTableObject> aobj;
    QString group;
    const QString annDescription;
    Task* queryTask;
    QString url;
};

class CreateAnnotationsFromHttpBlastResultTask;
class RemoteBlastHttpRequestTask;

class RemoteBLASTTask : public Task {
    Q_OBJECT
public:
    RemoteBLASTTask(const RemoteBLASTTaskSettings& cfg);
    QList<Task*> onSubTaskFinished(Task* subTask);

    QByteArray getOutputFile() const;

    const QList<SharedAnnotationData>& getResultedAnnotations() const;

private:
    RemoteBLASTTaskSettings cfg;
    RemoteBlastHttpRequestTask* httpBlastTask;
    CreateAnnotationsFromHttpBlastResultTask* createAnnotTask;
    QList<SharedAnnotationData> resultAnnotations;
};

class RemoteBlastHttpRequestTask : public Task {
    Q_OBJECT
public:
    RemoteBlastHttpRequestTask(const RemoteBLASTTaskSettings& cfg);
    void prepare();
    void run();

    void updateProgress();
    void resetProgress() {
        stateInfo.progress = 0;
        emit si_progressChanged();
    }

    QByteArray getOutputFile() const {
        return httpRequest.first()->getOutputFile();
    }

    struct Query {
        Query()
            : amino(false),
              complement(false),
              offs(0) {
        }
        QByteArray seq;
        bool amino;
        bool complement;
        int offs;  // translation frame offset
    };
    struct HttpBlastRequestTaskResult {
        HttpBlastRequestTaskResult(HttpRequest* r,
                                   Query& q)
            : request(r),
              query(q) {
        }
        HttpRequest* request;
        Query query;
    };

    const QList<HttpBlastRequestTaskResult>& getResults() const {
        return resultList;
    }
    bool isTimeOut() const {
        return timeout;
    }

private slots:
    void sl_timeout() {
        timeout = false;
    }

private:
    void prepareQueries();

    RemoteBLASTTaskSettings cfg;

    QList<Query> queries;
    QList<HttpRequest*> httpRequest;

    QList<HttpBlastRequestTaskResult> resultList;
    QTimer timer;
    bool timeout;
};

class CheckNCBISequenceCircularityTask : public Task {
    Q_OBJECT
public:
    CheckNCBISequenceCircularityTask(const QString& id);
    QList<Task*> onSubTaskFinished(Task* subTask);

    bool getResult() const {
        return result;
    }

private:
    QString seqId;
    Task* loadTask;
    QString tempUrl;
    bool result;
};

class CreateAnnotationsFromHttpBlastResultTask : public Task {
    Q_OBJECT
public:
    CreateAnnotationsFromHttpBlastResultTask(const RemoteBLASTTaskSettings& cfg,
                                             const QList<RemoteBlastHttpRequestTask::HttpBlastRequestTaskResult>& results);
    void prepare();
    QList<Task*> onSubTaskFinished(Task* subTask);
    const QList<SharedAnnotationData>& getResultedAnnotations() const {
        return resultAnnotations;
    }

private:
    void createAnnotations(const RemoteBlastHttpRequestTask::HttpBlastRequestTaskResult& result);
    QList<SharedAnnotationData> filterAnnotations(QList<SharedAnnotationData>& annotations);

    void mergeNeighbourResults();

    SharedAnnotationData merge(const SharedAnnotationData& start, const SharedAnnotationData& end);
    static bool annotationsReferToTheSameSeq(const SharedAnnotationData& start, const SharedAnnotationData& end);
    bool annotationsAreNeighbours(SharedAnnotationData& start, SharedAnnotationData& end);
    void orderNeighbors(SharedAnnotationData& start, SharedAnnotationData& end);
    void createCheckTask(const SharedAnnotationData& start, const SharedAnnotationData& end);

    RemoteBLASTTaskSettings cfg;
    int seqLen;
    QList<SharedAnnotationData> resultAnnotations;

    QList<RemoteBlastHttpRequestTask::HttpBlastRequestTaskResult> httpBlastResults;
    QVector<CheckNCBISequenceCircularityTask*> circCheckTasks;
    QVector<QPair<SharedAnnotationData, SharedAnnotationData>> mergeCandidates;
};

// used by CDSearchWorker
class RemoteCDSearch : public CDSearchResultListener {
public:
    RemoteCDSearch(const CDSearchSettings& settings);

    virtual Task* getTask() const {
        return task;
    }
    virtual QList<SharedAnnotationData> getCDSResults() const {
        return task->getResultedAnnotations();
    }

private:
    RemoteBLASTTask* task;
};

class RemoteCDSearchFactory : public CDSearchFactory {
    virtual CDSearchResultListener* createCDSearch(const CDSearchSettings& settings) const {
        return new RemoteCDSearch(settings);
    }
};

class Merge : public QObject {
    Q_OBJECT
public:
    static U2Qualifier equalQualifiers(const QString& qualName, const SharedAnnotationData& first, const SharedAnnotationData& second);

    static U2Qualifier percentQualifiers(const QString& qualName, const SharedAnnotationData& first, const SharedAnnotationData& second);

    static U2Qualifier hitFromQualifier(const SharedAnnotationData& first, const SharedAnnotationData& second);
    static U2Qualifier hitToQualifier(const SharedAnnotationData& first, const SharedAnnotationData& second);

    static U2Qualifier sumQualifiers(const QString& qualName, const SharedAnnotationData& first, const SharedAnnotationData& second);

    static U2Qualifier eValueQualifier(int seqLen, const SharedAnnotationData& first, const SharedAnnotationData& second);
};

}  // namespace U2
