/*
 * Copyright (C) 2014-2024 CZ.NIC
 *
 * 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 3 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 <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#pragma once

#include <QDate>
#include <QDateTime>
#include <QMap>
#include <QObject>
#include <QReadWriteLock>
#include <QString>
#include <QVariant>

#include "src/datovka_shared/io/prefs_db.h"

/*!
 * @brief Encapsulates general preferences.
 */
class Prefs : public QObject {
	Q_OBJECT

private:
	class EntryContent {
	public:
		EntryContent(enum PrefsDb::ValueType type,
		    const QVariant &value)
		    : m_type(type), m_defaultValue(value), m_modified(false)
		{ }

		/* Values with null default value are considered not to have any default value. */
		inline
		bool hasDefault(void) const
		{
			return !m_defaultValue.isNull();
		}

		enum PrefsDb::ValueType m_type; /*!< Entry type. */
		QVariant m_defaultValue; /*!<
		                          * Default value. Null default value
		                          * means that the entry is considered
		                          * not to have a default value.
		                          */
		bool m_modified; /*!< Actual modified entry is located in the database. */
	};

public:
	/*!
	 * @brief Modification status.
	 */
	enum Status {
		STAT_NO_DEFAULT = 1, /*!< There is no default value set. */
		STAT_DEFAULT, /*!< Using the default value. */
		STAT_MODIFIED /*!< Using a different than default value. */
	};

	class Entry {
	public:
		Entry(const QString &name, enum PrefsDb::ValueType type,
		    const QVariant &value)
		    : m_name(name), m_content(type, value)
		{ }

		QString m_name; /*!< Entry name. */
		EntryContent m_content; /*!< Entry content. */
	};

	/*!
	 * @brief Constructor.
	 */
	Prefs(void);

	/*!
	 * @brief Set default value. If there is a modified value in the
	 *     database then the value is marked as modified.
	 *
	 * @note Default values take always precedence before database values
	 *     if types do not match.
	 *
	 * @param[in] entry Entry containing the default value.
	 * @return True on success, false on failure.
	 */
	bool setDefault(const Entry &entry);

	/*!
	 * @brief Set modified values from database. If there is a modified
	 *     value in the database then the default value is marked as
	 *     modified. If there is no default value then it is set to a null
	 *     value and marked as modified.
	 *
	 * @note Default values take always precedence before database values
	 *     if types do not match.
	 *
	 * @param[in] prefsDb Preferences database to be used.
	 * @return True on success, false on failure.
	 */
	bool setDatabase(PrefsDb *prefsDb);

	/*!
	 * @brief Return names matching the regular expression.
	 *
	 * @param[in] re Regular expression.
	 * @return List of matching keys.
	 */
	QStringList names(const QRegularExpression &re) const;

	/*!
	 * @brief Get type of the entry with supplied name.
	 *
	 * @param[in]  name Entry name.
	 * @param[out] type Type of entry.
	 * @return True on success, false on any error.
	 */
	bool type(const QString &name, enum PrefsDb::ValueType &type) const;

	/*!
	 * @brief Get modification status of the entry with supplied name.
	 *
	 * @param[in]  name Entry name.
	 * @param[out] status Modification status.
	 * @return True on success, false on any error or if such value does not exist.
	 */
	bool status(const QString &name, enum Status &status) const;

	/*!
	 * @brief Reset value with given name.
	 *
	 * @note This erases the entry form the database and leaves
	 *     the default value if such is set.
	 */
	void resetVal(const QString &name);

	bool setBoolVal(const QString &name, bool val);
	bool boolVal(const QString &name, bool &val) const;

	bool setIntVal(const QString &name, qint64 val);
	bool intVal(const QString &name, qint64 &val) const;

	bool setFloatVal(const QString &name, double val);
	bool floatVal(const QString &name, double &val) const;

	bool setStrVal(const QString &name, const QString &val);
	bool strVal(const QString &name, QString &val) const;

	bool setColourVal(const QString &name, const QString &val);
	bool colourVal(const QString &name, QString &val) const;

	bool setDateTimeVal(const QString &name, const QDateTime &val);
	bool dateTimeVal(const QString &name, QDateTime &val) const;

	bool setDateVal(const QString &name, const QDate &val);
	bool dateVal(const QString &name, QDate &val) const;

signals:
	/*!
	 * @brief Emitted when a previously non-existent entry is created.
	 *
	 * @param[in] valueType Utilises enum PrefsDb::ValueType values.
	 * @param[in] name Entry name.
	 * @param[in] value Entry value holding the respective type.
	 */
	void entryCreated(int valueType, const QString &name,
	    const QVariant &value);

	/*!
	 * @brief Emitted when an existent entry is removed.
	 *
	 * @param[in] valueType Utilises enum PrefsDb::ValueType values.
	 * @param[in] name Entry name.
	 */
	void entryRemoved(int valueType, const QString &name);

	/*!
	 * @brief Emitted when any set*Val() method is successfully used.
	 *
	 * @param[in] valueType Utilises enum PrefsDb::ValueType values.
	 * @param[in] name Entry name.
	 * @param[in] value Entry value holding the respective type.
	 */
	void entrySet(int valueType, const QString &name,
	    const QVariant &value);

private:
	void resetDefaults(void);

	mutable QReadWriteLock m_lock; /*!< Read/write mutual exclusion. */

	QMap<QString, EntryContent> m_defaults;
	PrefsDb *m_prefsDb; /*!<
	                     * Pointer to preferences database. The object does
	                     * not take ownership of this object.
	                     */
};
