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

#include <QTextStream>

#include <U2Core/IOAdapter.h>
#include <U2Core/Task.h>
#include <U2Core/U2Type.h>

namespace U2 {

class GObject;

class Tokenizer
// Breaks input stream on tokens (QString)
{
public:
    Tokenizer(IOAdapter* io_)
        : io(io_) {
        assert(io_ && "IO must exist");
    }

    QString look();

    QString get();

    void skip() {
        (void)get();
    }

    QStringList getUntil(const QString& what, Qt::CaseSensitivity cs = Qt::CaseInsensitive);

    void skipUntil(const QString& what, Qt::CaseSensitivity cs = Qt::CaseInsensitive);

    QString readUntil(const QRegExp& regExpMatcher);

    bool isEof() {
        return io->isEof();
    }

    IOAdapter* getIO() {
        return io;
    }

    QString whiteSpacesAfterLastToken;

private:
    IOAdapter* io = nullptr;
    QString next;

    QString buff;
    QTextStream buffStream;
};

class NEXUSParser
// NEXUS File format parser
{
public:
    NEXUSParser(IOAdapter* io_, const U2DbiRef& dbiRef_, const QString& folder, U2OpStatus& ti_)
        : io(io_), dbiRef(dbiRef_), folder(folder), ti(ti_), tz(io) {
        assert(io_ && "IO must exist");
    }

    QList<GObject*> loadObjects(const U2DbiRef& dbiRef);

    bool hasError() {
        return !errors.isEmpty();
    }
    bool hasWarnings() {
        return !warnings.isEmpty();
    }

    const QStringList& getErrors() const {
        return errors;
    }
    const QStringList& getWarnings() const {
        return warnings;
    }

private:
    typedef QMap<QString, QString> Context;

    bool skipCommand();
    bool readSimpleCommand(Context& ctx);

    bool readBlock(Context& ctx, const U2DbiRef& dbiRef);
    bool skipBlockContents();
    bool readTaxaContents(Context& ctx);
    bool readDataContents(Context& ctx);
    bool readTreesContents(Context& ctx, const U2DbiRef& dbiRef);

    void reportProgress() {
        ti.setProgress(io->getProgress());
    }

    // append object to objects, and resolve name conflicts
    void addObject(GObject* obj);

private:
    static const QString BEGIN;
    static const QString END;
    static const QString BLK_TAXA;
    static const QString BLK_DATA;
    static const QString BLK_CHARACTERS;
    static const QString CMD_DIMENSIONS;
    static const QString CMD_FORMAT;
    static const QString CMD_MATRIX;
    static const QString BLK_TREES;
    static const QString CMD_TREE;
    static const QString CMD_UTREE;
    static const QString CMD_TRANSLATE;

private:
    IOAdapter* io;
    const U2DbiRef& dbiRef;
    QString folder;
    U2OpStatus& ti;
    Tokenizer tz;

    QList<GObject*> objects;
    QSet<QString> objectNames;

    Context global;

    QStringList errors;
    QStringList warnings;
};

}  // namespace U2
