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

#ifdef __GNUC__
#    include <stdlib.h>
#endif

#include <U2Core/U2SafePoints.h>

#include <U2Lang/WorkflowRunSerializedSchemeTask.h>

#include "SchemeHandle.h"
#include "SchemeWrapper.h"
#include "TextConversionUtils.h"

extern U2ErrorType processTask(U2::Task* task);

extern "C" {

U2SCRIPT_EXPORT U2ErrorType createScheme(const wchar_t* _pathToScheme, SchemeHandle* scheme) {
    const QString pathToScheme = QString::fromWCharArray(_pathToScheme);
    U2ErrorType result = U2_OK;
    try {
        *scheme = new U2::SchemeWrapper(pathToScheme, &result);
    } catch (const std::bad_alloc&) {
        delete reinterpret_cast<U2::SchemeWrapper*>(*scheme);
        result = U2_NOT_ENOUGH_MEMORY;
    }
    return result;
}

U2SCRIPT_EXPORT U2ErrorType addReaderToScheme(SchemeHandle _scheme, const wchar_t* _readerType, const wchar_t* _inputFilePath, int maxExpectedNameLength, wchar_t* _name) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_readerType != nullptr && _inputFilePath != nullptr && _name != nullptr, U2_INVALID_STRING);
    const QString readerType = QString::fromWCharArray(_readerType);
    const QString inputFilePath = QString::fromWCharArray(_inputFilePath);

    QString readerName;
    U2ErrorType result = scheme->addReaderAndGetItsName(readerType, inputFilePath, readerName);
    CHECK(U2_OK == result, result);
    return U2::TextConversionUtils::qstringToCstring(readerName, maxExpectedNameLength, _name);
}

U2SCRIPT_EXPORT U2ErrorType addWriterToScheme(SchemeHandle _scheme, const wchar_t* _writerType, const wchar_t* _outputFilePath, int maxExpectedNameLength, wchar_t* _name) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_writerType != nullptr && _outputFilePath != nullptr && _name != nullptr, U2_INVALID_STRING);
    const QString writerType = QString::fromWCharArray(_writerType);
    const QString outputFilePath = QString::fromWCharArray(_outputFilePath);

    QString writerName;
    U2ErrorType result = scheme->addWriterAndGetItsName(writerType, outputFilePath, writerName);
    CHECK(U2_OK == result, result);
    return U2::TextConversionUtils::qstringToCstring(writerName, maxExpectedNameLength, _name);
}

U2SCRIPT_EXPORT U2ErrorType setSchemeElementAttribute(SchemeHandle _scheme,
                                                      const wchar_t* _elementName,
                                                      const wchar_t* _attributeName,
                                                      const wchar_t* _attributeValue) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_elementName != nullptr && _attributeName != nullptr && _attributeValue != nullptr,
          U2_INVALID_STRING);
    const QString elementName = QString::fromWCharArray(_elementName);
    const QString attributeName = QString::fromWCharArray(_attributeName);
    const QString attributeValue = QString::fromWCharArray(_attributeValue);

    return scheme->setElementAttribute(elementName, attributeName, attributeValue);
}

U2SCRIPT_EXPORT U2ErrorType getSchemeElementAttribute(SchemeHandle _scheme,
                                                      const wchar_t* _elementName,
                                                      const wchar_t* _attributeName,
                                                      int maxExpectedNameLength,
                                                      wchar_t* _attributeValue) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_elementName != nullptr && _attributeName != nullptr && nullptr != _attributeValue,
          U2_INVALID_STRING);
    const QString elementName = QString::fromWCharArray(_elementName);
    const QString attributeName = QString::fromWCharArray(_attributeName);

    QString value;
    U2ErrorType result = scheme->getElementAttribute(elementName, attributeName, value);
    CHECK(U2_OK == result, result);
    return U2::TextConversionUtils::qstringToCstring(value, maxExpectedNameLength, _attributeValue);
}

U2SCRIPT_EXPORT U2ErrorType addElementToScheme(SchemeHandle _scheme, const wchar_t* _elementType, int maxExpectedNameLength, wchar_t* _name) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_elementType != nullptr, U2_INVALID_STRING);
    const QString elementType = QString::fromWCharArray(_elementType);

    QString elementName;
    U2ErrorType result = scheme->addNewElementAndGetItsName(elementType, elementName);
    CHECK(U2_OK == result, result);
    return U2::TextConversionUtils::qstringToCstring(elementName, maxExpectedNameLength, _name);
}

U2SCRIPT_EXPORT U2ErrorType addFlowToScheme(SchemeHandle _scheme, const wchar_t* _srcElementName, const wchar_t* _srcPortName, const wchar_t* _dstElementName, const wchar_t* _dstPortName) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_srcElementName != nullptr && _srcPortName != nullptr && nullptr != _dstElementName && nullptr != _dstPortName, U2_INVALID_STRING);
    const QString srcElementName = QString::fromWCharArray(_srcElementName);
    const QString srcPortName = QString::fromWCharArray(_srcPortName);
    const QString dstElementName = QString::fromWCharArray(_dstElementName);
    const QString dstPortName = QString::fromWCharArray(_dstPortName);

    return scheme->addFlow(srcElementName, srcPortName, dstElementName, dstPortName);
}

U2SCRIPT_EXPORT U2ErrorType addSchemeActorsBinding(SchemeHandle _scheme,
                                                   const wchar_t* _srcElementName,
                                                   const wchar_t* _srcSlotName,
                                                   const wchar_t* _dstElementName,
                                                   const wchar_t* _dstPortAndSlotNames) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_srcElementName != nullptr && _srcSlotName != nullptr && nullptr != _dstElementName && nullptr != _dstPortAndSlotNames, U2_INVALID_STRING);
    const QString srcElementName = QString::fromWCharArray(_srcElementName);
    const QString srcSlotName = QString::fromWCharArray(_srcSlotName);
    const QString dstElementName = QString::fromWCharArray(_dstElementName);
    const QString dstPortAndSlotNames = QString::fromWCharArray(_dstPortAndSlotNames);

    return scheme->addActorsBinding(srcElementName, srcSlotName, dstElementName, dstPortAndSlotNames);
}

U2SCRIPT_EXPORT U2ErrorType saveSchemeToFile(SchemeHandle _scheme, const wchar_t* _path) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    CHECK(scheme != nullptr, U2_INVALID_SCHEME);
    CHECK(_path != nullptr, U2_INVALID_STRING);
    QString pathString = QString::fromWCharArray(_path);
    return scheme->saveToFile(pathString);
}

U2SCRIPT_EXPORT void releaseScheme(SchemeHandle _scheme) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    delete scheme;
}

U2SCRIPT_EXPORT U2ErrorType createSas(const wchar_t* _algorithmType, const wchar_t* _inputPath, const wchar_t* _outputPath, SchemeHandle* scheme) {
    CHECK(_algorithmType != nullptr, U2_INVALID_STRING);
    const QString algorithmType = QString::fromWCharArray(_algorithmType);
    const QString inputPath = QString::fromWCharArray(_inputPath);
    const QString outputPath = QString::fromWCharArray(_outputPath);
    return U2::SchemeWrapper::createSas(algorithmType, inputPath, outputPath, reinterpret_cast<U2::SchemeWrapper**>(scheme));
}

U2SCRIPT_EXPORT U2ErrorType launchScheme(SchemeHandle _scheme, int* outputFilesCount, wchar_t*** outputFileNames) {
    auto scheme = reinterpret_cast<U2::SchemeWrapper*>(_scheme);
    if (scheme == nullptr) {
        return U2_INVALID_SCHEME;
    }
    QString pathToScheme;
    U2ErrorType result = scheme->saveToFile(pathToScheme);
    CHECK(U2_OK == result, result);
    U2::BaseSerializedSchemeRunner* launchSchemeTask = nullptr;
    try {
        QStringList outputFiles;
        launchSchemeTask = new U2::WorkflowRunSerializedSchemeTask(pathToScheme, nullptr, outputFiles);
        result = processTask(launchSchemeTask);
        CHECK(U2_OK == result, result);
        const int countOfFiles = outputFiles.size();
        *outputFileNames = (wchar_t**)malloc(countOfFiles * sizeof(wchar_t**));
        if (*outputFileNames == nullptr) {
            throw std::bad_alloc();
        }
        for (int i = 0; i < countOfFiles; ++i) {
            const int pathLength = outputFiles[i].size() + 1;
            (*outputFileNames)[i] = (wchar_t*)malloc(pathLength * sizeof(wchar_t));
            // handle failed memory allocation
            if ((*outputFileNames)[i] == nullptr) {
                for (int j = 0; j < i; ++j) {
                    free((*outputFileNames)[i]);
                }
                free(*outputFileNames);
                throw std::bad_alloc();
            }
            outputFiles[i].toWCharArray((*outputFileNames)[i]);
            (*outputFileNames)[i][outputFiles[i].size()] = '\0';
        }
        *outputFilesCount = countOfFiles;
    } catch (const std::bad_alloc&) {
        delete launchSchemeTask;
        return U2_NOT_ENOUGH_MEMORY;
    }
    return U2_OK;
}

U2SCRIPT_EXPORT U2ErrorType launchSas(const wchar_t* _algorithmType, const wchar_t* _inputPath, const wchar_t* _outputPath, int* outputFilesCount, wchar_t*** _outputFileNames) {
    CHECK(_algorithmType != nullptr && _inputPath != nullptr && nullptr != _outputPath, U2_INVALID_STRING);
    U2::SchemeWrapper* scheme = nullptr;
    const QString algorithmType = QString::fromWCharArray(_algorithmType);
    const QString inputPath = QString::fromWCharArray(_inputPath);
    const QString outputPath = QString::fromWCharArray(_outputPath);

    U2ErrorType result = U2::SchemeWrapper::createSas(algorithmType, inputPath, outputPath, &scheme);
    CHECK(U2_OK == result, result);
    result = launchScheme(scheme, outputFilesCount, _outputFileNames);

    delete scheme;
    return U2_OK;
}
}
