/***************************************************************************
 *   SPDX-License-Identifier: GPL-2.0-or-later
 *                                                                         *
 *   SPDX-FileCopyrightText: 2004-2022 Thomas Fischer <fischer@unix-ag.uni-kl.de>
 *                                                                         *
 *   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, see <https://www.gnu.org/licenses/>. *
 ***************************************************************************/

#ifndef KBIBTEX_DATA_VALUE_H
#define KBIBTEX_DATA_VALUE_H

#include <QVector>
#include <QVariant>
#include <QSharedPointer>

#ifdef HAVE_KF
#include "kbibtexdata_export.h"
#endif // HAVE_KF

/**
  * Generic class of an information element in a @see Value object.
  * In BibTeX, ValueItems are concatenated by "#".
  */
class KBIBTEXDATA_EXPORT ValueItem
{
public:
    enum class ReplaceMode {CompleteMatch, AnySubstring};

    ValueItem();
    virtual ~ValueItem();

    virtual void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) = 0;

    /**
      * Check if this object contains text pattern @p pattern.
      * @param pattern Pattern to check for
      * @param caseSensitive Case sensitivity setting for check
      * @return TRUE if pattern is contained within this value, otherwise FALSE
      */
    virtual bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const = 0;

    /**
      * Compare to instance if they contain the same content.
      * Subclasses implement under which conditions two instances are equal.
      * Subclasses of different type are never equal.
      * @param other other instance to compare with
      * @return TRUE if both instances are equal
      */
    virtual bool operator==(const ValueItem &other) const = 0;
    bool operator!=(const ValueItem &other) const;

    /**
     * Unique numeric identifier for every ValueItem instance.
     * @return Unique numeric identifier
     */
    quint64 id() const;

protected:
    /// contains text fragments to be removed before performing a "contains pattern" operation
    /// includes among other "{" and "}"
    static const QRegularExpression ignoredInSorting;

private:
    /// Unique numeric identifier
    const quint64 internalId;
    /// Keeping track of next available unique numeric identifier
    static quint64 internalIdCounter;
};

class KBIBTEXDATA_EXPORT Keyword: public ValueItem
{
public:
    Keyword(const Keyword &other);
    explicit Keyword(const QString &text);

    void setText(const QString &text);
    QString text() const;

    void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override;
    bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override;
    bool operator==(const ValueItem &other) const override;

    /**
     * Cheap and fast test if another ValueItem is a Keyword object.
     * @param other another ValueItem object to test
     * @return true if ValueItem is actually a Keyword
     */
    static bool isKeyword(const ValueItem &other);

protected:
    QString m_text;
};

class KBIBTEXDATA_EXPORT Person: public ValueItem
{
public:
    /**
    * Create a representation for a person's name. In bibliographies,
    * a person is either an author or an editor. The four parameters
    * cover all common parts of a name. Only first and last name are
    * mandatory (each person should have those).
    @param firstName First name of a person. Example: "Peter"
    @param lastName Last name of a person. Example: "Smith"
    @param suffix Suffix after a name. Example: "jr."
    */
    Person(const QString &firstName, const QString &lastName, const QString &suffix = QString());
    Person(const Person &other);

    QString firstName() const;
    QString lastName() const;
    QString suffix() const;

    void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override;
    bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override;
    bool operator==(const ValueItem &other) const override;

    static QString transcribePersonName(const QString &formatting, const QString &firstName, const QString &lastName, const QString &suffix = QString());
    static QString transcribePersonName(const Person *person, const QString &formatting);

    /**
     * Cheap and fast test if another ValueItem is a Person object.
     * @param other another ValueItem object to test
     * @return true if ValueItem is actually a Person
     */
    static bool isPerson(const ValueItem &other);

private:
    QString m_firstName;
    QString m_lastName;
    QString m_suffix;
};

Q_DECLARE_METATYPE(Person*)

KBIBTEXDATA_EXPORT QDebug operator<<(QDebug dbg, const Person *person);


class KBIBTEXDATA_EXPORT MacroKey: public ValueItem
{
public:
    MacroKey(const MacroKey &other);
    explicit MacroKey(const QString &text);

    void setText(const QString &text);
    QString text() const;
    bool isValid();

    void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override;
    bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override;
    bool operator==(const ValueItem &other) const override;

    /**
     * Cheap and fast test if another ValueItem is a MacroKey object.
     * @param other another ValueItem object to test
     * @return true if ValueItem is actually a MacroKey
     */
    static bool isMacroKey(const ValueItem &other);

protected:
    QString m_text;
};

KBIBTEXDATA_EXPORT QDebug operator<<(QDebug dbg, const MacroKey &macrokey);


class KBIBTEXDATA_EXPORT PlainText: public ValueItem
{
public:
    PlainText(const PlainText &other);
    explicit PlainText(const QString &text);

    void setText(const QString &text);
    QString text() const;

    void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override;
    bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override;
    bool operator==(const ValueItem &other) const override;

    /**
     * Cheap and fast test if another ValueItem is a PlainText object.
     * @param other another ValueItem object to test
     * @return true if ValueItem is actually a PlainText
     */
    static bool isPlainText(const ValueItem &other);

protected:
    QString m_text;
};

KBIBTEXDATA_EXPORT QDebug operator<<(QDebug dbg, const PlainText &plainText);


class KBIBTEXDATA_EXPORT VerbatimText: public ValueItem
{
public:
    VerbatimText(const VerbatimText &other);
    explicit VerbatimText(const QString &text);

    void setText(const QString &text);
    QString text() const;
    bool hasComment() const;
    void removeComment();
    void setComment(const QString &comment = QString());
    QString comment() const;


    void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode) override;
    bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const override;
    bool operator==(const ValueItem &other) const override;

    /**
     * Cheap and fast test if another ValueItem is a VerbatimText object.
     * @param other another ValueItem object to test
     * @return true if ValueItem is actually a VerbatimText
     */
    static bool isVerbatimText(const ValueItem &other);

protected:
    bool m_hasComment;
    QString m_text, m_comment;

private:
#ifdef HAVE_KF
    struct ColorLabelPair {
        QString hexColor;
        QString label;
    };

    static QList<ColorLabelPair> colorLabelPairs;
    static bool colorLabelPairsInitialized;
#endif // HAVE_KF
};

KBIBTEXDATA_EXPORT QDebug operator<<(QDebug dbg, const VerbatimText &verbatimText);


/**
 * Container class to hold values of BibTeX entry fields and similar value types in BibTeX file.
 * A Value object is built from a list of @see ValueItem objects.
 * @author Thomas Fischer <fischer@unix-ag.uni-kl.de>
 */
class KBIBTEXDATA_EXPORT Value: public QVector<QSharedPointer<ValueItem> >
{
public:
    Value();
    Value(const Value &other);
    Value(Value &&other);
    virtual ~Value();

    void replace(const QString &before, const QString &after, ValueItem::ReplaceMode replaceMode);
    void replace(const QString &before, const QSharedPointer<ValueItem> &after);

    /**
      * Check if this value contains text pattern @p pattern.
      * @param pattern Pattern to check for
      * @param caseSensitive Case sensitivity setting for check
      * @return TRUE if pattern is contained within this value, otherwise FALSE
      */
    bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const;

    bool contains(const ValueItem &item) const;

    Value &operator=(const Value &rhs);
    Value &operator=(Value &&rhs);
    Value &operator<<(const QSharedPointer<ValueItem> &value);
    bool operator==(const Value &rhs) const;
    bool operator!=(const Value &rhs) const;
};

KBIBTEXDATA_EXPORT QDebug operator<<(QDebug dbg, const Value &value);


class KBIBTEXDATA_EXPORT PlainTextValue
{
public:
    enum FormattingOption {
        NoOptions = 0x0,
        BeautifyMonth = 0x1
    };
    Q_DECLARE_FLAGS(FormattingOptions, FormattingOption)
    static QString text(const Value &value, const FormattingOptions formattingOptions = FormattingOption::NoOptions);
    static QString text(const ValueItem &valueItem);
    static QString text(const QSharedPointer<const ValueItem> &valueItem);

private:
    enum class ValueItemType { Other = 0, Person, Keyword};

    static QString text(const ValueItem &valueItem, ValueItemType &vit);
};

Q_DECLARE_OPERATORS_FOR_FLAGS(PlainTextValue::FormattingOptions)

Q_DECLARE_METATYPE(Value)

#endif // KBIBTEX_DATA_VALUE_H
