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

#include <QDomElement>

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

namespace U2 {

// musthave attributes
#define DOC_ATTR "document"
#define SEQUENCE_ATTR "sequence"
#define STRAND_ATTR "strand"
#define PATTERN_ATTR "pattern"
#define RANGE_ATTR "range"
#define MAX_ERR_ATTR "max_error"
#define ALGORITHM_ATTR "algorithm"
#define EXPECTED_ATTR "expected"
// flags
#define SOURCE_ATTR "source"
#define AMBIG_ATTR "ambig"
#define MAXLEN_ATTR "max_len"

#define CIRCULAR_LABEL "circular"

#define TASK_NOT_CANCELLED "checkTaskNotCancelled"
#define SKIP_RESULTS_CHECK "skipResultsCheck"

U2Region stringToRegion(const QString& regionStr) {
    int region[2];
    QStringList regStrList = regionStr.split("..", QString::SkipEmptyParts);
    if (regStrList.size() != 2) {
        return U2Region();
    }
    bool ok;
    for (int i = 0; i < 2; i++) {
        region[i] = regStrList[i].toInt(&ok);
        if (!ok) {
            return U2Region();
        }
    }
    return U2Region(region[0], region[1] - region[0]);
}

void GTest_FindAlgorithmTest::init(XMLTestFormat*, const QDomElement& el) {
    QString buf = el.attribute(STRAND_ATTR);
    if (buf.isEmpty()) {
        stateInfo.setError(GTest::tr("value not set %1").arg(STRAND_ATTR));
        return;
    }
    if (buf == "direct") {
        settings.strand = FindAlgorithmStrand_Direct;
    } else if (buf == "both") {
        settings.strand = FindAlgorithmStrand_Both;
    } else if (buf == "complement") {
        settings.strand = FindAlgorithmStrand_Complement;
    } else {
        stateInfo.setError(GTest::tr("value for %1 is incorrect").arg(STRAND_ATTR));
        return;
    }

    buf = el.attribute(PATTERN_ATTR);
    if (buf.isEmpty()) {
        stateInfo.setError(GTest::tr("value not set %1").arg(PATTERN_ATTR));
        return;
    }
    settings.pattern = buf.toLatin1();

    buf = el.attribute(SOURCE_ATTR);
    if (buf == "translation") {
        translatetoAmino = true;
    } else {
        translatetoAmino = false;
    }

    buf = el.attribute(DOC_ATTR);
    if (buf.isEmpty()) {
        stateInfo.setError(GTest::tr("value not set %1").arg(DOC_ATTR));
        return;
    }
    docName = buf;

    buf = el.attribute(SEQUENCE_ATTR);
    if (buf.isEmpty()) {
        stateInfo.setError(GTest::tr("value not set %1").arg(SEQUENCE_ATTR));
        return;
    }
    sequenceName = buf;

    buf = el.attribute(RANGE_ATTR);
    QStringList regionStr = buf.split("..", QString::SkipEmptyParts);
    bool ok;
    int region[2];
    if (regionStr.size() != 2) {
        stateInfo.setError(GTest::tr("value incorrect for %1").arg(RANGE_ATTR));
        return;
    }
    for (int i = 0; i < 2; i++) {
        region[i] = regionStr[i].toInt(&ok);
        if (!ok) {
            stateInfo.setError(GTest::tr("value incorrect for %1").arg(RANGE_ATTR));
            return;
        }
    }

    settings.searchRegion = U2Region(region[0], region[1] - region[0]);

    buf = el.attribute(MAX_ERR_ATTR);
    settings.maxErr = buf.toInt(&ok);
    if (!ok) {
        stateInfo.setError(GTest::tr("value incorrect for %1").arg(MAX_ERR_ATTR));
        return;
    }
    if (settings.maxErr >= settings.pattern.length()) {
        stateInfo.setError(GTest::tr("%1 attribute value greater or equal pattern length").arg(MAX_ERR_ATTR));
        return;
    }

    buf = el.attribute(ALGORITHM_ATTR);
    if (buf.isEmpty()) {
        stateInfo.setError(GTest::tr("value not set %1").arg(ALGORITHM_ATTR));
        return;
    }
    if (buf == "exact") {
        settings.patternSettings = FindAlgorithmPatternSettings_Exact;
    } else if (buf == "subst") {
        settings.patternSettings = FindAlgorithmPatternSettings_Subst;
    } else if (buf == "insdel") {
        settings.patternSettings = FindAlgorithmPatternSettings_InsDel;
    } else if (buf == "regexp") {
        settings.patternSettings = FindAlgorithmPatternSettings_RegExp;
        settings.maxRegExpResultLength = el.attribute(MAXLEN_ATTR).toInt(&ok);
        if (!ok) {
            stateInfo.setError(GTest::tr("value incorrect for %1").arg(MAXLEN_ATTR));
            return;
        }
    } else {
        stateInfo.setError(GTest::tr("value for %1 incorrect").arg(ALGORITHM_ATTR));
    }

    buf = el.attribute(AMBIG_ATTR);
    if (buf == "true") {
        settings.useAmbiguousBases = true;
    } else {
        settings.useAmbiguousBases = false;
    }

    buf = el.attribute(EXPECTED_ATTR);
    QStringList splittedToRegions = buf.split(";", QString::SkipEmptyParts);
    U2Region r;
    foreach (QString regStr, splittedToRegions) {
        if (regStr.startsWith(QString(CIRCULAR_LABEL))) {
            regStr.chop(1);
            regStr.remove(QString(CIRCULAR_LABEL) + "(");

            QStringList regList = regStr.split(",", QString::SkipEmptyParts);
            r = U2Region();
            for (const QString& reg : qAsConst(regList)) {
                U2Region regionPart = stringToRegion(reg);
                if (regionPart.startPos > r.startPos) {
                    r.startPos = regionPart.startPos;
                }
                r.length += regionPart.length;
            }
        } else {
            r = stringToRegion(regStr);
        }
        if (r.isEmpty()) {
            stateInfo.setError(GTest::tr("value incorrect for %1").arg(EXPECTED_ATTR));
        }
        expectedResults.append(r);
    }

    buf = el.attribute(TASK_NOT_CANCELLED);
    if (!buf.isEmpty()) {
        if (buf.toLower() == "true") {
            checkTaskNotCancelled = true;
        }
    }

    buf = el.attribute(SKIP_RESULTS_CHECK);
    if (!buf.isEmpty()) {
        if (buf.toLower() == "true") {
            skipResultsCheck = true;
        }
    }
}

void GTest_FindAlgorithmTest::prepare() {
    Document* doc = getContext<Document>(this, docName);
    if (doc == nullptr) {
        stateInfo.setError(GTest::tr("context not found %1").arg(docName));
        return;
    }
    QList<GObject*> list = doc->findGObjectByType(GObjectTypes::SEQUENCE);
    if (list.size() == 0) {
        stateInfo.setError(GTest::tr("container of object with type \"%1\" is empty").arg(GObjectTypes::SEQUENCE));
        return;
    }

    foreach (GObject* go, list) {
        if (go->getGObjectName() == sequenceName) {
            se = qobject_cast<U2SequenceObject*>(go);
            break;
        }
    }

    U2OpStatusImpl os;
    settings.sequence = se->getWholeSequenceData(os);
    SAFE_POINT_OP(os, );
    settings.searchIsCircular = se->isCircular();
    settings.complementTT = GObjectUtils::findComplementTT(se->getAlphabet());
    if (translatetoAmino) {
        settings.proteinTT = GObjectUtils::findAminoTT(se, false);
    }
    t = new FindAlgorithmTask(settings);
    addSubTask(t);
}

Task::ReportResult GTest_FindAlgorithmTest::report() {
    QList<FindAlgorithmResult> actualResults = t->popResults();
    if (checkTaskNotCancelled && t->isCanceled()) {
        stateInfo.setError("'FindAlgorithmTask' was cancelled, but shouldn't.");
        return ReportResult_Finished;
    }
    if (skipResultsCheck) {
        return ReportResult_Finished;
    }
    if (actualResults.size() != expectedResults.size()) {
        stateInfo.setError(GTest::tr("Expected and actual result sizes are different: %1 , %2")
                               .arg(expectedResults.size())
                               .arg(actualResults.size()));
        return ReportResult_Finished;
    }

    for (int i = 0; i < actualResults.size(); i++) {
        if (!expectedResults.contains(actualResults[i].region)) {
            stateInfo.setError(GTest::tr("Expected and actual regions are different: %1..%2 , %3..%4")
                                   .arg(expectedResults[i].startPos)
                                   .arg(expectedResults[i].endPos())
                                   .arg(actualResults[i].region.startPos)
                                   .arg(actualResults[i].region.endPos()));
            return ReportResult_Finished;
        }
    }
    return ReportResult_Finished;
}

///////////////////////////////////////////////////////////////////////////////////////////
QList<XMLTestFactory*> FindAlgorithmTests::createTestFactories() {
    QList<XMLTestFactory*> res;
    res.append(GTest_FindAlgorithmTest::createFactory());

    return res;
}

}  // namespace U2
