#pragma once

/// @brief Class representing a cheat or a cheat category.
class CheatEntry
{
public:
    /// @brief Dummy empty constructor.
    CheatEntry()
        : _isCheatCategory(false), _flagsPointer(nullptr), _cheatData(nullptr), _cheatDataLength(0) { }

    /// @brief Constructor for a cheat.
    CheatEntry(const char* name, const char* description, u32* flagsPointer, const void* cheatData, u32 cheatDataLength)
        : _name(name), _description(description), _isCheatCategory(false), _flagsPointer(flagsPointer)
        , _cheatData(cheatData), _cheatDataLength(cheatDataLength) { }

    /// @brief Constructor for a category.
    CheatEntry(const char* name, const char* description, bool isMaxOneCheatActive, CheatEntry* subEntries, u32 numberOfSubEntries)
        : _name(name), _description(description), _isCheatCategory(true), _isMaxOneCheatActive(isMaxOneCheatActive)
        , _subEntries(subEntries), _numberOfSubEntries(numberOfSubEntries) { }

    CheatEntry(const CheatEntry& other) = delete;

    CheatEntry(CheatEntry&& other)
        : _isCheatCategory(false), _flagsPointer(nullptr), _cheatData(nullptr), _cheatDataLength(0)
    {
        *this = std::move(other);
    }

    ~CheatEntry()
    {
        if (_isCheatCategory && _subEntries != nullptr)
        {
            delete[] _subEntries;
        }
    }

    CheatEntry& operator=(const CheatEntry& other) = delete;

    CheatEntry& operator=(CheatEntry&& other)
    {
        if (_isCheatCategory && _subEntries != nullptr)
        {
            delete[] _subEntries;
            _subEntries = nullptr;
        }

        _name = other._name;
        other._name = nullptr;
        _description = other._description;
        other._description = nullptr;
        _isCheatCategory = other.IsCheatCategory();
        if (_isCheatCategory)
        {
            _isMaxOneCheatActive = other._isMaxOneCheatActive;
            _subEntries = other._subEntries;
            other._subEntries = nullptr;
            _numberOfSubEntries = other._numberOfSubEntries;
            other._numberOfSubEntries = 0;
        }
        else
        {
            _flagsPointer = other._flagsPointer;
            other._flagsPointer = nullptr;
            _cheatData = other._cheatData;
            other._cheatData = nullptr;
            _cheatDataLength = other._cheatDataLength;
            other._cheatDataLength = 0;
        }

        return *this;
    }

    /// @brief Gets the name of this cheat entry.
    /// @return A pointer to the name of this cheat entry.
    const char* GetName() const
    {
        return _name;
    }

    /// @brief Gets the description of this cheat entry.
    /// @return A pointer to the description of this cheat entry.
    const char* GetDescription() const
    {
        return _description;
    }

    /// @brief When this entry is a cheat, gets a pointer to the cheat data.
    /// @param cheatDataLength The length of the cheat data is returned in this reference.
    /// @return A pointer to the cheat data.
    ///         This pointer is only valid for the lifetime of the \see GameCheats instance this cheat belongs to.
    ///         If this entry is not a cheat, \c nullptr is returned.
    const void* GetCheatData(u32& cheatDataLength) const
    {
        if (_isCheatCategory)
        {
            cheatDataLength = 0;
            return nullptr;
        }
        else
        {
            cheatDataLength = _cheatDataLength;
            return _cheatData;
        }
    }

    /// @brief Gets whether this entry is an active (enabled) cheat or not.
    /// @return \c true when this entry is an active cheat, or \c false otherwise.
    bool GetIsCheatActive() const
    {
        if (_isCheatCategory)
        {
            return false;
        }
        else
        {
            return ((*_flagsPointer >> 24) & 1) == 1;
        }
    }

    /// @brief If this entry is a cheat, sets whether this cheat is active (enabled) or not.
    /// @param isCheatActive \c true to enable this cheat, or \c false to disable this cheat.
    void SetIsCheatActive(bool isCheatActive) const
    {
        if (!_isCheatCategory)
        {
            u32 flags = *_flagsPointer;
            flags &= ~(1 << 24);
            if (isCheatActive)
            {
                flags |= 1 << 24;
            }
            *_flagsPointer = flags;
        }
    }

    /// @brief Indicates if this entry is a cheat category or not.
    /// @return \c true when this entry is a cheat category, or \c false when this entry is a cheat.
    bool IsCheatCategory() const
    {
        return _isCheatCategory;
    }

    /// @brief Indicates if this entry is a cheat category in which only one cheat is allowed to be on at a time or not.
    /// @return \c true when this entry is a cheat category in which only one cheat is allowed
    ///         to be active at a time, or \c false otherwise.
    bool GetIsMaxOneCheatActive() const
    {
        return _isCheatCategory && _isMaxOneCheatActive;
    }

    /// @brief Gets the sub-entries of this entry.
    /// @param numberOfSubEntries The number of sub-entries is returned through this reference.
    /// @return A pointer to an array of entries. This may be \c nullptr when \p numberOfSubEntries is 0.
    const CheatEntry* GetSubEntries(u32& numberOfSubEntries) const
    {
        if (_isCheatCategory)
        {
            numberOfSubEntries = _numberOfSubEntries;
            return _subEntries;
        }
        else
        {
            numberOfSubEntries = 0;
            return nullptr;
        }
    }

private:
    const char* _name = nullptr;
    const char* _description = nullptr;
    bool _isCheatCategory;

    union
    {
        struct
        {
            // For cheat
            u32* _flagsPointer;
            const void* _cheatData;
            u32 _cheatDataLength;
        };
        struct
        {
            // For category
            bool _isMaxOneCheatActive;
            CheatEntry* _subEntries;
            u32 _numberOfSubEntries;
        };
    };
};
