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

#include <QScopedPointer>

#include <U2Core/AnnotationTableObject.h>
#include <U2Core/AppContext.h>
#include <U2Core/Counter.h>
#include <U2Core/DNATranslation.h>
#include <U2Core/IOAdapter.h>
#include <U2Core/IOAdapterUtils.h>
#include <U2Core/L10n.h>
#include <U2Core/TextUtils.h>
#include <U2Core/U2SafePoints.h>

namespace U2 {

const QString ExportAnnotations2CSVTask::SEQUENCE_NAME = "sequence_name";

ExportAnnotations2CSVTask::ExportAnnotations2CSVTask(const QList<Annotation*>& annotations, const QByteArray& sequence, const QString& _seqName, const DNATranslation* complementTranslation, bool exportSequence, bool _exportSeqName, const QString& url, bool apnd, const QString& sep)
    : Task(tr("Export annotations to CSV format"), TaskFlag_None), annotations(annotations), sequence(sequence), seqName(_seqName),
      complementTranslation(complementTranslation), exportSequence(exportSequence), exportSequenceName(_exportSeqName), url(url),
      append(apnd), separator(sep) {
    GCOUNTER(cvar, "ExportAnnotattions2CSVTask");
}

static void writeCSVLine(const QStringList& container, IOAdapter* ioAdapter, const QString& separator, U2OpStatus& os) {
    bool first = true;
    foreach (const QString& value, container) {
        if (!first) {
            if (0 == ioAdapter->writeBlock(separator.toLatin1())) {
                os.setError(L10N::errorWritingFile(ioAdapter->getURL()));
                return;
            }
        }
        QString preparedStr = value;
        preparedStr.replace("\"", "\"\"");
        preparedStr = "\"" + preparedStr + "\"";

        if (0 == ioAdapter->writeBlock(preparedStr.toLocal8Bit())) {
            os.setError(L10N::errorWritingFile(ioAdapter->getURL()));
            return;
        }
        first = false;
    }
    if (0 == ioAdapter->writeBlock("\n")) {
        os.setError(L10N::errorWritingFile(ioAdapter->getURL()));
        return;
    }
}

void ExportAnnotations2CSVTask::run() {
    QScopedPointer<IOAdapter> ioAdapter;

    IOAdapterId ioAdapterId = IOAdapterUtils::url2io(url);
    IOAdapterRegistry* ioRegistry = AppContext::getIOAdapterRegistry();
    CHECK_EXT(ioRegistry != nullptr,
              stateInfo.setError(tr("Invalid I/O environment!").arg(url)), );
    IOAdapterFactory* ioAdapterFactory = ioRegistry->getIOAdapterFactoryById(ioAdapterId);
    CHECK_EXT(ioAdapterFactory != nullptr,
              stateInfo.setError(tr("No IO adapter found for URL: %1").arg(url)), );
    ioAdapter.reset(ioAdapterFactory->createIOAdapter());

    if (!ioAdapter->open(url, append ? IOAdapterMode_Append : IOAdapterMode_Write)) {
        stateInfo.setError(L10N::errorOpeningFileWrite(url));
        return;
    }

    QHash<QString, int> columnIndices;
    QStringList columnNames;
    columnNames << tr("Group") << tr("Name") << tr("Start") << tr("End") << tr("Length") << tr("Complementary");
    if (exportSequenceName) {
        columnNames << tr("Sequence name");
    }
    if (exportSequence) {
        columnNames << tr("Sequence");
    }

    bool hasSequenceNameQualifier = false;
    for (Annotation* annotation: qAsConst(annotations)) {
        foreach (const U2Qualifier& qualifier, annotation->getQualifiers()) {
            const QString& qName = qualifier.name;
            if (qName == SEQUENCE_NAME) {
                hasSequenceNameQualifier = true;
                continue;
            }
            if (!columnIndices.contains(qName)) {
                columnIndices.insert(qName, columnNames.size());
                columnNames.append(qName);
            }
        }
    }
    writeCSVLine(columnNames, ioAdapter.data(), separator, stateInfo);
    CHECK_OP(stateInfo, );

    bool noComplementarySequence = false;
    for (Annotation* annotation: qAsConst(annotations)) {
        QVector<U2Region> regions = annotation->getRegions();
        for (const U2Region& region: qAsConst(regions)) {
            QStringList values;
            values << annotation->getGroup()->getGroupPath();
            values << annotation->getName();
            values << QString::number(region.startPos + 1);
            values << QString::number(region.startPos + region.length);
            values << QString::number(region.length);

            const bool isComplementary = annotation->getStrand().isComplementary();
            values << ((isComplementary) ? tr("yes") : tr("no"));

            if (exportSequenceName) {
                if (!seqName.isEmpty()) {
                    values << seqName.toLatin1();
                } else if (hasSequenceNameQualifier) {
                    foreach (const U2Qualifier& qf, annotation->getQualifiers()) {
                        if (qf.name == SEQUENCE_NAME) {
                            values << qf.value;
                        }
                    }
                }
            }
            if (exportSequence) {
                QByteArray sequencePart = sequence.mid(region.startPos, region.length);
                if (isComplementary) {
                    if (complementTranslation != nullptr) {
                        complementTranslation->translate(sequencePart.data(), sequencePart.size());
                        TextUtils::reverse(sequencePart.data(), sequencePart.size());
                    } else {
                        noComplementarySequence = true;
                        sequencePart.clear();
                    }
                }
                values << sequencePart;
            }

            // add empty strings as default qualifier values
            while (values.size() < columnNames.size()) {
                values << QString();
            }

            foreach (const U2Qualifier& qualifier, annotation->getQualifiers()) {
                if (qualifier.name == SEQUENCE_NAME) {
                    continue;
                }

                int qualifiedIndex = columnIndices[qualifier.name];
                SAFE_POINT(qualifiedIndex > 0 && qualifiedIndex < values.length(), "Invalid qualifier index", );
                values[qualifiedIndex] = qualifier.value;
            }
            writeCSVLine(values, ioAdapter.data(), separator, stateInfo);
            CHECK_OP(stateInfo, );
        }
    }
    if (noComplementarySequence) {
        taskLog.error(tr("Attaching a sequence to an annotation was ignored. The annotation is on the complementary strand. Can not generate a complementary sequence for a non-nucleic alphabet."));
    }
}

}  // namespace U2
