#include "w_PreferencesWindow.hpp"

#include "components/ntp/QvNTPClient.hpp"
#include "components/translations/QvTranslator.hpp"
#include "core/connection/ConnectionIO.hpp"
#include "core/handler/ConfigHandler.hpp"
#include "core/kernel/V2RayKernelInteractions.hpp"
#include "core/settings/SettingsBackend.hpp"
#include "src/plugin-interface/QvPluginInterface.hpp"
#include "ui/common/autolaunch/QvAutoLaunch.hpp"
#include "ui/widgets/styles/StyleManager.hpp"
#include "ui/widgets/widgets/DnsSettingsWidget.hpp"
#include "ui/widgets/widgets/RouteSettingsMatrix.hpp"
#include "utils/HTTPRequestHelper.hpp"
#include "utils/QvHelpers.hpp"

#include <QColorDialog>
#include <QCompleter>
#include <QDesktopServices>
#include <QFileDialog>
#include <QHostInfo>
#include <QInputDialog>
#include <QMessageBox>

using Qv2ray::common::validation::IsIPv4Address;
using Qv2ray::common::validation::IsIPv6Address;
using Qv2ray::common::validation::IsValidDNSServer;
using Qv2ray::common::validation::IsValidIPAddress;

#define LOADINGCHECK                                                                                                                                 \
    if (!finishedLoading)                                                                                                                            \
        return;
#define NEEDRESTART                                                                                                                                  \
    LOADINGCHECK                                                                                                                                     \
    if (finishedLoading)                                                                                                                             \
        NeedRestart = true;

#define SET_PROXY_UI_ENABLE(_enabled)                                                                                                                \
    qvProxyTypeCombo->setEnabled(_enabled);                                                                                                          \
    qvProxyAddressTxt->setEnabled(_enabled);                                                                                                         \
    qvProxyPortCB->setEnabled(_enabled);

#define SET_AUTOSTART_UI_ENABLED(_enabled)                                                                                                           \
    autoStartConnCombo->setEnabled(_enabled);                                                                                                        \
    autoStartSubsCombo->setEnabled(_enabled);

PreferencesWindow::PreferencesWindow(QWidget *parent) : QvDialog("PreferenceWindow", parent), CurrentConfig()
{
    addStateOptions("width", { [&] { return width(); }, [&](QJsonValue val) { resize(val.toInt(), size().height()); } });
    addStateOptions("height", { [&] { return height(); }, [&](QJsonValue val) { resize(size().width(), val.toInt()); } });
    addStateOptions("x", { [&] { return x(); }, [&](QJsonValue val) { move(val.toInt(), y()); } });
    addStateOptions("y", { [&] { return y(); }, [&](QJsonValue val) { move(x(), val.toInt()); } });

    setupUi(this);
    //
    QvMessageBusConnect(PreferencesWindow);
    textBrowser->setHtml(StringFromFile(":/assets/credit.html"));
    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
    configdirLabel->setText(QV2RAY_CONFIG_DIR);

    // We add locales
    auto langs = Qv2rayTranslator->GetAvailableLanguages();
    if (!langs.empty())
    {
        languageComboBox->clear();
        languageComboBox->addItems(langs);
    }
    else
    {
        languageComboBox->setDisabled(true);
        // Since we can't have languages detected. It worths nothing to translate these.
        languageComboBox->setToolTip("Cannot find any language providers.");
    }

    // Set auto start button state
    SetAutoStartButtonsState(GetLaunchAtLoginStatus());
    themeCombo->addItems(StyleManager->AllStyles());
    //
    qvVersion->setText(QV2RAY_VERSION_STRING ":" + QSTRN(QV2RAY_VERSION_BUILD));
    qvBuildInfo->setText(QV2RAY_BUILD_INFO);
    qvBuildExInfo->setText(QV2RAY_BUILD_EXTRA_INFO);
    qvBuildTime->setText(__DATE__ " " __TIME__);
    qvPluginInterfaceVersionLabel->setText(tr("Version: %1").arg(QV2RAY_PLUGIN_INTERFACE_VERSION));
    //
    // Deep copy
    CurrentConfig.loadJson(GlobalConfig.toJson());
    //
    themeCombo->setCurrentText(CurrentConfig.uiConfig.theme);
    darkThemeCB->setChecked(CurrentConfig.uiConfig.useDarkTheme);
    darkTrayCB->setChecked(CurrentConfig.uiConfig.useDarkTrayIcon);
    languageComboBox->setCurrentText(CurrentConfig.uiConfig.language);
    logLevelComboBox->setCurrentIndex(CurrentConfig.logLevel);
    quietModeCB->setChecked(CurrentConfig.uiConfig.quietMode);
    useOldShareLinkFormatCB->setChecked(CurrentConfig.uiConfig.useOldShareLinkFormat);
    //
    //
    listenIPTxt->setText(CurrentConfig.inboundConfig.listenip);
    //
    bool have_http = CurrentConfig.inboundConfig.useHTTP;
    httpGroupBox->setChecked(have_http);
    httpPortLE->setValue(CurrentConfig.inboundConfig.httpSettings.port);
    httpAuthCB->setChecked(CurrentConfig.inboundConfig.httpSettings.useAuth);
    //
    httpAuthUsernameTxt->setEnabled(CurrentConfig.inboundConfig.httpSettings.useAuth);
    httpAuthPasswordTxt->setEnabled(CurrentConfig.inboundConfig.httpSettings.useAuth);
    httpAuthUsernameTxt->setText(CurrentConfig.inboundConfig.httpSettings.account.user);
    httpAuthPasswordTxt->setText(CurrentConfig.inboundConfig.httpSettings.account.pass);
    httpSniffingCB->setChecked(CurrentConfig.inboundConfig.httpSettings.sniffing);
    //
    //
    bool have_socks = CurrentConfig.inboundConfig.useSocks;
    socksGroupBox->setChecked(have_socks);
    socksPortLE->setValue(CurrentConfig.inboundConfig.socksSettings.port);
    //
    socksAuthCB->setChecked(CurrentConfig.inboundConfig.socksSettings.useAuth);
    socksAuthUsernameTxt->setEnabled(CurrentConfig.inboundConfig.socksSettings.useAuth);
    socksAuthPasswordTxt->setEnabled(CurrentConfig.inboundConfig.socksSettings.useAuth);
    socksAuthUsernameTxt->setText(CurrentConfig.inboundConfig.socksSettings.account.user);
    socksAuthPasswordTxt->setText(CurrentConfig.inboundConfig.socksSettings.account.pass);
    // Socks UDP Options
    socksUDPCB->setChecked(CurrentConfig.inboundConfig.socksSettings.enableUDP);
    socksUDPIP->setEnabled(CurrentConfig.inboundConfig.socksSettings.enableUDP);
    socksUDPIP->setText(CurrentConfig.inboundConfig.socksSettings.localIP);
    socksSniffingCB->setChecked(CurrentConfig.inboundConfig.socksSettings.sniffing);
    //
    //
    bool have_tproxy = CurrentConfig.inboundConfig.useTPROXY;
    tproxGroupBox->setChecked(have_tproxy);
    tproxyListenAddr->setText(CurrentConfig.inboundConfig.tProxySettings.tProxyIP);
    tproxyListenV6Addr->setText(CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP);
    tProxyPort->setValue(CurrentConfig.inboundConfig.tProxySettings.port);
    tproxyEnableTCP->setChecked(CurrentConfig.inboundConfig.tProxySettings.hasTCP);
    tproxyEnableUDP->setChecked(CurrentConfig.inboundConfig.tProxySettings.hasUDP);
    tproxyMode->setCurrentText(CurrentConfig.inboundConfig.tProxySettings.mode);
    outboundMark->setValue(CurrentConfig.outboundConfig.mark);
    //
    dnsIntercept->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.dnsIntercept);
    DnsFreedomCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.v2rayFreedomDNS);
    //
    // Kernel Settings
    {
        vCorePathTxt->setText(CurrentConfig.kernelConfig.KernelPath());
        vCoreAssetsPathTxt->setText(CurrentConfig.kernelConfig.AssetsPath());
        enableAPI->setChecked(CurrentConfig.kernelConfig.enableAPI);
        statsPortBox->setValue(CurrentConfig.kernelConfig.statsPort);
        //
        V2RayOutboundStatsCB->setChecked(CurrentConfig.uiConfig.graphConfig.useOutboundStats);
        hasDirectStatisticsCB->setEnabled(CurrentConfig.uiConfig.graphConfig.useOutboundStats);
        hasDirectStatisticsCB->setChecked(CurrentConfig.uiConfig.graphConfig.hasDirectStats);
        //
        pluginKernelV2RayIntegrationCB->setChecked(CurrentConfig.pluginConfig.v2rayIntegration);
        pluginKernelPortAllocateCB->setValue(CurrentConfig.pluginConfig.portAllocationStart);
        pluginKernelPortAllocateCB->setEnabled(CurrentConfig.pluginConfig.v2rayIntegration);
    }
    // Connection Settings
    {
        bypassCNCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.bypassCN);
        bypassBTCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.bypassBT);
        proxyDefaultCb->setChecked(!CurrentConfig.defaultRouteConfig.connectionConfig.enableProxy);
        bypassPrivateCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.bypassLAN);
        //
        localDNSCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.withLocalDNS);
    }
    //
    //
    latencyTCPingRB->setChecked(CurrentConfig.networkConfig.latencyTestingMethod == TCPING);
    latencyICMPingRB->setChecked(CurrentConfig.networkConfig.latencyTestingMethod == ICMPING);
    latencyRealPingTestURLTxt->setText(CurrentConfig.networkConfig.latencyRealPingTestURL);
    //
    {
        qvProxyPortCB->setValue(CurrentConfig.networkConfig.port);
        qvProxyAddressTxt->setText(CurrentConfig.networkConfig.address);
        qvProxyTypeCombo->setCurrentText(CurrentConfig.networkConfig.type);
        qvNetworkUATxt->setEditText(CurrentConfig.networkConfig.userAgent);
        //
        qvProxyNoProxy->setChecked(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_NONE);
        qvProxySystemProxy->setChecked(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_SYSTEM);
        qvProxyCustomProxy->setChecked(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_CUSTOM);
        SET_PROXY_UI_ENABLE(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_CUSTOM)
    }
    //
    //
    //
    // Advanced config.
    {
        setAllowInsecureCB->setChecked(CurrentConfig.advancedConfig.setAllowInsecure);
        setSessionResumptionCB->setChecked(CurrentConfig.advancedConfig.setSessionResumption);
        setTestLatenctCB->setChecked(CurrentConfig.advancedConfig.testLatencyPeriodcally);
        disableSystemRootCB->setChecked(CurrentConfig.advancedConfig.disableSystemRoot);
    }
    //
    {
        dnsSettingsWidget = new DnsSettingsWidget(this);
        dnsSettingsWidget->SetDNSObject(CurrentConfig.defaultRouteConfig.dnsConfig);
        dnsSettingsLayout->addWidget(dnsSettingsWidget);
        //
        routeSettingsWidget = new RouteSettingsMatrixWidget(CurrentConfig.kernelConfig.AssetsPath(), this);
        routeSettingsWidget->SetRouteConfig(CurrentConfig.defaultRouteConfig.routeConfig);
        advRouteSettingsLayout->addWidget(routeSettingsWidget);
    }
    //
#ifdef DISABLE_AUTO_UPDATE
    updateSettingsGroupBox->setEnabled(false);
    updateSettingsGroupBox->setToolTip(tr("Update is disabled by your vendor."));
#endif
    //
    updateChannelCombo->setCurrentIndex(CurrentConfig.updateConfig.updateChannel);
    cancelIgnoreVersionBtn->setEnabled(!CurrentConfig.updateConfig.ignoredVersion.isEmpty());
    ignoredNextVersion->setText(CurrentConfig.updateConfig.ignoredVersion);
    //
    //
    {
        noAutoConnectRB->setChecked(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_NONE);
        lastConnectedRB->setChecked(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_LAST_CONNECTED);
        fixedAutoConnectRB->setChecked(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_FIXED);
        //
        SET_AUTOSTART_UI_ENABLED(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_FIXED);
        //
        if (CurrentConfig.autoStartId.isEmpty())
        {
            CurrentConfig.autoStartId.groupId = DefaultGroupId;
        }
        //
        const auto &autoStartConnId = CurrentConfig.autoStartId.connectionId;
        const auto &autoStartGroupId = CurrentConfig.autoStartId.groupId;
        //
        for (const auto &group : ConnectionManager->AllGroups()) //
            autoStartSubsCombo->addItem(GetDisplayName(group), group.toString());

        autoStartSubsCombo->setCurrentText(GetDisplayName(autoStartGroupId));

        for (const auto &conn : ConnectionManager->Connections(autoStartGroupId))
            autoStartConnCombo->addItem(GetDisplayName(conn), conn.toString());

        autoStartConnCombo->setCurrentText(GetDisplayName(autoStartConnId));
    }
    // FP Settings
    if (CurrentConfig.defaultRouteConfig.forwardProxyConfig.type.trimmed().isEmpty())
    {
        CurrentConfig.defaultRouteConfig.forwardProxyConfig.type = "http";
    }

    fpGroupBox->setChecked(CurrentConfig.defaultRouteConfig.forwardProxyConfig.enableForwardProxy);
    fpUsernameTx->setText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.username);
    fpPasswordTx->setText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.password);
    fpAddressTx->setText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.serverAddress);
    fpTypeCombo->setCurrentText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.type);
    fpPortSB->setValue(CurrentConfig.defaultRouteConfig.forwardProxyConfig.port);
    fpUseAuthCB->setChecked(CurrentConfig.defaultRouteConfig.forwardProxyConfig.useAuth);
    fpUsernameTx->setEnabled(fpUseAuthCB->isChecked());
    fpPasswordTx->setEnabled(fpUseAuthCB->isChecked());
    //
    maxLogLinesSB->setValue(CurrentConfig.uiConfig.maximumLogLines);
    jumpListCountSB->setValue(CurrentConfig.uiConfig.maxJumpListCount);
    //
    setSysProxyCB->setChecked(CurrentConfig.inboundConfig.systemProxySettings.setSystemProxy);
    //
    finishedLoading = true;
}

QvMessageBusSlotImpl(PreferencesWindow)
{
    switch (msg)
    {
        MBShowDefaultImpl;
        MBHideDefaultImpl;
        MBRetranslateDefaultImpl;
        case UPDATE_COLORSCHEME: break;
    }
}

PreferencesWindow::~PreferencesWindow(){};

void PreferencesWindow::on_buttonBox_accepted()
{
    // Note:
    // A signal-slot connection from buttonbox_accpted to QDialog::accepted()
    // has been removed. To prevent closing this Dialog.
    QSet<int> ports;
    auto size = 0;

    if (CurrentConfig.inboundConfig.useHTTP)
    {
        size++;
        ports << CurrentConfig.inboundConfig.httpSettings.port;
    }

    if (CurrentConfig.inboundConfig.useSocks)
    {
        size++;
        ports << CurrentConfig.inboundConfig.socksSettings.port;
    }

    if (CurrentConfig.inboundConfig.useTPROXY)
    {
        size++;
        ports << CurrentConfig.inboundConfig.tProxySettings.port;
    }

    if (!QvCoreApplication->StartupArguments.noAPI)
    {
        size++;
        ports << CurrentConfig.kernelConfig.statsPort;
    }

    if (ports.size() != size)
    {
        // Duplicates detected.
        QvMessageBoxWarn(this, tr("Preferences"), tr("Duplicated port numbers detected, please check the port number settings."));
    }
    else if (!IsValidIPAddress(CurrentConfig.inboundConfig.listenip))
    {
        QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid inbound listening address."));
    }
    else if (!IsIPv4Address(CurrentConfig.inboundConfig.tProxySettings.tProxyIP))
    {
        QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid tproxy listening ivp4 address."));
    }
    else if (CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP != "" && !IsIPv6Address(CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP))
    {
        QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid tproxy listening ipv6 address."));
    }
    else if (!dnsSettingsWidget->CheckIsValidDNS())
    {
        QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid DNS settings."));
    }
    else
    {
        if (CurrentConfig.uiConfig.language != GlobalConfig.uiConfig.language)
        {
            // Install translator
            if (Qv2rayTranslator->InstallTranslation(CurrentConfig.uiConfig.language))
            {
                UIMessageBus.EmitGlobalSignal(QvMBMessage::RETRANSLATE);
                QApplication::processEvents();
            }
        }
        CurrentConfig.defaultRouteConfig.routeConfig = routeSettingsWidget->GetRouteConfig();
        if (!(CurrentConfig.defaultRouteConfig.routeConfig == GlobalConfig.defaultRouteConfig.routeConfig))
        {
            NEEDRESTART
        }
        CurrentConfig.defaultRouteConfig.dnsConfig = dnsSettingsWidget->GetDNSObject();
        if (!(CurrentConfig.defaultRouteConfig.dnsConfig == GlobalConfig.defaultRouteConfig.dnsConfig))
        {
            NEEDRESTART
        }
        //
        //
        if (CurrentConfig.uiConfig.theme != GlobalConfig.uiConfig.theme)
        {
            StyleManager->ApplyStyle(CurrentConfig.uiConfig.theme);
        }
        GlobalConfig.loadJson(CurrentConfig.toJson());
        UIMessageBus.EmitGlobalSignal(QvMBMessage::UPDATE_COLORSCHEME);
        if (NeedRestart && !KernelInstance->CurrentConnection().isEmpty())
        {
            const auto message = tr("You may need to reconnect to apply the settings now.") + NEWLINE +              //
                                 tr("Otherwise they will be applied next time you connect to a server.") + NEWLINE + //
                                 NEWLINE +                                                                           //
                                 tr("Do you want to reconnect now?");
            const auto askResult = QvMessageBoxAsk(this, tr("Reconnect Required"), message);
            if (askResult == Yes)
            {
                ConnectionManager->RestartConnection();
                this->setEnabled(false);
            }
        }
        emit accept();
    }
}

void PreferencesWindow::on_httpAuthCB_stateChanged(int checked)
{
    NEEDRESTART
    bool enabled = checked == Qt::Checked;
    httpAuthUsernameTxt->setEnabled(enabled);
    httpAuthPasswordTxt->setEnabled(enabled);
    CurrentConfig.inboundConfig.httpSettings.useAuth = enabled;
}

void PreferencesWindow::on_socksAuthCB_stateChanged(int checked)
{
    NEEDRESTART
    bool enabled = checked == Qt::Checked;
    socksAuthUsernameTxt->setEnabled(enabled);
    socksAuthPasswordTxt->setEnabled(enabled);
    CurrentConfig.inboundConfig.socksSettings.useAuth = enabled;
}

void PreferencesWindow::on_languageComboBox_currentTextChanged(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.uiConfig.language = arg1;
}

void PreferencesWindow::on_logLevelComboBox_currentIndexChanged(int index)
{
    NEEDRESTART
    CurrentConfig.logLevel = index;
}

void PreferencesWindow::on_vCoreAssetsPathTxt_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.kernelConfig.AssetsPath(arg1);
}

void PreferencesWindow::on_listenIPTxt_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.listenip = arg1;

    if (arg1 == "" || IsValidIPAddress(arg1))
    {
        BLACK(listenIPTxt);
    }
    else
    {
        RED(listenIPTxt);
    }

    // pacAccessPathTxt->setText("http://" + arg1 + ":" +
    // QSTRN(pacPortSB->value()) + "/pac");
}

void PreferencesWindow::on_httpAuthUsernameTxt_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.httpSettings.account.user = arg1;
}

void PreferencesWindow::on_httpAuthPasswordTxt_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.httpSettings.account.pass = arg1;
}

void PreferencesWindow::on_socksAuthUsernameTxt_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.socksSettings.account.user = arg1;
}

void PreferencesWindow::on_socksAuthPasswordTxt_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.socksSettings.account.pass = arg1;
}

void PreferencesWindow::on_latencyRealPingTestURLTxt_textEdited(const QString &arg1)
{
    CurrentConfig.networkConfig.latencyRealPingTestURL = arg1;
}

void PreferencesWindow::on_proxyDefaultCb_stateChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.defaultRouteConfig.connectionConfig.enableProxy = !(arg1 == Qt::Checked);
}

void PreferencesWindow::on_localDNSCb_stateChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.defaultRouteConfig.connectionConfig.withLocalDNS = arg1 == Qt::Checked;
}

void PreferencesWindow::on_selectVAssetBtn_clicked()
{
    NEEDRESTART
    QString dir = QFileDialog::getExistingDirectory(this, tr("Open V2Ray assets folder"), QDir::currentPath());

    if (!dir.isEmpty())
    {
        vCoreAssetsPathTxt->setText(dir);
        on_vCoreAssetsPathTxt_textEdited(dir);
    }
}

void PreferencesWindow::on_selectVCoreBtn_clicked()
{
    QString core = QFileDialog::getOpenFileName(this, tr("Open V2Ray core file"), QDir::currentPath());

    if (!core.isEmpty())
    {
        vCorePathTxt->setText(core);
        on_vCorePathTxt_textEdited(core);
    }
}

void PreferencesWindow::on_vCorePathTxt_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.kernelConfig.KernelPath(arg1);
}

void PreferencesWindow::on_aboutQt_clicked()
{
    QApplication::aboutQt();
}

void PreferencesWindow::on_cancelIgnoreVersionBtn_clicked()
{
    CurrentConfig.updateConfig.ignoredVersion.clear();
    cancelIgnoreVersionBtn->setEnabled(false);
}

void PreferencesWindow::on_bypassCNCb_stateChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.defaultRouteConfig.connectionConfig.bypassCN = arg1 == Qt::Checked;
}

void PreferencesWindow::on_bypassBTCb_stateChanged(int arg1)
{
    NEEDRESTART
    if (arg1 == Qt::Checked)
    {
        QvMessageBoxInfo(this, tr("Note"),
                         tr("To recognize the protocol of a connection, one must enable sniffing option in inbound proxy.") + NEWLINE +
                             tr("tproxy inbound's sniffing is enabled by default."));
    }
    CurrentConfig.defaultRouteConfig.connectionConfig.bypassBT = arg1 == Qt::Checked;
}

void PreferencesWindow::on_statsPortBox_valueChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.kernelConfig.statsPort = arg1;
}

void PreferencesWindow::on_socksPortLE_valueChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.socksSettings.port = arg1;
}

void PreferencesWindow::on_httpPortLE_valueChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.httpSettings.port = arg1;
}

void PreferencesWindow::on_socksUDPCB_stateChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.socksSettings.enableUDP = arg1 == Qt::Checked;
    socksUDPIP->setEnabled(arg1 == Qt::Checked);
}

void PreferencesWindow::on_socksUDPIP_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.socksSettings.localIP = arg1;

    if (IsValidIPAddress(arg1))
    {
        BLACK(socksUDPIP);
    }
    else
    {
        RED(socksUDPIP);
    }
}

void PreferencesWindow::on_themeCombo_currentTextChanged(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.uiConfig.theme = arg1;
}

void PreferencesWindow::on_darkThemeCB_stateChanged(int arg1)
{
    LOADINGCHECK
    CurrentConfig.uiConfig.useDarkTheme = arg1 == Qt::Checked;
}

void PreferencesWindow::on_darkTrayCB_stateChanged(int arg1)
{
    LOADINGCHECK
    CurrentConfig.uiConfig.useDarkTrayIcon = arg1 == Qt::Checked;
}

void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1)
{
    LOADINGCHECK
    NEEDRESTART
    CurrentConfig.inboundConfig.systemProxySettings.setSystemProxy = arg1 == Qt::Checked;
}

void PreferencesWindow::on_autoStartSubsCombo_currentIndexChanged(const QString &arg1)
{
    LOADINGCHECK
    if (arg1.isEmpty())
    {
        CurrentConfig.autoStartId.clear();
        autoStartConnCombo->clear();
    }
    else
    {
        auto list = ConnectionManager->Connections(GroupId(autoStartSubsCombo->currentData().toString()));
        autoStartConnCombo->clear();

        for (const auto &id : list)
        {
            autoStartConnCombo->addItem(GetDisplayName(id), id.toString());
        }
    }
}

void PreferencesWindow::on_autoStartConnCombo_currentIndexChanged(const QString &arg1)
{
    LOADINGCHECK
    if (arg1.isEmpty())
    {
        CurrentConfig.autoStartId.clear();
    }
    else
    {
        CurrentConfig.autoStartId.groupId = GroupId(autoStartSubsCombo->currentData().toString());
        CurrentConfig.autoStartId.connectionId = ConnectionId(autoStartConnCombo->currentData().toString());
    }
}

void PreferencesWindow::on_startWithLoginCB_stateChanged(int arg1)
{
    bool isEnabled = arg1 == Qt::Checked;
    SetLaunchAtLoginStatus(isEnabled);

    if (GetLaunchAtLoginStatus() != isEnabled)
    {
        QvMessageBoxWarn(this, tr("Start with boot"), tr("Failed to set auto start option."));
    }

    SetAutoStartButtonsState(GetLaunchAtLoginStatus());
}

void PreferencesWindow::SetAutoStartButtonsState(bool isAutoStart)
{
    startWithLoginCB->setChecked(isAutoStart);
}

void PreferencesWindow::on_fpTypeCombo_currentIndexChanged(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.defaultRouteConfig.forwardProxyConfig.type = arg1.toLower();
}

void PreferencesWindow::on_fpAddressTx_textEdited(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.defaultRouteConfig.forwardProxyConfig.serverAddress = arg1;

    if (IsValidIPAddress(arg1))
    {
        BLACK(fpAddressTx);
    }
    else
    {
        RED(fpAddressTx);
    }
}

void PreferencesWindow::on_fpUseAuthCB_stateChanged(int arg1)
{
    bool authEnabled = arg1 == Qt::Checked;
    CurrentConfig.defaultRouteConfig.forwardProxyConfig.useAuth = authEnabled;
    fpUsernameTx->setEnabled(authEnabled);
    fpPasswordTx->setEnabled(authEnabled);
}

void PreferencesWindow::on_fpUsernameTx_textEdited(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.defaultRouteConfig.forwardProxyConfig.username = arg1;
}

void PreferencesWindow::on_fpPasswordTx_textEdited(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.defaultRouteConfig.forwardProxyConfig.password = arg1;
}

void PreferencesWindow::on_fpPortSB_valueChanged(int arg1)
{
    LOADINGCHECK
    CurrentConfig.defaultRouteConfig.forwardProxyConfig.port = arg1;
}

void PreferencesWindow::on_checkVCoreSettings_clicked()
{
    auto vcorePath = vCorePathTxt->text();
    auto vAssetsPath = vCoreAssetsPathTxt->text();
    QString result;

    // prevent some bullshit situations.
    if (const auto vCorePathSmallCased = vcorePath.toLower(); vCorePathSmallCased.endsWith("qv2ray") || vCorePathSmallCased.endsWith("qv2ray.exe"))
    {
        const auto strWarnTitle = tr("Watch Out!");
        const auto strWarnContent = //
            tr("You may be about to set V2Ray core incorrectly to Qv2ray itself, which is absolutely not correct.\r\n"
               "This won't trigger a fork bomb, however, since Qv2ray works in singleton mode.\r\n"
               "If your V2Ray core filename happened to be 'qv2ray'-something, you are totally free to ignore this warning.");
        const auto answer = QMessageBox::warning(this, strWarnTitle, strWarnContent,                                       //
                                                 QMessageBox::StandardButton::Abort | QMessageBox::StandardButton::Ignore, //
                                                 QMessageBox::StandardButton::Abort);
        if (answer == QMessageBox::StandardButton::Abort)
            return;
    }
    else if (vCorePathSmallCased.endsWith("v2ctl") || vCorePathSmallCased.endsWith("v2ctl.exe"))
    {
        const auto strWarnTitle = tr("Watch Out!");
        const auto strWarnContent = //
            tr("You may be about to set V2Ray core incorrectly to V2Ray Control executable, which is absolutely not correct.\r\n"
               "The filename of V2Ray core is usually 'v2ray' or 'v2ray.exe'. Make sure to choose it wisely.\r\n"
               "If you insist to proceed, we're not providing with any support.");
        const auto answer = QMessageBox::warning(this, strWarnTitle, strWarnContent,                                       //
                                                 QMessageBox::StandardButton::Abort | QMessageBox::StandardButton::Ignore, //
                                                 QMessageBox::StandardButton::Abort);
        if (answer == QMessageBox::StandardButton::Abort)
            return;
    }

    if (!V2RayKernelInstance::ValidateKernel(vcorePath, vAssetsPath, &result))
    {
        QvMessageBoxWarn(this, tr("V2Ray Core Settings"), result);
    }
    else if (!result.toLower().contains("v2ray"))
    {
        const auto strWarnContent = //
            tr("This does not seem like an output from V2Ray Core.\r\n"
               "If you've been looking for plugin cores, you should change this in plugin settings rather than here.\r\n"
               "Output: \r\n\r\n") +
            result;
        QvMessageBoxWarn(this, tr("'V2Ray Core' Settings"), strWarnContent);
    }
    else
    {
        QvMessageBoxInfo(this, tr("V2Ray Core Settings"),
                         tr("V2Ray path configuration check passed.") + NEWLINE + NEWLINE + tr("Current version of V2Ray is: ") + NEWLINE + result);
    }
}

void PreferencesWindow::on_httpGroupBox_clicked(bool checked)
{
    LOADINGCHECK
    NEEDRESTART
    CurrentConfig.inboundConfig.useHTTP = checked;
}

void PreferencesWindow::on_socksGroupBox_clicked(bool checked)
{
    LOADINGCHECK
    NEEDRESTART
    CurrentConfig.inboundConfig.useSocks = checked;
}

void PreferencesWindow::on_fpGroupBox_clicked(bool checked)
{
    LOADINGCHECK
    NEEDRESTART
    CurrentConfig.defaultRouteConfig.forwardProxyConfig.enableForwardProxy = checked;
}

void PreferencesWindow::on_maxLogLinesSB_valueChanged(int arg1)
{
    LOADINGCHECK
    NEEDRESTART
    CurrentConfig.uiConfig.maximumLogLines = arg1;
}

void PreferencesWindow::on_enableAPI_stateChanged(int arg1)
{
    LOADINGCHECK
    NEEDRESTART

    CurrentConfig.kernelConfig.enableAPI = arg1 == Qt::Checked;
    if (arg1 == Qt::Unchecked)
    {
        const auto msgAPIDisableTitle = tr("Disabling API Subsystem");
        const auto msgAPIDisableMsg = tr("Disabling API subsystem will also disable the statistics function of Qv2ray.") + NEWLINE + //
                                      tr("Speed chart and traffic statistics will be disabled.");
        QvMessageBoxWarn(this, msgAPIDisableTitle, msgAPIDisableMsg);
    }
}

void PreferencesWindow::on_updateChannelCombo_currentIndexChanged(int index)
{
    LOADINGCHECK
    CurrentConfig.updateConfig.updateChannel = (Qv2rayConfig_Update::UpdateChannel) index;
    CurrentConfig.updateConfig.ignoredVersion.clear();
}

void PreferencesWindow::on_pluginKernelV2RayIntegrationCB_stateChanged(int arg1)
{
    LOADINGCHECK
    if (KernelInstance->ActivePluginKernelsCount() > 0)
        NEEDRESTART;
    CurrentConfig.pluginConfig.v2rayIntegration = arg1 == Qt::Checked;
    pluginKernelPortAllocateCB->setEnabled(arg1 == Qt::Checked);
}

void PreferencesWindow::on_pluginKernelPortAllocateCB_valueChanged(int arg1)
{
    LOADINGCHECK
    if (KernelInstance->ActivePluginKernelsCount() > 0)
        NEEDRESTART;
    CurrentConfig.pluginConfig.portAllocationStart = arg1;
}

void PreferencesWindow::on_qvProxyAddressTxt_textEdited(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.networkConfig.address = arg1;
}

void PreferencesWindow::on_qvProxyTypeCombo_currentTextChanged(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.networkConfig.type = arg1.toLower();
}

void PreferencesWindow::on_qvProxyPortCB_valueChanged(int arg1)
{
    LOADINGCHECK
    CurrentConfig.networkConfig.port = arg1;
}

void PreferencesWindow::on_setAllowInsecureCB_stateChanged(int arg1)
{
    LOADINGCHECK
    if (arg1 == Qt::Checked)
    {
        QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("You will lose the advantage of TLS and make your connection under MITM attack."));
    }
    CurrentConfig.advancedConfig.setAllowInsecure = arg1 == Qt::Checked;
}

void PreferencesWindow::on_setTestLatenctCB_stateChanged(int arg1)
{
    LOADINGCHECK
    if (arg1 == Qt::Checked)
    {
        QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("This will (probably) make it easy to fingerprint your connection."));
    }
    CurrentConfig.advancedConfig.testLatencyPeriodcally = arg1 == Qt::Checked;
}

void PreferencesWindow::on_setSessionResumptionCB_stateChanged(int arg1)
{
    LOADINGCHECK
    if (arg1 == Qt::Checked)
    {
        QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("This will make your TLS fingerpring different from common Golang programs."));
    }
    CurrentConfig.advancedConfig.setSessionResumption = arg1 == Qt::Checked;
}

void PreferencesWindow::on_quietModeCB_stateChanged(int arg1)
{
    LOADINGCHECK
    CurrentConfig.uiConfig.quietMode = arg1 == Qt::Checked;
}

void PreferencesWindow::on_tproxGroupBox_toggled(bool arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.useTPROXY = arg1;
}

void PreferencesWindow::on_tProxyPort_valueChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.tProxySettings.port = arg1;
}

void PreferencesWindow::on_tproxyEnableTCP_toggled(bool checked)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.tProxySettings.hasTCP = checked;
}

void PreferencesWindow::on_tproxyEnableUDP_toggled(bool checked)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.tProxySettings.hasUDP = checked;
}

void PreferencesWindow::on_tproxyMode_currentTextChanged(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.tProxySettings.mode = arg1;
}

void PreferencesWindow::on_tproxyListenAddr_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.tProxySettings.tProxyIP = arg1;

    if (arg1 == "" || IsIPv4Address(arg1))
    {
        BLACK(tproxyListenAddr);
    }
    else
    {
        RED(tproxyListenAddr);
    }
}

void PreferencesWindow::on_tproxyListenV6Addr_textEdited(const QString &arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP = arg1;

    if (arg1 == "" || IsIPv6Address(arg1))
    {
        BLACK(tproxyListenV6Addr);
    }
    else
    {
        RED(tproxyListenV6Addr);
    }
}

void PreferencesWindow::on_jumpListCountSB_valueChanged(int arg1)
{
    CurrentConfig.uiConfig.maxJumpListCount = arg1;
}

void PreferencesWindow::on_outboundMark_valueChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.outboundConfig.mark = arg1;
}

void PreferencesWindow::on_dnsIntercept_toggled(bool checked)
{
    NEEDRESTART
    CurrentConfig.defaultRouteConfig.connectionConfig.dnsIntercept = checked;
}

void PreferencesWindow::on_qvProxyCustomProxy_clicked()
{
    CurrentConfig.networkConfig.proxyType = Qv2rayConfig_Network::QVPROXY_CUSTOM;
    SET_PROXY_UI_ENABLE(true);
    qvProxyNoProxy->setChecked(false);
    qvProxySystemProxy->setChecked(false);
    qvProxyCustomProxy->setChecked(true);
}

void PreferencesWindow::on_qvProxySystemProxy_clicked()
{
    CurrentConfig.networkConfig.proxyType = Qv2rayConfig_Network::QVPROXY_SYSTEM;
    SET_PROXY_UI_ENABLE(false);
    qvProxyNoProxy->setChecked(false);
    qvProxyCustomProxy->setChecked(false);
    qvProxySystemProxy->setChecked(true);
}

void PreferencesWindow::on_qvProxyNoProxy_clicked()
{
    CurrentConfig.networkConfig.proxyType = Qv2rayConfig_Network::QVPROXY_NONE;
    SET_PROXY_UI_ENABLE(false);
    qvProxySystemProxy->setChecked(false);
    qvProxyCustomProxy->setChecked(false);
    qvProxyNoProxy->setChecked(true);
}

void PreferencesWindow::on_DnsFreedomCb_stateChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.defaultRouteConfig.connectionConfig.v2rayFreedomDNS = arg1 == Qt::Checked;
}

void PreferencesWindow::on_httpSniffingCB_stateChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.httpSettings.sniffing = arg1 == Qt::Checked;
}

void PreferencesWindow::on_socksSniffingCB_stateChanged(int arg1)
{
    NEEDRESTART
    CurrentConfig.inboundConfig.socksSettings.sniffing = arg1 == Qt::Checked;
}

void PreferencesWindow::on_pushButton_clicked()
{
    const auto ntpTitle = tr("NTP Checker");
    const auto ntpHint = tr("Check date and time from server:");
    const static QStringList ntpServerList = { "cn.pool.ntp.org",         //
                                               "cn.ntp.org.cn",           //
                                               "edu.ntp.org.cn",          //
                                               "time.pool.aliyun.com",    //
                                               "time1.cloud.tencent.com", //
                                               "ntp.neu.edu.cn" };
    bool ok = false;
    QString ntpServer = QInputDialog::getItem(this, ntpTitle, ntpHint, ntpServerList, 0, true, &ok).trimmed();
    if (!ok)
        return;

    auto client = new ntp::NtpClient(this);
    connect(client, &ntp::NtpClient::replyReceived, [&](const QHostAddress &, quint16, const ntp::NtpReply &reply) {
        const int offsetSecTotal = reply.localClockOffset() / 1000;
        if (offsetSecTotal >= 90 || offsetSecTotal <= -90)
        {
            const auto inaccurateWarning = tr("Your time offset is %1 seconds, which is too high.") + NEWLINE + //
                                           tr("Please synchronize your system to use V2Ray.");
            QvMessageBoxWarn(this, tr("Time Inaccurate"), inaccurateWarning.arg(offsetSecTotal));
        }
        else if (offsetSecTotal > 15 || offsetSecTotal < -15)
        {
            const auto smallErrorWarning = tr("Your time offset is %1 seconds, which is a little high.") + NEWLINE + //
                                           tr("V2Ray may still work, but we suggest you synchronize your clock.");
            QvMessageBoxInfo(this, tr("Time Somewhat Inaccurate"), smallErrorWarning.arg(offsetSecTotal));
        }
        else
        {
            const auto accurateInfo = tr("Your time offset is %1 seconds, which looks good.") + NEWLINE + //
                                      tr("V2Ray may not suffer from time inaccuracy.");
            QvMessageBoxInfo(this, tr("Time Accurate"), accurateInfo.arg(offsetSecTotal));
        }
    });

    const auto hostInfo = QHostInfo::fromName(ntpServer);
    if (hostInfo.error() == QHostInfo::NoError)
    {
        client->sendRequest(hostInfo.addresses().first(), 123);
    }
    else
    {
        QvMessageBoxWarn(this, ntpTitle, tr("Failed to lookup server: %1").arg(hostInfo.errorString()));
    }
}

void PreferencesWindow::on_noAutoConnectRB_clicked()
{
    LOADINGCHECK
    CurrentConfig.autoStartBehavior = AUTO_CONNECTION_NONE;
    SET_AUTOSTART_UI_ENABLED(false);
}

void PreferencesWindow::on_lastConnectedRB_clicked()
{
    LOADINGCHECK
    CurrentConfig.autoStartBehavior = AUTO_CONNECTION_LAST_CONNECTED;
    SET_AUTOSTART_UI_ENABLED(false);
}

void PreferencesWindow::on_fixedAutoConnectRB_clicked()
{
    LOADINGCHECK
    CurrentConfig.autoStartBehavior = AUTO_CONNECTION_FIXED;
    SET_AUTOSTART_UI_ENABLED(true);
}

void PreferencesWindow::on_latencyTCPingRB_clicked()
{
    LOADINGCHECK
    CurrentConfig.networkConfig.latencyTestingMethod = TCPING;
    latencyICMPingRB->setChecked(false);
    latencyTCPingRB->setChecked(true);
}

void PreferencesWindow::on_latencyICMPingRB_clicked()
{
    LOADINGCHECK
    CurrentConfig.networkConfig.latencyTestingMethod = ICMPING;
    latencyICMPingRB->setChecked(true);
    latencyTCPingRB->setChecked(false);
}

void PreferencesWindow::on_qvNetworkUATxt_editTextChanged(const QString &arg1)
{
    LOADINGCHECK
    CurrentConfig.networkConfig.userAgent = arg1;
}

void PreferencesWindow::on_V2RayOutboundStatsCB_stateChanged(int arg1)
{
    hasDirectStatisticsCB->setEnabled(arg1 == Qt::Checked);
    LOADINGCHECK
    NEEDRESTART
    CurrentConfig.uiConfig.graphConfig.useOutboundStats = arg1 == Qt::Checked;
}

void PreferencesWindow::on_hasDirectStatisticsCB_stateChanged(int arg1)
{
    NEEDRESTART
    LOADINGCHECK
    CurrentConfig.uiConfig.graphConfig.hasDirectStats = arg1 == Qt::Checked;
}

void PreferencesWindow::on_useOldShareLinkFormatCB_stateChanged(int arg1)
{
    LOADINGCHECK
    CurrentConfig.uiConfig.useOldShareLinkFormat = arg1 == Qt::Checked;
}

void PreferencesWindow::on_bypassPrivateCb_clicked(bool checked)
{
    LOADINGCHECK
    NEEDRESTART
    CurrentConfig.defaultRouteConfig.connectionConfig.bypassLAN = checked;
}

void PreferencesWindow::on_disableSystemRootCB_stateChanged(int arg1)
{
    LOADINGCHECK
    CurrentConfig.advancedConfig.disableSystemRoot = arg1 == Qt::Checked;
}

void PreferencesWindow::on_openConfigDirCB_clicked()
{
    QvCoreApplication->OpenURL(QV2RAY_CONFIG_DIR);
}
