// SPDX-FileCopyrightText: 2020 Mathis Brüchert <mbb@kaidan.im>
// SPDX-FileCopyrightText: 2020 Melvin Keskin <melvo@olomono.de>
// SPDX-FileCopyrightText: 2021 Jonah Brüchert <jbb@kaidan.im>
// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef SETTINGS_H
#define SETTINGS_H

#include <QMutex>
#include <QObject>
#include <QPoint>
#include <QSettings>
#include <QSize>

#include "Account.h"
#include "Encryption.h"
#include "Kaidan.h"

#include <optional>

constexpr quint16 PORT_AUTODETECT = 0;

/**
 * Manages settings stored in the settings file.
 *
 * All methods are thread-safe.
 */
class Settings : public QObject
{
    Q_OBJECT

    Q_PROPERTY(Kaidan::PasswordVisibility passwordVisibility READ authPasswordVisibility WRITE setAuthPasswordVisibility NOTIFY authPasswordVisibilityChanged)
    Q_PROPERTY(Encryption::Enum encryption READ encryption WRITE setEncryption NOTIFY encryptionChanged)
    Q_PROPERTY(bool contactAdditionQrCodePageExplanationVisible READ contactAdditionQrCodePageExplanationVisible WRITE
                   setContactAdditionQrCodePageExplanationVisible NOTIFY contactAdditionQrCodePageExplanationVisibleChanged)
    Q_PROPERTY(bool keyAuthenticationPageExplanationVisible READ keyAuthenticationPageExplanationVisible WRITE setKeyAuthenticationPageExplanationVisible NOTIFY
                   keyAuthenticationPageExplanationVisibleChanged)
    Q_PROPERTY(QPoint windowPosition READ windowPosition WRITE setWindowPosition NOTIFY windowPositionChanged)
    Q_PROPERTY(QSize windowSize READ windowSize WRITE setWindowSize NOTIFY windowSizeChanged)
    Q_PROPERTY(Account::AutomaticMediaDownloadsRule automaticMediaDownloadsRule READ automaticMediaDownloadsRule WRITE setAutomaticMediaDownloadsRule NOTIFY
                   automaticMediaDownloadsRuleChanged)

public:
    explicit Settings(QObject *parent = nullptr);

    ///
    /// Avoid using this in favour of adding methods here,
    /// but it is useful if you need to manually manage config groups
    ///
    QSettings &raw();

    bool authOnline() const;
    void setAuthOnline(bool online);

    QString authJid() const;
    void setAuthJid(const QString &jid);

    QString authJidResourcePrefix() const;
    void setAuthJidResourcePrefix(const QString &prefix);

    QString authPassword() const;
    void setAuthPassword(const QString &password);

    QXmppCredentials authCredentials() const;
    void setAuthCredentials(const QXmppCredentials &credentials);

    QString authHost() const;
    void setAuthHost(const QString &host);
    void resetAuthHost();

    quint16 authPort() const;
    void setAuthPort(quint16 port);
    void resetAuthPort();
    bool isDefaultAuthPort() const;

    bool authTlsErrorsIgnored() const;
    void setAuthTlsErrorsIgnored(bool enabled);

    QXmppConfiguration::StreamSecurityMode authTlsRequirement() const;
    void setAuthTlsRequirement(QXmppConfiguration::StreamSecurityMode mode);

    Kaidan::PasswordVisibility authPasswordVisibility() const;
    void setAuthPasswordVisibility(Kaidan::PasswordVisibility visibility);

    QUuid userAgentDeviceId() const;
    void setUserAgentDeviceId(QUuid deviceId);

    Encryption::Enum encryption() const;
    void setEncryption(Encryption::Enum encryption);

    bool contactAdditionQrCodePageExplanationVisible() const;
    void setContactAdditionQrCodePageExplanationVisible(bool visible);

    bool keyAuthenticationPageExplanationVisible() const;
    void setKeyAuthenticationPageExplanationVisible(bool visible);

    QStringList favoriteEmojis() const;
    void setFavoriteEmojis(const QStringList &emoji);

    QPoint windowPosition() const;
    void setWindowPosition(const QPoint &windowPosition);

    QSize windowSize() const;
    void setWindowSize(const QSize &windowSize);

    Account::AutomaticMediaDownloadsRule automaticMediaDownloadsRule() const;
    void setAutomaticMediaDownloadsRule(Account::AutomaticMediaDownloadsRule rule);

    void remove(const QStringList &keys);

Q_SIGNALS:
    void authOnlineChanged();
    void authJidChanged();
    void authJidResourcePrefixChanged();
    void authPasswordChanged();
    void authHostChanged();
    void authPortChanged();
    void authIgnoreTlsErrosChanged();
    void authTlsRequirementChanged();
    void authPasswordVisibilityChanged();
    void encryptionChanged();
    void contactAdditionQrCodePageExplanationVisibleChanged();
    void keyAuthenticationPageExplanationVisibleChanged();
    void favoriteEmojisChanged();
    void windowPositionChanged();
    void windowSizeChanged();
    void automaticMediaDownloadsRuleChanged();

private:
    template<typename T>
    T value(const QString &key, const std::optional<T> &defaultValue = {}) const
    {
        QMutexLocker locker(&m_mutex);

        if (defaultValue) {
            return m_settings.value(key, QVariant::fromValue(*defaultValue)).template value<T>();
        }

        return m_settings.value(key).template value<T>();
    }

    template<typename T>
    void setValue(const QString &key, const T &value)
    {
        QMutexLocker locker(&m_mutex);
        if constexpr (!has_enum_type<T>::value && std::is_enum<T>::value) {
            m_settings.setValue(key, static_cast<std::underlying_type_t<T>>(value));
        } else if constexpr (has_enum_type<T>::value) {
            m_settings.setValue(key, static_cast<typename T::Int>(value));
        } else {
            m_settings.setValue(key, QVariant::fromValue(value));
        }
    }

    template<typename T, typename S, typename std::enable_if<int(QtPrivate::FunctionPointer<S>::ArgumentCount) <= 1, T> * = nullptr>
    void setValue(const QString &key, const T &value, S s)
    {
        setValue(key, value);

        if constexpr (int(QtPrivate::FunctionPointer<S>::ArgumentCount) == 0) {
            Q_EMIT(this->*s)();
        } else {
            Q_EMIT(this->*s)(value);
        }
    }

    template<typename S, typename std::enable_if<int(QtPrivate::FunctionPointer<S>::ArgumentCount) == 0, S> * = nullptr>
    void remove(const QString &key, S s)
    {
        QMutexLocker locker(&m_mutex);
        m_settings.remove(key);
        locker.unlock();

        Q_EMIT(this->*s)();
    }

    QSettings m_settings;
    mutable QMutex m_mutex;
};

#endif // SETTINGS_H
