/*
    Copyright (c) 2013 Lukas Tinkl <ltinkl@redhat.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) version 3, or any
    later version accepted by the membership of KDE e.V. (or its
    successor approved by the membership of KDE e.V.), which shall
    act as a proxy defined in Section 6 of version 3 of the license.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "ipv4widget.h"
#include "ui_ipv4.h"
#include "ipv4delegate.h"

#include <QDialog>
#include <QDialogButtonBox>
#include <QStandardItemModel>
#include <QItemSelection>
#include <QNetworkAddressEntry>
#include <QFormLayout>
#include <QSpinBox>
#include <QHostInfo>

#include <KEditListWidget>
#include <KLocalizedString>

quint32 suggestNetmask(quint32 ip)
{
    /*
        A   0       0.0.0.0 <-->127.255.255.255  255.0.0.0 <--->/8
        B   10      128.0.0.0 <>191.255.255.255  255.255.0.0 <->/16
        C   110     192.0.0.0 <>223.255.255.255  255.255.255.0 >/24
        D   1110    224.0.0.0 <>239.255.255.255  not defined <->not defined
        E   1111    240.0.0.0 <>255.255.255.254  not defined <->not defined
    */
    quint32 netmask = 0;

    if (!(ip & 0x80000000)) {
        // test 0 leading bit
        netmask = 0xFF000000;
    }
    else if (!(ip & 0x40000000)) {
        // test 10 leading bits
        netmask = 0xFFFF0000;
    }
    else if (!(ip & 0x20000000)) {
        // test 110 leading bits
        netmask = 0xFFFFFF00;
    }

    return netmask;
}

class IPv4Widget::Private
{
public:
    Private() : model(0,3)
    {
        QStandardItem * headerItem = new QStandardItem(i18nc("Header text for IPv4 address", "Address"));
        model.setHorizontalHeaderItem(0, headerItem);
        headerItem = new QStandardItem(i18nc("Header text for IPv4 netmask", "Netmask"));
        model.setHorizontalHeaderItem(1, headerItem);
        headerItem = new QStandardItem(i18nc("Header text for IPv4 gateway", "Gateway"));
        model.setHorizontalHeaderItem(2, headerItem);
    }
    QStandardItemModel model;
};


IPv4Widget::IPv4Widget(const NetworkManager::Setting::Ptr &setting, QWidget* parent, Qt::WindowFlags f):
    SettingWidget(setting, parent, f),
    m_ui(new Ui::IPv4Widget),
    d(new IPv4Widget::Private())
{
    m_ui->setupUi(this);

    m_ui->tableViewAddresses->setModel(&d->model);
    m_ui->tableViewAddresses->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
    m_ui->tableViewAddresses->horizontalHeader()->setStretchLastSection(true);

    IpV4Delegate *ipDelegate = new IpV4Delegate(this);
    m_ui->tableViewAddresses->setItemDelegateForColumn(0, ipDelegate);
    m_ui->tableViewAddresses->setItemDelegateForColumn(1, ipDelegate);
    m_ui->tableViewAddresses->setItemDelegateForColumn(2, ipDelegate);

    connect(m_ui->btnAdd, &QPushButton::clicked, this, &IPv4Widget::slotAddIPAddress);
    connect(m_ui->btnRemove, &QPushButton::clicked, this, &IPv4Widget::slotRemoveIPAddress);

    connect(m_ui->dnsMorePushButton, &QPushButton::clicked, this, &IPv4Widget::slotDnsServers);
    connect(m_ui->dnsSearchMorePushButton, &QPushButton::clicked, this, &IPv4Widget::slotDnsDomains);

    connect(m_ui->tableViewAddresses->selectionModel(), &QItemSelectionModel::selectionChanged, this, &IPv4Widget::selectionChanged);

    connect(&d->model, &QStandardItemModel::itemChanged, this, &IPv4Widget::tableViewItemChanged);

    if (setting) {
        loadConfig(setting);
    }

    connect(m_ui->method, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &IPv4Widget::slotModeComboChanged);
    slotModeComboChanged(m_ui->method->currentIndex());

    connect(m_ui->btnRoutes, &QPushButton::clicked, this, &IPv4Widget::slotRoutesDialog);
    connect(m_ui->btnAdvanced, &QPushButton::clicked, this, &IPv4Widget::slotAdvancedDialog);

    // Connect for setting check
    watchChangedSetting();

    // Connect for validity check
    connect(m_ui->dns, &KLineEdit::textChanged, this, &IPv4Widget::slotWidgetChanged);
    connect(m_ui->method, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &IPv4Widget::slotWidgetChanged);
    connect(&d->model, &QStandardItemModel::dataChanged, this, &IPv4Widget::slotWidgetChanged);
    connect(&d->model, &QStandardItemModel::rowsRemoved, this, &IPv4Widget::slotWidgetChanged);

    KAcceleratorManager::manage(this);
}

IPv4Widget::~IPv4Widget()
{
    delete d;
    delete m_ui;
}

void IPv4Widget::loadConfig(const NetworkManager::Setting::Ptr &setting)
{
    NetworkManager::Ipv4Setting::Ptr ipv4Setting = setting.staticCast<NetworkManager::Ipv4Setting>();

    // BUG:406118
    // We don't have route-metric in the UI, maybe even won't have for now, but that doesn't mean we
    // want to loose it when it's configured manually in a config file
    m_tmpIpv4Setting.setRouteMetric(ipv4Setting->routeMetric());

    m_tmpIpv4Setting.setRoutes(ipv4Setting->routes());
    m_tmpIpv4Setting.setNeverDefault(ipv4Setting->neverDefault());
    m_tmpIpv4Setting.setIgnoreAutoRoutes(ipv4Setting->ignoreAutoRoutes());

    m_tmpIpv4Setting.setDhcpHostname(ipv4Setting->dhcpHostname());
    m_tmpIpv4Setting.setDhcpSendHostname(ipv4Setting->dhcpSendHostname());
    m_tmpIpv4Setting.setDadTimeout(ipv4Setting->dadTimeout());

    // method
    switch (ipv4Setting->method()) {
        case NetworkManager::Ipv4Setting::Automatic:
            if (ipv4Setting->ignoreAutoDns()) {
                m_ui->method->setCurrentIndex(AutomaticOnlyIP);
            } else {
                m_ui->method->setCurrentIndex(Automatic);
            }
            break;
        case NetworkManager::Ipv4Setting::Manual:
            m_ui->method->setCurrentIndex(Manual);
            break;
        case NetworkManager::Ipv4Setting::LinkLocal:
            m_ui->method->setCurrentIndex(LinkLocal);
            break;
        case NetworkManager::Ipv4Setting::Shared:
            m_ui->method->setCurrentIndex(Shared);
            break;
        case NetworkManager::Ipv4Setting::Disabled:
            m_ui->method->setCurrentIndex(Disabled);
            break;
    }

    // dns
    QStringList tmp;
    for (const QHostAddress &addr : ipv4Setting->dns()) {
        tmp.append(addr.toString());
    }
    m_ui->dns->setText(tmp.join(","));
    m_ui->dnsSearch->setText(ipv4Setting->dnsSearch().join(","));

    m_ui->dhcpClientId->setText(ipv4Setting->dhcpClientId());

    // addresses
    for (const NetworkManager::IpAddress &addr : ipv4Setting->addresses()) {
        QList<QStandardItem *> item;
        item << new QStandardItem(addr.ip().toString())
             << new QStandardItem(addr.netmask().toString())
             << new QStandardItem(addr.gateway().toString());

        d->model.appendRow(item);
    }

    // may-fail
    m_ui->ipv4RequiredCB->setChecked(!ipv4Setting->mayFail());
}

QVariantMap IPv4Widget::setting() const
{
    NetworkManager::Ipv4Setting ipv4Setting;

    // BUG:406118
    // We don't have route-metric in the UI, maybe even won't have for now, but that doesn't mean we
    // want to loose it when it's configured manually in a config file
    ipv4Setting.setRouteMetric(m_tmpIpv4Setting.routeMetric());

    ipv4Setting.setRoutes(m_tmpIpv4Setting.routes());
    ipv4Setting.setNeverDefault(m_tmpIpv4Setting.neverDefault());
    ipv4Setting.setIgnoreAutoRoutes(m_tmpIpv4Setting.ignoreAutoRoutes());

    ipv4Setting.setDhcpHostname(m_tmpIpv4Setting.dhcpHostname());
    ipv4Setting.setDhcpSendHostname(m_tmpIpv4Setting.dhcpSendHostname());
    ipv4Setting.setDadTimeout(m_tmpIpv4Setting.dadTimeout());

    // method
    switch ((MethodIndex)m_ui->method->currentIndex()) {
        case Automatic:
            ipv4Setting.setMethod(NetworkManager::Ipv4Setting::Automatic);
            break;
        case IPv4Widget::AutomaticOnlyIP:
            ipv4Setting.setMethod(NetworkManager::Ipv4Setting::Automatic);
            ipv4Setting.setIgnoreAutoDns(true);
            break;
        case Manual:
            ipv4Setting.setMethod(NetworkManager::Ipv4Setting::Manual);
            break;
        case LinkLocal:
            ipv4Setting.setMethod(NetworkManager::Ipv4Setting::LinkLocal);
            break;
        case Shared:
            ipv4Setting.setMethod(NetworkManager::Ipv4Setting::Shared);
            break;
        case Disabled:
            ipv4Setting.setMethod(NetworkManager::Ipv4Setting::Disabled);
            break;
    }

    // dns
    if (m_ui->dns->isEnabled() && !m_ui->dns->text().isEmpty()) {
        QStringList tmp = m_ui->dns->text().split(',');
        QList<QHostAddress> tmpAddrList;
        for (const QString &str : tmp) {
            QHostAddress addr(str);
            if (!addr.isNull())
                tmpAddrList.append(addr);
        }
        ipv4Setting.setDns(tmpAddrList);
    }
    if (m_ui->dnsSearch->isEnabled() && !m_ui->dnsSearch->text().isEmpty()) {
        ipv4Setting.setDnsSearch(m_ui->dnsSearch->text().split(','));
    }

    // dhcp id
    if (m_ui->dhcpClientId->isEnabled() && !m_ui->dhcpClientId->text().isEmpty()) {
        ipv4Setting.setDhcpClientId(m_ui->dhcpClientId->text());
    }

    // addresses
    if (m_ui->tableViewAddresses->isEnabled()) {
        QList<NetworkManager::IpAddress> list;
        for (int i = 0, rowCount = d->model.rowCount(); i < rowCount; i++) {
            NetworkManager::IpAddress address;
            address.setIp(QHostAddress(d->model.item(i, 0)->text()));
            address.setNetmask(QHostAddress(d->model.item(i, 1)->text()));
            address.setGateway(QHostAddress(d->model.item(i, 2)->text()));
            list << address;
        }
        if (!list.isEmpty()) {
            ipv4Setting.setAddresses(list);
        }
    }

    // may-fail
    if (m_ui->ipv4RequiredCB->isEnabled()) {
        ipv4Setting.setMayFail(!m_ui->ipv4RequiredCB->isChecked());
    }

    return ipv4Setting.toMap();
}

void IPv4Widget::slotModeComboChanged(int index)
{
    if (index == Automatic) {  // Automatic
        m_ui->dnsLabel->setText(i18n("Other DNS Servers:"));
        m_ui->dns->setEnabled(true);
        m_ui->dnsMorePushButton->setEnabled(true);
        m_ui->dnsSearch->setEnabled(true);
        m_ui->dnsSearchMorePushButton->setEnabled(true);
        m_ui->dhcpClientId->setEnabled(true);
        m_ui->ipv4RequiredCB->setEnabled(true);
        m_ui->btnRoutes->setEnabled(true);
        m_ui->tableViewAddresses->setEnabled(false);
        m_ui->btnAdd->setEnabled(false);
        m_ui->btnRemove->setEnabled(false);
    } else if (index == AutomaticOnlyIP) {
        m_ui->dnsLabel->setText(i18n("DNS Servers:"));
        m_ui->dns->setEnabled(true);
        m_ui->dnsMorePushButton->setEnabled(true);
        m_ui->dnsSearch->setEnabled(true);
        m_ui->dnsSearchMorePushButton->setEnabled(true);
        m_ui->dhcpClientId->setEnabled(true);
        m_ui->ipv4RequiredCB->setEnabled(true);
        m_ui->btnRoutes->setEnabled(true);
        m_ui->tableViewAddresses->setEnabled(false);
        m_ui->btnAdd->setEnabled(false);
        m_ui->btnRemove->setEnabled(false);
    } else if (index == Manual) {  // Manual
        m_ui->dnsLabel->setText(i18n("DNS Servers:"));
        m_ui->dns->setEnabled(true);
        m_ui->dnsMorePushButton->setEnabled(true);
        m_ui->dnsSearch->setEnabled(true);
        m_ui->dnsSearchMorePushButton->setEnabled(true);
        m_ui->dhcpClientId->setEnabled(false);
        m_ui->ipv4RequiredCB->setEnabled(true);
        m_ui->btnRoutes->setEnabled(true);
        m_ui->tableViewAddresses->setEnabled(true);
        m_ui->btnAdd->setEnabled(true);
        m_ui->btnRemove->setEnabled(true);
    } else if (index == LinkLocal || index == Shared) {  // Link-local or Shared
        m_ui->dnsLabel->setText(i18n("DNS Servers:"));
        m_ui->dns->setEnabled(false);
        m_ui->dnsMorePushButton->setEnabled(false);
        m_ui->dnsSearch->setEnabled(false);
        m_ui->dnsSearchMorePushButton->setEnabled(false);
        m_ui->dhcpClientId->setEnabled(false);
        m_ui->ipv4RequiredCB->setEnabled(true);
        m_ui->btnRoutes->setEnabled(false);
        m_ui->tableViewAddresses->setEnabled(false);
        m_ui->btnAdd->setEnabled(false);
        m_ui->btnRemove->setEnabled(false);
    } else if (index == Disabled) {  // Disabled
        m_ui->dnsLabel->setText(i18n("DNS Servers:"));
        m_ui->dns->setEnabled(false);
        m_ui->dnsMorePushButton->setEnabled(false);
        m_ui->dnsSearch->setEnabled(false);
        m_ui->dnsSearchMorePushButton->setEnabled(false);
        m_ui->dhcpClientId->setEnabled(false);
        m_ui->ipv4RequiredCB->setEnabled(false);
        m_ui->btnRoutes->setEnabled(false);
        m_ui->tableViewAddresses->setEnabled(false);
        m_ui->btnAdd->setEnabled(false);
        m_ui->btnRemove->setEnabled(false);
    }
}

void IPv4Widget::slotAddIPAddress()
{
    QList<QStandardItem *> item;
    item << new QStandardItem << new QStandardItem << new QStandardItem;
    d->model.appendRow(item);

    const int rowCount = d->model.rowCount();
    if (rowCount > 0) {
        m_ui->tableViewAddresses->selectRow(rowCount - 1);

        QItemSelectionModel * selectionModel = m_ui->tableViewAddresses->selectionModel();
        QModelIndexList list = selectionModel->selectedIndexes();
        if (!list.isEmpty()) {
            // QTableView is configured to select only rows.
            // So, list[0] - IP address.
            m_ui->tableViewAddresses->edit(list[0]);
        }
    }
}

void IPv4Widget::slotRemoveIPAddress()
{
    QItemSelectionModel * selectionModel = m_ui->tableViewAddresses->selectionModel();
    if (selectionModel->hasSelection()) {
        QModelIndexList indexes = selectionModel->selectedIndexes();
        d->model.takeRow(indexes[0].row());
    }
    m_ui->btnRemove->setEnabled(m_ui->tableViewAddresses->selectionModel()->hasSelection());
}

void IPv4Widget::selectionChanged(const QItemSelection & selected)
{
    m_ui->btnRemove->setEnabled(!selected.isEmpty());
}

void IPv4Widget::tableViewItemChanged(QStandardItem *item)
{
    if (item->text().isEmpty()) {
        return;
    }

    const int column = item->column();
    if (column == 0) { // ip
        int row = item->row();

        QStandardItem *netmaskItem = d->model.item(row, column + 1); // netmask
        if (netmaskItem && netmaskItem->text().isEmpty()) {
            QHostAddress addr(item->text());
            const quint32 netmask = suggestNetmask(addr.toIPv4Address());
            if (netmask) {
                QHostAddress v(netmask);
                netmaskItem->setText(v.toString());
            }
        }
    }
}

void IPv4Widget::slotRoutesDialog()
{
    QPointer<IpV4RoutesWidget> dlg = new IpV4RoutesWidget(this);

    dlg->setRoutes(m_tmpIpv4Setting.routes());
    dlg->setNeverDefault(m_tmpIpv4Setting.neverDefault());
    if (m_ui->method->currentIndex() == 2) {  // manual
        dlg->setIgnoreAutoRoutesCheckboxEnabled(false);
    } else {
        dlg->setIgnoreAutoRoutes(m_tmpIpv4Setting.ignoreAutoRoutes());
    }

    connect(dlg.data(), &QDialog::accepted,
            [dlg, this] () {
                m_tmpIpv4Setting.setRoutes(dlg->routes());
                m_tmpIpv4Setting.setNeverDefault(dlg->neverDefault());
                m_tmpIpv4Setting.setIgnoreAutoRoutes(dlg->ignoreautoroutes());
            });
    connect(dlg.data(), &QDialog::finished,
            [dlg] () {
                if (dlg) {
                    dlg->deleteLater();
                }
            });
    dlg->setModal(true);
    dlg->show();
}

void IPv4Widget::slotAdvancedDialog()
{
    auto dlg = new QDialog(this);
    dlg->setAttribute(Qt::WA_DeleteOnClose);

    auto layout = new QFormLayout(dlg);
    dlg->setLayout(layout);

    layout->addRow(new QLabel(i18n("<qt>You can find more information about these values here:<br/><a href='https://developer.gnome.org/NetworkManager/stable/nm-settings.html'>https://developer.gnome.org/NetworkManager/stable/nm-settings.html</a></qt>")));

    auto sendHostname = new QCheckBox;
    sendHostname->setChecked(m_tmpIpv4Setting.dhcpSendHostname());
    layout->addRow(i18n("Send hostname:"), sendHostname);

    auto dhcpHostname = new QLineEdit;
    dhcpHostname->setText(m_tmpIpv4Setting.dhcpHostname());
    dhcpHostname->setPlaceholderText(QHostInfo::localHostName());
    layout->addRow(i18n("DHCP hostname:"), dhcpHostname);

    connect(sendHostname, &QCheckBox::toggled, dhcpHostname, &QLineEdit::setEnabled);

    auto dadTimeout = new QSpinBox;
    dadTimeout->setSpecialValueText(i18n("Default"));
    dadTimeout->setSuffix(i18nc("Milliseconds", " ms"));
    dadTimeout->setMinimum(-1);
    dadTimeout->setValue(m_tmpIpv4Setting.dadTimeout());
    layout->addRow(i18n("DAD timeout:"), dadTimeout);

    QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dlg);
    connect(box, &QDialogButtonBox::accepted, dlg, &QDialog::accept);
    connect(box, &QDialogButtonBox::rejected, dlg, &QDialog::reject);
    layout->addWidget(box);

    connect(dlg, &QDialog::accepted, this,
            [&] () {
                m_tmpIpv4Setting.setDhcpSendHostname(sendHostname->isChecked());
                m_tmpIpv4Setting.setDhcpHostname(dhcpHostname->text());
                m_tmpIpv4Setting.setDadTimeout(dadTimeout->value());
            });

    dlg->setModal(true);
    dlg->show();
}

void IPv4Widget::slotDnsServers()
{
    QPointer<QDialog> dialog = new QDialog(this);
    dialog->setWindowTitle(i18n("Edit DNS servers"));
    dialog->setLayout(new QVBoxLayout);
    QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, dialog);
    connect(buttons, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
    connect(buttons, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
    KEditListWidget * listWidget = new KEditListWidget(dialog);
    listWidget->setItems(m_ui->dns->text().split(',').replaceInStrings(" ", ""));
    listWidget->lineEdit()->setFocus(Qt::OtherFocusReason);
    dialog->layout()->addWidget(listWidget);
    dialog->layout()->addWidget(buttons);
    connect(dialog.data(), &QDialog::accepted,
            [listWidget, this] () {
                QString text = listWidget->items().join(",");
                if (text.endsWith(',')) {
                    text.chop(1);
                }
                m_ui->dns->setText(text);
            });
    connect(dialog.data(), &QDialog::finished,
            [dialog] () {
                if (dialog) {
                    dialog->deleteLater();
                }
            });
    dialog->setModal(true);
    dialog->show();
}

void IPv4Widget::slotDnsDomains()
{
    QPointer<QDialog> dialog = new QDialog(this);
    dialog->setWindowTitle(i18n("Edit DNS search domains"));
    dialog->setLayout(new QVBoxLayout);
    QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, dialog);
    connect(buttons, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
    connect(buttons, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
    KEditListWidget * listWidget = new KEditListWidget(dialog);
    listWidget->setItems(m_ui->dnsSearch->text().split(',').replaceInStrings(" ", ""));
    listWidget->lineEdit()->setFocus(Qt::OtherFocusReason);
    dialog->layout()->addWidget(listWidget);
    dialog->layout()->addWidget(buttons);
    connect(dialog.data(), &QDialog::accepted,
            [listWidget, this] () {
                QString text = listWidget->items().join(",");
                if (text.endsWith(',')) {
                    text.chop(1);
                }
                m_ui->dnsSearch->setText(text);
            });
    connect(dialog.data(), &QDialog::finished,
            [dialog] () {
                if (dialog) {
                    dialog->deleteLater();
                }
            });
    dialog->setModal(true);
    dialog->show();
}

bool IPv4Widget::isValid() const
{
    if (m_ui->method->currentIndex() == Manual) {
        if (!d->model.rowCount()) {
            return false;
        }

        for (int i = 0, rowCount = d->model.rowCount(); i < rowCount; i++) {
            QHostAddress ip = QHostAddress(d->model.item(i, 0)->text());
            QHostAddress netmask = QHostAddress(d->model.item(i, 1)->text());
            QHostAddress gateway = QHostAddress(d->model.item(i, 2)->text());

            if (ip.isNull() || netmask.isNull() || (gateway.isNull() && !d->model.item(i, 2)->text().isEmpty())) {
                return false;
            }
        }
    }

    if (!m_ui->dns->text().isEmpty() && (m_ui->method->currentIndex() == Automatic || m_ui->method->currentIndex() == Manual || m_ui->method->currentIndex() == AutomaticOnlyIP)) {
        const QStringList tmp = m_ui->dns->text().split(',');
        for (const QString &str : tmp) {
            QHostAddress addr(str);
            if (addr.isNull()) {
                return false;
            }
        }
    }

    return true;
}
