/*
 * This file is part of OpenTTD.
 * OpenTTD 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, version 2.
 * OpenTTD 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 OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
 */

/** @file settingentry_gui.h Declarations of classes for handling display of individual configuration settings. */

#ifndef SETTINGENTRY_GUI_H
#define SETTINGENTRY_GUI_H

#include "core/enum_type.hpp"
#include "settings_internal.h"
#include "stringfilter_type.h"

/**
 * Flags for #SettingEntry
 * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
 */
enum class SettingEntryFlag : uint8_t {
	LeftDepressed, ///< Of a numeric setting entry, the left button is depressed
	RightDepressed, ///< Of a numeric setting entry, the right button is depressed
	LastField, ///< This entry is the last one in a (sub-)page
	Filtered, ///< Entry is hidden by the string filter
};
using SettingEntryFlags = EnumBitSet<SettingEntryFlag, uint8_t>; ///< Bitset of the SettingEntryFlag elements.

static constexpr SettingEntryFlags SEF_BUTTONS_MASK = {SettingEntryFlag::LeftDepressed, SettingEntryFlag::RightDepressed}; ///< Mask for button flags

/** How the list of advanced settings is filtered. */
enum RestrictionMode : uint8_t {
	RM_BASIC,                            ///< Display settings associated to the "basic" list.
	RM_ADVANCED,                         ///< Display settings associated to the "advanced" list.
	RM_ALL,                              ///< List all settings regardless of the default/newgame/... values.
	RM_CHANGED_AGAINST_DEFAULT,          ///< Show only settings which are different compared to default values.
	RM_CHANGED_AGAINST_NEW,              ///< Show only settings which are different compared to the user's new game setting values.
	RM_END,                              ///< End for iteration.
};
DECLARE_INCREMENT_DECREMENT_OPERATORS(RestrictionMode)

/** Filter for settings list. */
struct SettingFilter {
	StringFilter string;     ///< Filter string.
	RestrictionMode min_cat; ///< Minimum category needed to display all filtered strings (#RM_BASIC, #RM_ADVANCED, or #RM_ALL).
	bool type_hides;         ///< Whether the type hides filtered strings.
	RestrictionMode mode;    ///< Filter based on category.
	SettingType type;        ///< Filter based on type.
};

/** Data structure describing a single setting in a tab */
struct BaseSettingEntry {
	SettingEntryFlags flags; ///< Flags of the setting entry. @see SettingEntryFlags
	uint8_t level; ///< Nesting level of this setting entry

	BaseSettingEntry() : flags(), level(0) {}
	/** Ensure the destructor of the sub classes are called as well. */
	virtual ~BaseSettingEntry() = default;

	virtual void Init(uint8_t level = 0);
	/** Recursively close all folds of sub-pages */
	virtual void FoldAll() {}
	/** Recursively open all folds of sub-pages */
	virtual void UnFoldAll() {}
	/** Resets all settings to their default values */
	virtual void ResetAll() = 0;

	/**
	 * Set whether this is the last visible entry of the parent node.
	 * @param last_field Value to set
	 */
	void SetLastField(bool last_field) { this->flags.Set(SettingEntryFlag::LastField, last_field); }

	/**
	 * Get the number of rows needed to show this entry.
	 * @return Number of rows.
	 */
	virtual uint Length() const = 0;
	/**
	 * Recursively accumulate the folding state of the tree.
	 * @param[in,out] all_folded Set to false, if one entry is not folded.
	 * @param[in,out] all_unfolded Set to false, if one entry is folded.
	 */
	virtual void GetFoldingState([[maybe_unused]] bool &all_folded, [[maybe_unused]] bool &all_unfolded) const {}
	virtual bool IsVisible(const BaseSettingEntry *item) const;
	virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
	/**
	 * Get the biggest height of the help text(s), if the width is at least \a maxw. Help text gets wrapped if needed.
	 * @param maxw Maximal width of a line help text.
	 * @return Biggest height needed to display any help text of this node (and its descendants).
	 */
	virtual uint GetMaxHelpHeight([[maybe_unused]] int maxw) { return 0; }

	/**
	 * Check whether an entry is hidden due to filters
	 * @return true if hidden.
	 */
	bool IsFiltered() const { return this->flags.Test(SettingEntryFlag::Filtered); }

	/**
	 * Update the filter state.
	 * @param filter Filter
	 * @param force_visible Whether to force all items visible, no matter what
	 * @return true if item remains visible
	 */
	virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;

	virtual uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const;

	static inline Dimension circle_size; ///< Dimension of the circle +/- icon.
	static inline int line_height; ///< Height of a single setting.

protected:
	/**
	 * Function to draw setting value (button + text + current value)
	 * @param settings_ptr Pointer to current values of all settings.
	 * @param left Left-most position in window/panel to start drawing.
	 * @param right Right-most position in window/panel to draw.
	 * @param y Upper-most position in window/panel to start drawing.
	 * @param highlight Whether to highlight the entry.
	 */
	virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
};

/** Standard setting */
struct SettingEntry : BaseSettingEntry {
	const std::string_view name; ///< Name of the setting
	const IntSettingDesc *setting = nullptr; ///< Setting description of the setting

	/**
	 * Create the entry. The view is stored in the static #GetSettingsTree(), so must never be deallocated.
	 * @param name The name of the setting.
	 */
	SettingEntry(std::string_view name) : name(name) {}

	void Init(uint8_t level = 0) override;
	void ResetAll() override;
	uint Length() const override;
	uint GetMaxHelpHeight(int maxw) override;
	bool UpdateFilterState(SettingFilter &filter, bool force_visible) override;

	void SetButtons(SettingEntryFlags new_val);

protected:
	void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const override;

private:
	bool IsVisibleByRestrictionMode(RestrictionMode mode) const;
};

/** Containers for BaseSettingEntry */
struct SettingsContainer {
	using EntryVector = std::vector<BaseSettingEntry*>; ///< Vector of pointers.
	EntryVector entries; ///< Settings on this page

	/**
	 * Add an item to the container.
	 * @param item The item to add.
	 * @return The just added item (same pointer).
	 */
	template <typename T>
	T *Add(T *item)
	{
		this->entries.push_back(item);
		return item;
	}

	void Init(uint8_t level = 0);
	void ResetAll();
	void FoldAll();
	void UnFoldAll();

	uint Length() const;
	void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
	bool IsVisible(const BaseSettingEntry *item) const;
	BaseSettingEntry *FindEntry(uint row, uint *cur_row);
	uint GetMaxHelpHeight(int maxw);

	bool UpdateFilterState(SettingFilter &filter, bool force_visible);

	uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const;
};

/** Data structure describing one page of settings in the settings window. */
struct SettingsPage : BaseSettingEntry, SettingsContainer {
	StringID title;     ///< Title of the sub-page
	bool folded;        ///< Sub-page is folded (not visible except for its title)

	SettingsPage(StringID title);

	void Init(uint8_t level = 0) override;
	void ResetAll() override;
	void FoldAll() override;
	void UnFoldAll() override;

	uint Length() const override;
	void GetFoldingState(bool &all_folded, bool &all_unfolded) const override;
	bool IsVisible(const BaseSettingEntry *item) const override;
	BaseSettingEntry *FindEntry(uint row, uint *cur_row) override;
	uint GetMaxHelpHeight(int maxw) override { return SettingsContainer::GetMaxHelpHeight(maxw); }

	bool UpdateFilterState(SettingFilter &filter, bool force_visible) override;

	uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const override;

protected:
	void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const override;
};

SettingsContainer &GetSettingsTree();
const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd);

#endif /* SETTINGENTRY_GUI_H */

