/**
 * 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 <QPair>
#include <QSharedData>

#include <U2Core/AnnotationData.h>
#include <U2Core/DNASequence.h>

#include <U2Lang/Configuration.h>

namespace U2 {

enum QDStrandOption { QDStrand_DirectOnly,
                      QDStrand_ComplementOnly,
                      QDStrand_Both };

class U2LANG_EXPORT QDParameters : public QObject, public Configuration {
    Q_OBJECT
public:
    virtual void setParameter(const QString& name, const QVariant& val);
    virtual void remap(const QMap<ActorId, ActorId>&) {
    }
signals:
    void si_modified();
};

class U2LANG_EXPORT QDActorParameters : public QDParameters {
    Q_OBJECT
public:
    QDActorParameters()
        : annKey("misc_feature") {
    }
    void setLabel(const QString& l) {
        label = l;
        emit si_modified();
    }
    const QString& getLabel() const {
        return label;
    }
    void setAnnotationKey(const QString& key) {
        annKey = key;
        emit si_modified();
    }
    const QString& getAnnotationKey() const {
        return annKey;
    }

private:
    QString label;
    QString annKey;
};

class QDSchemeUnit;
class U2LANG_EXPORT QDResultUnitData : public QSharedData {
public:
    QDResultUnitData()
        : owner(nullptr) {
    }

    QDSchemeUnit* owner;
    U2Strand strand;
    U2Region region;
    QVector<U2Qualifier> quals;
};

typedef QSharedDataPointer<QDResultUnitData> QDResultUnit;

class U2LANG_EXPORT QDResultGroup {
public:
    QDResultGroup(QDStrandOption _strand = QDStrand_DirectOnly)
        : strand(_strand), startPos(0), endPos(0) {
    }
    U2Region location() const;
    void add(const QDResultUnit& res);
    void add(const QList<QDResultUnit>& res);
    const QList<QDResultUnit>& getResultsList() const;

public:
    static void buildGroupFromSingleResult(const QDResultUnit& ru, QList<QDResultGroup*>& results);

public:
    QDStrandOption strand;

private:
    int startPos;
    int endPos;
    QList<QDResultUnit> results;
};

inline U2Region QDResultGroup::location() const {
    return U2Region(startPos, endPos - startPos);
}
inline const QList<QDResultUnit>& QDResultGroup::getResultsList() const {
    return results;
}

typedef QPair<QString, QString> StringAttribute;

class QDScheme;
class QDConstraint;
class QDActor;

class U2LANG_EXPORT QDActorPrototype {
public:
    QDActorPrototype()
        : editor(nullptr) {
    }
    virtual ~QDActorPrototype();
    const QList<Attribute*>& getParameters() const {
        return attributes;
    }
    ConfigurationEditor* getEditor() const {
        return editor;
    }

    const Descriptor& getDescriptor() const {
        return descriptor;
    }
    QString getId() const {
        return descriptor.getId();
    }
    QString getDisplayName() const {
        return descriptor.getDisplayName();
    }
    virtual QIcon getIcon() const {
        return QIcon();
    }

    virtual QDActor* createInstance() const = 0;

protected:
    QList<Attribute*> attributes;
    ConfigurationEditor* editor;
    Descriptor descriptor;
};

class U2LANG_EXPORT QDActor : public QObject {
    friend class QDScheme;
    Q_OBJECT
public:
    QDActor(QDActorPrototype const* proto);
    virtual ~QDActor();

    QDScheme* getScheme() const {
        return scheme;
    }
    QDActorPrototype const* getProto() const {
        return proto;
    }
    QDActorParameters* getParameters() const {
        return cfg;
    }
    QString getUnitId(QDSchemeUnit* su) const {
        return units.key(su);
    }
    QDSchemeUnit* getSchemeUnit(const QString& id) const {
        return units.value(id);
    }
    QList<QDSchemeUnit*> getSchemeUnits() const {
        return units.values();
    }
    const QList<QDConstraint*>& getParamConstraints() const {
        return paramConstraints;
    }

    QDStrandOption getStrand() const;
    void setStrand(QDStrandOption stOp);

    virtual void updateEditor() {
    }

    bool hasAnyDirection() const {
        return getStrand() == QDStrand_Both;
    }
    bool hasForwardDirection() const {
        return getStrand() == QDStrand_DirectOnly;
    }
    bool hasBackwardDirection() const {
        return getStrand() == QDStrand_ComplementOnly;
    }

    bool isSimmetric() const {
        return simmetric;
    }

    QList<QDConstraint*> getConstraints() const;

    QString getActorType() const {
        return proto->getId();
    }
    const QString& annotateAs() const {
        return cfg->getAnnotationKey();
    }

    void filterResults(const QVector<U2Region>& location);

    QList<QDResultGroup*> popResults();
    void reset();

    virtual int getMinResultLen() const = 0;
    virtual int getMaxResultLen() const = 0;
    virtual QString getText() const = 0;
    virtual Task* getAlgorithmTask(const QVector<U2Region>& location) = 0;
    virtual bool hasStrand() const {
        return true;
    }
    // implement in subclass to provide configuration representation in serialized scheme
    virtual QList<QPair<QString, QString>> saveConfiguration() const;
    virtual void loadConfiguration(const QList<QPair<QString, QString>>& strMap);
    // implement in subclass to customize the color of the visual actor representation
    virtual QColor defaultColor() const {
        return QColor(0x98, 0xff, 0xc5);
    }
signals:
    void si_strandChanged(QDStrandOption);

public:
    static const int DEFAULT_MAX_RESULT_LENGTH;

protected:
    QDStrandOption getStrandToRun() const;

protected:
    QDScheme* scheme;
    QDActorPrototype const* proto;
    QDActorParameters* cfg;
    QMap<QString, QDSchemeUnit*> units;
    QList<QDConstraint*> paramConstraints;
    QList<QDResultGroup*> results;
    QMap<QString, QVariant> defaultCfg;
    QDStrandOption strand;
    bool simmetric;
};

class QDDistanceConstraint;
class U2LANG_EXPORT QDSchemeUnit {
    friend class QDScheme;

public:
    QDSchemeUnit(QDActor* parent)
        : actor(parent) {
    }
    const QList<QDConstraint*>& getConstraints() const {
        return schemeConstraints;
    }
    QList<QDDistanceConstraint*> getDistanceConstraints() const;
    QDActor* getActor() const {
        return actor;
    }
    QString getId() const {
        auto thisPtr = const_cast<QDSchemeUnit*>(this);
        return actor->getUnitId(thisPtr);
    }

private:
    QDActor* actor;
    QList<QDConstraint*> schemeConstraints;
};

class U2LANG_EXPORT QDPath {
public:
    QDPath()
        : pathSrc(nullptr), pathDst(nullptr), overallConstraint(nullptr) {
    }
    ~QDPath();
    QDPath* clone() const;
    bool addConstraint(QDDistanceConstraint* dc);
    QDSchemeUnit const* getSrc() const {
        return pathSrc;
    }
    QDSchemeUnit const* getDst() const {
        return pathDst;
    }
    const QList<QDDistanceConstraint*>& getConstraints() const {
        return constraints;
    }
    // returns unsorted list of involved scheme units
    const QList<QDSchemeUnit*>& getSchemeUnits() const {
        return schemeUnits;
    }
    QDDistanceConstraint* toConstraint();

private:
    QDSchemeUnit* pathSrc;
    QDSchemeUnit* pathDst;
    QDDistanceConstraint* overallConstraint;
    QList<QDDistanceConstraint*> constraints;
    QList<QDSchemeUnit*> schemeUnits;
};

class U2SequenceObject;
class U2LANG_EXPORT QDScheme : public QObject {
    Q_OBJECT
public:
    QDScheme()
        : strand(QDStrand_Both) {
    }
    ~QDScheme();
    void clear();
    void addActor(QDActor* a);
    void addConstraint(QDConstraint* constraint);
    bool removeActor(QDActor* a);
    void removeConstraint(QDConstraint* constraint);
    const DNASequence& getSequence() const {
        return dna;
    }
    U2EntityRef getEntityRef() const {
        return entityRef;
    }
    void setSequence(const DNASequence& sequence) {
        dna = sequence;
    }
    void setEntityRef(const U2EntityRef& _entityRef) {
        entityRef = _entityRef;
    }
    const QList<QDActor*>& getActors() const {
        return actors;
    }
    QList<QDConstraint*> getConstraints() const;
    QList<QDConstraint*> getConstraints(QDSchemeUnit const* su1, QDSchemeUnit const* su2) const;
    QList<QDPath*> findPaths(QDSchemeUnit* src, QDSchemeUnit* dst);
    void setOrder(QDActor* a, int serialNum);
    bool isEmpty() const {
        return actors.isEmpty();
    }
    bool isValid() const;
    QDActor* getActorByLabel(const QString& label) const;

    QDStrandOption getStrand() const {
        return strand;
    }
    void setStrand(QDStrandOption opt) {
        strand = opt;
    }

    // actor group methods
    void adaptActorsOrder();
    QString getActorGroup(QDActor* a) const;
    QList<QString> getActorGroups() const {
        return actorGroups.keys();
    }
    QList<QDActor*> getActors(const QString& group) const {
        return actorGroups.value(group);
    }
    void addActorToGroup(QDActor* a, const QString& group);
    bool removeActorFromGroup(QDActor* a);
    void createActorGroup(const QString& name);
    bool removeActorGroup(const QString& name);
    bool validateGroupName(const QString& name) const;
    int getRequiredNumber(const QString& group) const {
        return actorGroupReqNum.value(group);
    }
    void setRequiredNum(const QString& group, int num);
signals:
    void si_schemeChanged();

private:
    void findRoute(QDSchemeUnit* curSu, QDSchemeUnit* routeDst, QList<QDSchemeUnit*>& currentRoute, QList<QList<QDSchemeUnit*>> routes);

private:
    QList<QDActor*> actors;
    QMap<QString, QList<QDActor*>> actorGroups;
    QMap<QString, int> actorGroupReqNum;
    U2EntityRef entityRef;
    DNASequence dna;
    QDStrandOption strand;
};

class U2LANG_EXPORT QDAttributeNameConverter {
public:
    static QString convertAttrName(const QString& str) {
        return str.toLower().replace(' ', '_');
    }
};

class U2LANG_EXPORT QDAttributeValueMapper {
public:
    enum ValueType { BOOLEAN_TYPE,
                     UNKNOWN_TYPE };

    static QVariant stringToAttributeValue(const QString& str);
    static ValueType getType(const QString& val);

public:
    static const QMap<QString, bool> BOOLEAN_MAP;

private:
    static QMap<QString, bool> initBooleanMap();
};

}  // namespace U2

Q_DECLARE_METATYPE(U2::QDActorPrototype*)
