/*
    SPDX-FileCopyrightText: 2007 Nicolas Roffet <nicolas-kde@roffet.com>
    SPDX-FileCopyrightText: 2007 Pino Toscano <toscano.pino@tiscali.it>
    SPDX-FileCopyrightText: 2011-2012 Stefan Majewsky <majewsky@gmx.net>

    SPDX-License-Identifier: LGPL-2.0-only
*/

#ifndef KGDIFFICULTY_H
#define KGDIFFICULTY_H

// own
#include <libkdegames_export.h>
// Qt
#include <QMetaType>
#include <QObject>
// Std
#include <memory>

/**
 * @class KgDifficultyLevel kgdifficulty.h <KgDifficultyLevel>
 * @see KgDifficulty
 */
class KDEGAMES_EXPORT KgDifficultyLevel : public QObject
{
    Q_OBJECT
    Q_DISABLE_COPY(KgDifficultyLevel)
    Q_PROPERTY(bool default READ isDefault)
    Q_PROPERTY(int hardness READ hardness)
    Q_PROPERTY(QByteArray key READ key)
    Q_PROPERTY(QString title READ title)
    Q_PROPERTY(StandardLevel standardLevel READ standardLevel)

public:
    enum StandardLevel {
        Custom = -1, ///< standardLevel() returns this for custom levels.
        RidiculouslyEasy = 10,
        VeryEasy = 20,
        Easy = 30,
        Medium = 40,
        Hard = 50,
        VeryHard = 60,
        ExtremelyHard = 70,
        Impossible = 80
    };
    Q_ENUM(StandardLevel)

    /// Refer to the getters' documentation for details on the params.
    KgDifficultyLevel(int hardness, const QByteArray &key, const QString &title, bool isDefault = false);
    explicit KgDifficultyLevel(StandardLevel level, bool isDefault = false);
    ~KgDifficultyLevel() override;

    /// @return whether this level is the default level when no selection has
    ///        been stored (e.g. on first startup)
    bool isDefault() const;
    /// @return a numeric key which is used to sort the levels by difficulty
    ///        (smaller values mean easier levels)
    /// @note For standard levels, this equals the numeric value of the level
    ///      in the StandardLevel enumeration.
    int hardness() const;
    /// @return a @b non-localized key for this level
    QByteArray key() const;
    /// @return a @b localized title for this level
    QString title() const;
    /// @return the standard level which was used to create this level, or
    ///        KgDifficultyLevel::Custom for custom levels
    StandardLevel standardLevel() const;

private:
    std::unique_ptr<class KgDifficultyLevelPrivate> const d;
};

/**
 * @class KgDifficulty kgdifficulty.h <KgDifficulty>
 * @brief KgDifficulty manages difficulty levels of a game in a standard way.
 *
 * The difficulty can be a type of game (like in KMines: small or big field) or
 * the AI skills (like in Bovo: how deep should the computer search to find the
 * best move) or a combination of both of them. On the user point of view, it's
 * not really different: either is the game easy or hard to play.
 *
 * KgDifficulty contains a list of KgDifficultyLevel instances. One of
 * these levels is selected; this selection will be recorded when the
 * application is closed. A set of standard difficulty levels is provided by
 * KgDifficultyLevel, but custom levels can be defined at the same time.
 */
class KDEGAMES_EXPORT KgDifficulty : public QObject
{
    Q_OBJECT
    Q_DISABLE_COPY(KgDifficulty)
    // Use currentLevel in game logic and selectedLevel in level selection UI.
    Q_PROPERTY(const KgDifficultyLevel *currentLevel READ currentLevel WRITE select NOTIFY currentLevelChanged)
    Q_PROPERTY(const KgDifficultyLevel *selectedLevel READ currentLevel WRITE select NOTIFY selectedLevelChanged)
    Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged)
    Q_PROPERTY(bool gameRunning READ isGameRunning WRITE setGameRunning NOTIFY gameRunningChanged)

public:
    explicit KgDifficulty(QObject *parent = nullptr);
    /// Destroys this instance and all DifficultyLevel instances in it.
    ~KgDifficulty() override;

    /// Adds a difficulty level to this instance. This will not affect the
    /// currentLevel() if there is one.
    void addLevel(KgDifficultyLevel *level);
    /// A shortcut for addLevel(new KgDifficultyLevel(@a level)).
    void addStandardLevel(KgDifficultyLevel::StandardLevel level, bool isDefault = false);
    /// This convenience method adds a range of standard levels to this
    /// instance (including the boundaries). For example:
    /// @code
    /// difficulty.addStandardLevelRange(
    ///     KgDifficultyLevel::Easy,
    ///     KgDifficultyLevel::VeryHard
    ///);
    /// @endcode
    /// This adds the levels "Easy", "Medium", "Hard" and "Very hard".
    void addStandardLevelRange(KgDifficultyLevel::StandardLevel from, KgDifficultyLevel::StandardLevel to);
    /// @overload
    /// This overload allows to specify a @a defaultLevel.
    void addStandardLevelRange(KgDifficultyLevel::StandardLevel from, KgDifficultyLevel::StandardLevel to, KgDifficultyLevel::StandardLevel defaultLevel);

    /// @return a list of all difficulty levels, sorted by hardness
    QList<const KgDifficultyLevel *> levels() const;
    /// @return the current difficulty level
    ///
    /// After the KgDifficulty object has been created, the current
    /// difficulty level will not be determined until this method is called
    /// for the first time. This allows the application developer to set up
    /// the difficulty levels before KgDifficulty retrieves the last
    /// selected level from the configuration file.
    const KgDifficultyLevel *currentLevel() const;

    /// @return whether the difficulty level selection may be edited
    bool isEditable() const;
    /// @return whether a running game has been marked @see setGameRunning
    bool isGameRunning() const;
    /// Set whether the difficulty level selection may be edited. The
    /// default value is true.
    void setEditable(bool editable);
    /// KgDifficulty has optional protection against changing the
    /// difficulty level while a game is running. If setGameRunning(true) has
    /// been called, and select() is called to select a new difficulty level,
    /// the user will be asked for confirmation.
    void setGameRunning(bool running);
Q_SIGNALS:
    /// Emitted when the editability changes. @see setEditable
    void editableChanged(bool editable);
    /// Emitted when a running game has been marked or unmarked. @see setGameRunning
    void gameRunningChanged(bool gameRunning);
    /// Emitted when a new difficulty level has been selected.
    void currentLevelChanged(const KgDifficultyLevel *level);
    /// Emitted after every call to select(), even when the user has rejected
    /// the change. This is useful to reset a difficulty level selection UI
    /// after a rejected change.
    void selectedLevelChanged(const KgDifficultyLevel *level);
public Q_SLOTS:
    /// Select a new difficulty level. The given level must already have been
    /// added to this instance.
    /// @note This does nothing if isEditable() is false. If a game is
    /// running (according to setGameRunning()), the user will be asked for
    /// confirmation before the new difficulty level is selected.
    void select(const KgDifficultyLevel *level);

private:
    std::unique_ptr<class KgDifficultyPrivate> const d;
};

Q_DECLARE_METATYPE(const KgDifficultyLevel *)

/**
 * @namespace Kg
 *
 * The namespace for methods to access the KgDifficulty singleton.
 *
 * @see KgDifficulty
 */
namespace Kg
{
/// @return a singleton instance of KgDifficulty
KDEGAMES_EXPORT KgDifficulty *difficulty();
/// A shortcut for Kg::difficulty()->currentLevel()->standardLevel().
KDEGAMES_EXPORT KgDifficultyLevel::StandardLevel difficultyLevel();
}

class KXmlGuiWindow;

/**
 * @namespace KgDifficultyGUI
 *
 * The namespace for methods to integrate KgDifficulty into the UI.
 *
 * @see KgDifficulty
 */
// TODO KDE5: move this into a separate QtWidgets support library
namespace KgDifficultyGUI
{
/// Install standard GUI components for the manipulation of the given
/// KgDifficulty instance in the given @a window.
///
/// Without a second parameter, the Kg::difficulty() singleton is used.
KDEGAMES_EXPORT void init(KXmlGuiWindow *window, KgDifficulty *difficulty = nullptr);
}

#endif // KGDIFFICULTY_H
