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

#include <U2Core/U2OpStatusUtils.h>
#include <U2Core/U2SafePoints.h>

#include <U2Lang/BaseSlots.h>
#include <U2Lang/BaseTypes.h>
#include <U2Lang/IntegralBus.h>
#include <U2Lang/IntegralBusModel.h>

namespace U2 {
namespace Workflow {

IntegralBusUtils::SplitResult IntegralBusUtils::splitCandidates(const QList<Descriptor>& candidates, const Descriptor& toDesc, DataTypePtr toDatatype) {
    CandidatesSplitter* splitter = CandidatesSplitterRegistry::instance()->findSplitter(toDesc, toDatatype);
    SAFE_POINT(splitter != nullptr, "NULL splitter", SplitResult());
    return splitter->splitCandidates(candidates);
}

void IntegralBusUtils::remapBus(StrStrMap& busMap, const ActorId& oldId, const ActorId& newId, const PortMapping& mapping) {
    foreach (QString key, busMap.uniqueKeys()) {
        U2OpStatus2Log os;
        QList<IntegralBusSlot> slotList = IntegralBusSlot::listFromString(busMap[key], os);
        QList<IntegralBusSlot> newSlots;
        for (const IntegralBusSlot& slot : qAsConst(slotList)) {
            IntegralBusSlot newSlot = slot;
            if (slot.actorId() == oldId) {
                U2OpStatusImpl os2;
                QString newSlotId = mapping.getDstSlotId(slot.getId(), os2);
                if (!os2.hasError()) {
                    newSlot = IntegralBusSlot(newSlotId, "", newId);
                }
            }
            newSlots << newSlot;
        }
        busMap[key] = IntegralBusSlot::listToString(newSlots);
    }
}

void IntegralBusUtils::remapPathedSlotString(QString& pathedSlotStr, const ActorId& oldId, const ActorId& newId, const PortMapping& mapping) {
    if (pathedSlotStr.isEmpty()) {
        return;
    }
    QString slotStr;
    QStringList path;
    BusMap::parseSource(pathedSlotStr, slotStr, path);

    U2OpStatus2Log logOs;
    IntegralBusSlot slot = IntegralBusSlot::fromString(slotStr, logOs);
    if (slot.actorId() == oldId) {
        U2OpStatusImpl os;
        QString newSlot = mapping.getDstSlotId(slot.getId(), os);
        if (!os.hasError()) {
            slot = IntegralBusSlot(newSlot, "", newId);
        }
    }
    pathedSlotStr = slot.toString();

    if (!path.isEmpty()) {
        for (QStringList::iterator i = path.begin(); i != path.end(); i++) {
            if (*i == oldId) {
                *i = newId;
            }
        }
        pathedSlotStr += ">" + path.join(",");
    }
}

namespace {
enum StringSlotType {
    TEXT,
    URL,
    DATASET
};

bool isUrlSlot(const Descriptor& slot) {
    const QString id = slot.getId();
    return id.contains(BaseSlots::URL_SLOT().getId());
}
bool isDatasetSlot(const Descriptor& slot) {
    return (BaseSlots::DATASET_SLOT() == slot);
}

StringSlotType getSlotType(const Descriptor& slot) {
    if (isUrlSlot(slot)) {
        return URL;
    }
    if (isDatasetSlot(slot)) {
        return DATASET;
    }
    return TEXT;
}
}  // namespace

QList<Descriptor> IntegralBusUtils::getSlotsByType(const QMap<Descriptor, DataTypePtr>& busMap, const Descriptor& slot, const DataTypePtr& type) {
    QList<Descriptor> result = busMap.keys(type);
    CHECK(BaseTypes::STRING_TYPE() == type, result);
    CHECK(result.size() > 1, result);

    const StringSlotType targetType = getSlotType(slot);
    foreach (const Descriptor& desc, result) {
        if (targetType != getSlotType(desc)) {
            result.removeOne(desc);
        }
    }
    return result;
}

/************************************************************************/
/* Splitters */
/************************************************************************/
class DefaultSplitter : public CandidatesSplitter {
public:
    DefaultSplitter()
        : CandidatesSplitter(ID) {
    }

    bool canSplit(const Descriptor& /*toDesc*/, DataTypePtr /*toDatatype*/) {
        return true;
    }

    static const QString ID;

protected:
    bool isMain(const QString& /*candidateSlotId*/) {
        return true;
    }
};

const QString DefaultSplitter::ID = "default";

class TextSplitter : public CandidatesSplitter {
public:
    TextSplitter()
        : CandidatesSplitter(ID) {
    }

    bool canSplit(const Descriptor& /*toDesc*/, DataTypePtr toDatatype) {
        return (BaseTypes::STRING_TYPE() == toDatatype);
    }

    static const QString ID;

protected:
    bool isMain(const QString& candidateSlotId) {
        return (BaseSlots::URL_SLOT().getId() != candidateSlotId && BaseSlots::DATASET_SLOT().getId() != candidateSlotId);
    }
};
const QString TextSplitter::ID = "text";

class DatasetsSplitter : public CandidatesSplitter {
public:
    DatasetsSplitter()
        : CandidatesSplitter(ID) {
    }

    bool canSplit(const Descriptor& toDesc, DataTypePtr toDatatype) {
        return ((BaseTypes::STRING_TYPE() == toDatatype) && isDatasetSlot(toDesc));
    }

    static const QString ID;

protected:
    bool isMain(const QString& candidateSlotId) {
        return (BaseSlots::DATASET_SLOT().getId() == candidateSlotId);
    }
};

const QString DatasetsSplitter::ID = "datasets";

class UrlSplitter : public CandidatesSplitter {
public:
    UrlSplitter()
        : CandidatesSplitter(ID) {
    }

    bool canSplit(const Descriptor& toDesc, DataTypePtr toDatatype) {
        return ((BaseTypes::STRING_TYPE() == toDatatype) && isUrlSlot(toDesc));
    }

    static const QString ID;

protected:
    bool isMain(const QString& candidateSlotId) {
        return (BaseSlots::URL_SLOT().getId() == candidateSlotId);
    }
};

const QString UrlSplitter::ID = "url";

/************************************************************************/
/* CandidatesSplitter */
/************************************************************************/
CandidatesSplitter::CandidatesSplitter(const QString& _id)
    : id(_id) {
}

CandidatesSplitter::~CandidatesSplitter() {
}

IntegralBusUtils::SplitResult CandidatesSplitter::splitCandidates(const QList<Descriptor>& candidates) {
    IntegralBusUtils::SplitResult r;
    foreach (const Descriptor& c, candidates) {
        U2OpStatus2Log os;
        IntegralBusSlot slot = IntegralBusSlot::fromString(c.getId(), os);
        if (slot.getId().isEmpty() || isMain(slot.getId())) {
            r.mainDescs << c;
        } else {
            r.otherDescs << c;
        }
    }
    return r;
}

const QString& CandidatesSplitter::getId() const {
    return id;
}

/************************************************************************/
/* CandidatesSplitterRegistry */
/************************************************************************/
CandidatesSplitterRegistry* CandidatesSplitterRegistry::_instance = nullptr;
CandidatesSplitterRegistry* CandidatesSplitterRegistry::instance() {
    if (_instance == nullptr) {
        _instance = new CandidatesSplitterRegistry();
    }
    return _instance;
}

CandidatesSplitter* CandidatesSplitterRegistry::findSplitter(const Descriptor& toDesc, DataTypePtr toDatatype) {
    foreach (CandidatesSplitter* splitter, splitters) {
        if (splitter->canSplit(toDesc, toDatatype)) {
            return splitter;
        }
    }
    return nullptr;
}

CandidatesSplitter* CandidatesSplitterRegistry::findSplitter(const QString& id) {
    foreach (CandidatesSplitter* splitter, splitters) {
        if (id == splitter->getId()) {
            return splitter;
        }
    }
    return nullptr;
}

void CandidatesSplitterRegistry::registerSplitter(CandidatesSplitter* splitter) {
    if (!splitters.contains(splitter)) {
        splitters.prepend(splitter);
    }
}

void CandidatesSplitterRegistry::unregisterSplitter(const QString& id) {
    CandidatesSplitter* splitter = findSplitter(id);
    CHECK(splitter != nullptr, );
    splitters.removeAll(splitter);
    delete splitter;
}

CandidatesSplitterRegistry::CandidatesSplitterRegistry() {
    // The order is needed
    splitters << new UrlSplitter();
    splitters << new DatasetsSplitter();
    splitters << new TextSplitter();
    splitters << new DefaultSplitter();
}

CandidatesSplitterRegistry::~CandidatesSplitterRegistry() {
    qDeleteAll(splitters);
    splitters.clear();
}

}  // namespace Workflow
}  // namespace U2
