/***************************************************************************************************
 * Copyright 2023 Qsaker(qsaker@foxmail.com). All rights reserved.
 *
 * The file is encoded using "utf8 with bom", it is a part of QtSwissArmyKnife project.
 *
 * QtSwissArmyKnife is licensed according to the terms in the file LICENCE in the root of the source
 * code directory.
 **************************************************************************************************/
#include "sakmodbusui.h"

#include <QAbstractSocket>
#include <QCheckBox>
#include <QComboBox>
#include <QDateTime>
#include <QGroupBox>
#include <QHostAddress>
#include <QItemDelegate>
#include <QLineEdit>
#include <QMessageBox>
#include <QModbusServer>
#include <QModbusTcpClient>
#include <QModbusTcpServer>
#include <QNetworkAddressEntry>
#include <QNetworkInterface>
#include <QPushButton>
#include <QRegularExpression>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QSpinBox>
#include <QStandardItemModel>
#include <QTextBrowser>
#include <QtEndian>
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
#include <QModbusRtuSerialMaster>
#include <QModbusRtuSerialSlave>
#else
#include <QModbusRtuSerialClient>
#include <QModbusRtuSerialServer>
#endif

#include "sakmodbusfactory.h"
#include "saksettings.h"
#include "ui_sakmodbusui.h"

#define RXCOLOR "green"
#define TXCOLOR "blue"
#define RXFLAG "rx:"
#define TXFLAG "tx:"
#define MAX_HISTORY_INDEX 9

struct SAKModbusUiSettingKeys
{
    const QString device_index = "SAKModbus/deviceIndex";

    const QString port_name = "SAKModbus/portName";
    const QString parity = "SAKModbus/parity";
    const QString baud_rate = "SAKModbus/baudRate";
    const QString data_bits = "SAKModbus/dataBits";
    const QString stop_bits = "SAKModbus/stopBits";
    const QString custom_baud_rate = "SAKModbus/customBaudRate";

    const QString address = "SAKModbus/address";
    const QString port = "SAKModbus/port";
    const QString custom_address = "SAKModbus/customAddress";

    const QString client_timeout = "SAKModbus/clientTimeout";
    const QString client_repeat_time = "SAKModbus/clientRepeatTime";

    const QString server_is_busy = "SAKModbus/serverIsBusy";
    const QString server_just_listen = "SAKModbus/serverJustListen";
    const QString server_address = "SAKModbus/serverAddress";

    const QString function_code = "SAKModbus/functionCode";
    const QString target_address = "SAKModbus/targetAddress";
    const QString start_address = "SAKModbus/startAddress";
    const QString address_number = "SAKModbus/addressNumber";

    const QString send_history = "SAKModbus/sendHistory";
    const QString send_history_index = "SAKModbus/sendHistoryIndex";
    const QString pdu = "pdu";
};

class ReadOnlyDelegate : public QItemDelegate
{
public:
    ReadOnlyDelegate(QWidget *parent = Q_NULLPTR)
        : QItemDelegate(parent)
    {}
    QWidget *createEditor(QWidget *parent,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override
    {
        Q_UNUSED(parent);
        Q_UNUSED(option);
        Q_UNUSED(index);
        return Q_NULLPTR;
    }
};

SAKModbusUi::SAKModbusUi(QWidget *parent)
    : QWidget{parent}
    , ui(new Ui::SAKModbusUi)
    , modbus_device_(Q_NULLPTR)
    , register_model_(Q_NULLPTR)
    , key_ctx_(new SAKModbusUiSettingKeys)
{
    if (!settings_) {
        settings_ = SAKSettings::instance();
    }

    ui->setupUi(this);

    InitComponents();
    InitSettings();
    InitSignals();

    UpdateUiState(false);
    OnDeviceTypeChanged();
    UpdateClientTableView();
    UpdateClientReadWriteButtonState();
}

SAKModbusUi::~SAKModbusUi()
{
    delete ui;
}

void SAKModbusUi::InitComponents()
{
    InitComponentDevices();
    InitComponentAddress();
    InitComponentPortName();
    InitComponnetBaudRate();
    InitComponnetDataBits();
    InitComponnetStopBits();
    InitComponnetParity();
    InitComponentFunctionCode();
    InitComponentRegisterTableView();
    InitComponentInput();
    InitComponentRegisterTabWidget();
}

void SAKModbusUi::InitComponentDevices()
{
    ui->device_list_->addItem(tr("RtuClient"), SAKModbusFactory::kModbusRtuSerialClient);
    ui->device_list_->addItem(tr("RtuServer"), SAKModbusFactory::kModbusRtuSerialServer);
    ui->device_list_->addItem(tr("TcpClient"), SAKModbusFactory::kModbusTcpClient);
    ui->device_list_->addItem(tr("TcpServer"), SAKModbusFactory::kModbusTcpServer);
}

void SAKModbusUi::InitComponentAddress()
{
    ui->address_combo_box->clear();
    QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
    for (QHostAddress &address : addresses) {
        if (address.protocol() == QAbstractSocket::IPv4Protocol) {
            ui->address_combo_box->addItem(address.toString());
        }
    }
}

void SAKModbusUi::InitComponentPortName()
{
    ui->port_name_->clear();
    QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts();
    for (QSerialPortInfo &info : infos) {
        ui->port_name_->addItem(info.portName());
    }
}

void SAKModbusUi::InitComponnetBaudRate()
{
    ui->baud_rate_->clear();
    QList<qint32> bds = QSerialPortInfo::standardBaudRates();
    for (qint32 &bd : bds) {
        ui->baud_rate_->addItem(QString::number(bd), bd);
    }
}

void SAKModbusUi::InitComponnetDataBits()
{
    ui->data_bits_->clear();
    ui->data_bits_->addItem("8", QSerialPort::Data8);
    ui->data_bits_->addItem("7", QSerialPort::Data7);
    ui->data_bits_->addItem("6", QSerialPort::Data6);
    ui->data_bits_->addItem("5", QSerialPort::Data5);
}

void SAKModbusUi::InitComponnetStopBits()
{
    ui->stop_bits_->clear();
    ui->stop_bits_->addItem("1", QSerialPort::OneStop);
#ifdef Q_OS_WIN
    ui->stop_bits_->addItem("1.5", QSerialPort::OneAndHalfStop);
#endif
    ui->stop_bits_->addItem("2", QSerialPort::TwoStop);
}

void SAKModbusUi::InitComponnetParity()
{
    ui->parity_->clear();
    ui->parity_->addItem(tr("NoParity"), QSerialPort::NoParity);
    ui->parity_->addItem(tr("EvenParity"), QSerialPort::EvenParity);
    ui->parity_->addItem(tr("OddParity"), QSerialPort::OddParity);
    ui->parity_->addItem(tr("SpaceParity"), QSerialPort::SpaceParity);
    ui->parity_->addItem(tr("MarkParity"), QSerialPort::MarkParity);
}

void SAKModbusUi::InitComponentFunctionCode()
{
    const QString str0x01 = tr("0x01-ReadCoils");
    const QString str0x02 = tr("0x02-ReadDiscreteInputs");
    const QString str0x03 = tr("0x03-ReadHoldingRegisters");
    const QString str0x04 = tr("0x04-ReadInputRegisters");
    const QString str0x05 = tr("0x05-WriteSingleCoil");
    const QString str0x06 = tr("0x06-WriteSingleRegister");
    const QString str0x0f = tr("0x0f-WriteMultipleCoils");
    const QString str0x10 = tr("0x10-WriteMultipleRegisters");

    int func0x01 = QModbusDataUnit::Coils;
    int func0x02 = QModbusDataUnit::DiscreteInputs;
    int func0x03 = QModbusDataUnit::HoldingRegisters;
    int func0x04 = QModbusDataUnit::InputRegisters;
    int func0x05 = QModbusDataUnit::Coils;
    int func0x06 = QModbusDataUnit::HoldingRegisters;
    int func0x0f = QModbusDataUnit::Coils;
    int func0x10 = QModbusDataUnit::HoldingRegisters;

    ui->function_code_->addItem(str0x01, func0x01);
    ui->function_code_->addItem(str0x02, func0x02);
    ui->function_code_->addItem(str0x03, func0x03);
    ui->function_code_->addItem(str0x04, func0x04);
    ui->function_code_->addItem(str0x05, func0x05);
    ui->function_code_->addItem(str0x06, func0x06);
    ui->function_code_->addItem(str0x0f, func0x0f);
    ui->function_code_->addItem(str0x10, func0x10);
}

void SAKModbusUi::InitComponentRegisterTableView()
{
    QTableView *table_view = CreateTableView(1, ui->client_registers_);
    register_model_ = qobject_cast<QStandardItemModel *>(table_view->model());
}

void SAKModbusUi::InitComponentInput()
{
    QRegularExpression re("([0-9a-fA-F][0-9a-fA-F][ ])*");
    QLineEdit *le = ui->pdu_->lineEdit();
    QRegularExpressionValidator *rev = new QRegularExpressionValidator(re, le);
    le->setValidator(rev);
    le->clear();
}

void SAKModbusUi::InitComponentRegisterTabWidget()
{
    QTabWidget *tab_widget = ui->server_registers_;
    QStringList titles = QStringList() << tr("Coils") << tr("DiscreteInputs")
                                       << tr("InputRegisters") << tr("HoldingRegisters");
    for (QString &title : titles) {
        QTableView *table_view = CreateTableView(UINT16_MAX + 1, Q_NULLPTR);
        table_view->verticalHeader()->hide();
        tab_widget->addTab(table_view, title);
    }
}

void SAKModbusUi::InitSettings()
{
    InitSettingsDevice();
    InitSettingsNetwork();
    InitSettingsSerialPort();
    InitSettingsClient();
    InitSettingsServer();
    InitSettingsClientOperations();
    InitSettingsInput();
}

void SAKModbusUi::InitSettingsDevice()
{
    int deviceIndex = settings_->value(key_ctx_->device_index).toInt();
    if (deviceIndex >= 0 && deviceIndex < ui->device_list_->count()) {
        ui->device_list_->setCurrentIndex(deviceIndex);
    }
}

void SAKModbusUi::InitSettingsNetwork()
{
    QString address = settings_->value(key_ctx_->address).toString();
    ui->address_combo_box->setCurrentText(address);

    QVariant portValiant = settings_->value(key_ctx_->port);
    int port = portValiant.toInt();
    if (portValiant.isNull()) {
        port = 502;
    }
    ui->port_spin_box->setValue(port);
}

void SAKModbusUi::InitSettingsSerialPort()
{
    auto SetComboBoxIndex = [](int index, QComboBox *cb) {
        if (index >= 0 && index < cb->count()) {
            cb->setCurrentIndex(index);
        }
    };

    QString portName = settings_->value(key_ctx_->port_name).toString();
    int index = ui->port_name_->findText(portName);
    SetComboBoxIndex(index, ui->port_name_);

    index = settings_->value(key_ctx_->parity).toInt();
    SetComboBoxIndex(index, ui->parity_);

    QString bd = settings_->value(key_ctx_->baud_rate).toString();
    ui->baud_rate_->setCurrentText(bd);

    index = settings_->value(key_ctx_->data_bits).toInt();
    SetComboBoxIndex(index, ui->data_bits_);

    index = settings_->value(key_ctx_->stop_bits).toInt();
    SetComboBoxIndex(index, ui->stop_bits_);
}

void SAKModbusUi::InitSettingsClient()
{
    int timeout = settings_->value(key_ctx_->client_timeout).toInt();
    ui->timeout_->setValue(timeout < 100 ? 100 : timeout);

    const QString key = key_ctx_->client_repeat_time;
    int repeatTimes = settings_->value(key).toInt();
    ui->repeat_time_->setValue(repeatTimes);
}

void SAKModbusUi::InitSettingsServer()
{
    bool isBusy = settings_->value(key_ctx_->server_is_busy).toBool();
    ui->device_busy_->setChecked(isBusy);

    QString key = key_ctx_->server_just_listen;
    bool just_listen = settings_->value(key).toBool();
    ui->listen_only_mode_->setChecked(just_listen);

    int address = settings_->value(key_ctx_->server_address).toInt();
    ui->server_address->setValue(address);
}

void SAKModbusUi::InitSettingsClientOperations()
{
    int index = settings_->value(key_ctx_->function_code).toInt();
    if (index >= 0 && index < ui->function_code_->count()) {
        ui->function_code_->setCurrentIndex(index);
    }

    int address = settings_->value(key_ctx_->target_address).toInt();
    ui->device_address_->setValue(address);

    int start = settings_->value(key_ctx_->start_address).toInt();
    ui->start_address_->setValue(start);

    int number = settings_->value(key_ctx_->address_number).toInt();
    ui->quantity_->setValue(number);
}

void SAKModbusUi::InitSettingsInput()
{
    ui->pdu_->clear();
    settings_->beginReadArray(key_ctx_->send_history);
    for (int i = 0; i < 10; i++) {
        settings_->setArrayIndex(i);
        QString text = settings_->value(key_ctx_->pdu).toString();
        if (!text.isEmpty()) {
            ui->pdu_->addItem(text);
        }
    }
    settings_->endArray();

    int index = settings_->value(key_ctx_->send_history_index).toInt();
    ui->pdu_->setCurrentIndex(index - 1);
}

void SAKModbusUi::InitSignals()
{
    InitSignalsDevice();
    InitSignalsNetworking();
    InitSignalsSerialPort();
    InitSignalsClient();
    InitSignalsServer();
    InitSignalsClientOperations();
}

void SAKModbusUi::InitSignalsDevice()
{
    connect(ui->device_list_,
            QOverload<int>::of(&QComboBox::currentIndexChanged),
            this,
            &SAKModbusUi::OnDeviceTypeChanged);
    connect(ui->open_button_, &QPushButton::clicked, this, &SAKModbusUi::OnOpenClicked);
    connect(ui->cloese_button_, &QPushButton::clicked, this, &SAKModbusUi::OnCloseClicked);
}

void SAKModbusUi::InitSignalsNetworking()
{
    connect(ui->address_combo_box,
            &QComboBox::currentTextChanged,
            this,
            &SAKModbusUi::OnAddressChanged);
    connect(ui->port_spin_box,
            QOverload<int>::of(&QSpinBox::valueChanged),
            this,
            &SAKModbusUi::OnPortChanged);
}

void SAKModbusUi::InitSignalsSerialPort()
{
    connect(ui->port_name_, &QComboBox::currentTextChanged, this, &SAKModbusUi::OnPortNameChanged);
    connect(ui->parity_,
            QOverload<int>::of(&QComboBox::currentIndexChanged),
            this,
            &SAKModbusUi::OnParityChanged);
    connect(ui->baud_rate_, &QComboBox::currentTextChanged, this, &SAKModbusUi::OnBaudRateChanged);
    connect(ui->data_bits_,
            QOverload<int>::of(&QComboBox::currentIndexChanged),
            this,
            &SAKModbusUi::OnDataBitsChanged);
    connect(ui->stop_bits_,
            QOverload<int>::of(&QComboBox::currentIndexChanged),
            this,
            &SAKModbusUi::OnStopBistChanged);
}

void SAKModbusUi::InitSignalsClient()
{
    connect(ui->timeout_,
            QOverload<int>::of(&QSpinBox::valueChanged),
            this,
            &SAKModbusUi::OnClientTimeoutChanged);
    connect(ui->repeat_time_,
            QOverload<int>::of(&QSpinBox::valueChanged),
            this,
            &SAKModbusUi::OnClientRepeatTimeChanged);
}

void SAKModbusUi::InitSignalsServer()
{
    connect(ui->device_busy_, &QCheckBox::clicked, this, &SAKModbusUi::OnServerIsBusyChanged);
    connect(ui->listen_only_mode_,
            &QCheckBox::clicked,
            this,
            &SAKModbusUi::OnServerJustListenChanged);
    connect(ui->server_address,
            QOverload<int>::of(&QSpinBox::valueChanged),
            this,
            &SAKModbusUi::OnServerAddressChanged);
}

void SAKModbusUi::InitSignalsClientOperations()
{
    connect(ui->function_code_,
            &QComboBox::currentTextChanged,
            this,
            &SAKModbusUi::OnFunctionCodeChanged);
    connect(ui->device_address_,
            QOverload<int>::of(&QSpinBox::valueChanged),
            this,
            &SAKModbusUi::OnTargetAddressChanged);
    connect(ui->start_address_,
            QOverload<int>::of(&QSpinBox::valueChanged),
            this,
            &SAKModbusUi::OnStartAddressChanged);
    connect(ui->quantity_,
            QOverload<int>::of(&QSpinBox::valueChanged),
            this,
            &SAKModbusUi::OnAddressNumberChanged);
    connect(ui->read_, &QPushButton::clicked, this, &SAKModbusUi::OnReadClicked);
    connect(ui->write_, &QPushButton::clicked, this, &SAKModbusUi::OnWriteClicked);
    connect(ui->send_, &QPushButton::clicked, this, &SAKModbusUi::OnSendClicked);
}

void SAKModbusUi::OnErrorOccurred()
{
    outputMessage(modbus_device_->errorString(), true, "", "error");
    if (modbus_device_->error() == QModbusDevice::ConnectionError) {
        const QString msg = modbus_device_->errorString();
        OnCloseClicked();
        QMessageBox::warning(this, tr("Error occured"), msg);
    }
}

void SAKModbusUi::OnDeviceTypeChanged()
{
    int type = ui->device_list_->currentData().toInt();
    bool isSerial = (type == SAKModbusFactory::kModbusRtuSerialClient
                     || type == SAKModbusFactory::kModbusRtuSerialServer);
    bool isClient = (type == SAKModbusFactory::kModbusRtuSerialClient
                     || type == SAKModbusFactory::kModbusTcpClient);

    // Hide ui component first then show ui component,
    // or the window will be resize to the max size of default.
    if (isSerial) {
        ui->networkGroupBox->setHidden(true);
        ui->serialPortGroupBox->setHidden(false);
    } else {
        ui->serialPortGroupBox->setHidden(true);
        ui->networkGroupBox->setHidden(false);
    }

    if (isClient) {
        ui->console_->setVisible(true);
        ui->serverGroupBox->setHidden(true);
        ui->registersGroupBox->setHidden(true);
        ui->clientOperationsGroupBox->setHidden(false);
        ui->clientCustomCommandGroupBox->setHidden(false);
        ui->clientRegistersGroupBox->setHidden(false);
    } else {
        ui->console_->setVisible(false);
        ui->clientCustomCommandGroupBox->setHidden(true);
        ui->clientRegistersGroupBox->setHidden(true);
        ui->clientOperationsGroupBox->setHidden(true);
        ui->serverGroupBox->setHidden(false);
        ui->registersGroupBox->setHidden(false);
    }

    settings_->setValue(key_ctx_->device_index, type);
}

void SAKModbusUi::OnCloseClicked()
{
    SAKModbusFactory::Instance()->DeleteModbusDevuce(&modbus_device_);
    UpdateUiState(false);
}

void SAKModbusUi::OnOpenClicked()
{
    ui->open_button_->setEnabled(false);
    SAKModbusFactory::Instance()->DeleteModbusDevuce(&modbus_device_);

    modbus_device_ = CreateModbusDevice();

    if (SAKModbusFactory::Instance()->IsServerDevice(modbus_device_)) {
        if (!UpdateServerMap(modbus_device_)) {
            ui->open_button_->setEnabled(true);
            qCWarning(kLoggingCategory) << "Can not reset server map!";
            return;
        }

        UpdateServerParameters();

        QModbusServer *server = qobject_cast<QModbusServer *>(modbus_device_);
        UpdateServerRegistersData();
        connect(server, &QModbusServer::dataWritten, this, &SAKModbusUi::OnDateWritten);
    } else if (SAKModbusFactory::Instance()->IsClientDevice(modbus_device_)) {
        UpdateClientParameters();
    } else {
        ui->open_button_->setEnabled(true);
        qCWarning(kLoggingCategory) << "Can not create modbus devices!";
        return;
    }

    connect(modbus_device_, &QModbusDevice::errorOccurred, this, &SAKModbusUi::OnErrorOccurred);
    SAKModbusFactory *factory = SAKModbusFactory::Instance();
    bool connected = factory->ConnectDeivce(modbus_device_);
    if (!connected) {
        QString errStr = modbus_device_->errorString();
        QString info = tr("Can not open device: %1."
                          "Please check the parameters and try again!")
                           .arg(errStr);
        QMessageBox::warning(this, tr("Can not open device"), info);
    }

    UpdateUiState(connected);
}

void SAKModbusUi::OnAddressChanged()
{
    settings_->setValue(key_ctx_->address, ui->address_combo_box->currentText());
}

void SAKModbusUi::OnPortChanged()
{
    settings_->setValue(key_ctx_->port, ui->port_spin_box->value());
}

void SAKModbusUi::OnPortNameChanged()
{
    settings_->setValue(key_ctx_->port_name, ui->port_name_->currentText());
}

void SAKModbusUi::OnParityChanged()
{
    settings_->setValue(key_ctx_->parity, ui->parity_->currentIndex());
}

void SAKModbusUi::OnBaudRateChanged()
{
    settings_->setValue(key_ctx_->baud_rate, ui->baud_rate_->currentText());
}

void SAKModbusUi::OnDataBitsChanged()
{
    settings_->setValue(key_ctx_->data_bits, ui->data_bits_->currentIndex());
}

void SAKModbusUi::OnStopBistChanged()
{
    settings_->setValue(key_ctx_->stop_bits, ui->stop_bits_->currentIndex());
}

void SAKModbusUi::OnInvokeRefresh()
{
    InitComponentPortName();
}

void SAKModbusUi::OnClientTimeoutChanged()
{
    settings_->setValue(key_ctx_->client_timeout, ui->timeout_->value());
    UpdateClientParameters();
}

void SAKModbusUi::OnClientRepeatTimeChanged()
{
    settings_->setValue(key_ctx_->client_repeat_time, ui->repeat_time_->value());
    UpdateClientParameters();
}

void SAKModbusUi::OnServerIsBusyChanged()
{
    settings_->setValue(key_ctx_->server_is_busy, ui->device_busy_->isChecked());
    UpdateServerParameters();
}

void SAKModbusUi::OnServerJustListenChanged()
{
    settings_->setValue(key_ctx_->server_just_listen, ui->listen_only_mode_->isChecked());
    UpdateServerParameters();
}

void SAKModbusUi::OnServerAddressChanged()
{
    settings_->setValue(key_ctx_->server_address, ui->server_address->value());
    UpdateServerParameters();
}

void SAKModbusUi::OnFunctionCodeChanged()
{
    settings_->setValue(key_ctx_->function_code, ui->function_code_->currentIndex());
    UpdateClientReadWriteButtonState();
}

void SAKModbusUi::OnTargetAddressChanged()
{
    settings_->setValue(key_ctx_->target_address, ui->device_address_->value());
}

void SAKModbusUi::OnStartAddressChanged()
{
    settings_->setValue(key_ctx_->start_address, ui->start_address_->value());
    UpdateClientTableView();
}

void SAKModbusUi::OnAddressNumberChanged()
{
    settings_->setValue(key_ctx_->address_number, ui->quantity_->value());
    UpdateClientTableView();
}

void SAKModbusUi::OnReadClicked()
{
    if (!IsConnected()) {
        return;
    }

    if (!SAKModbusFactory::Instance()->IsClientDevice(modbus_device_)) {
        return;
    }

    int register_type = ui->function_code_->currentData().toInt();
    quint16 start_address = ui->start_address_->value();
    quint16 quantity = ui->quantity_->value();
    quint16 server_address = ui->device_address_->value();
    quint8 function_code = GetClientFunctionCode();

    qCInfo(kLoggingCategory) << "[SendReadRequest]"
                             << "register type:" << register_type
                             << "start address:" << start_address << "quantity:" << quantity
                             << "server address:" << server_address;

    typedef QModbusDataUnit::RegisterType RegisterType;
    RegisterType type = static_cast<RegisterType>(register_type);
    QModbusDataUnit data_unit(type, start_address, quantity);
    QModbusClient *client = qobject_cast<QModbusClient *>(modbus_device_);
    QModbusReply *reply = client->sendReadRequest(data_unit, server_address);
    if (!SAKModbusFactory::Instance()->IsValidModbusReply(reply)) {
        return;
    }

    QString info = ui->function_code_->currentText();
    outputMessage(info, false, TXCOLOR, TXFLAG);
    connect(reply, &QModbusReply::finished, this, [=]() {
        OutputModbusReply(reply, function_code);

        if (reply->error() == QModbusDevice::NoError) {
            UpdateClientTableViewData(reply->result().values());
            reply->deleteLater();
        }
    });
}

void SAKModbusUi::OnWriteClicked()
{
    if (!IsConnected()) {
        return;
    }

    int registerType = ui->function_code_->currentData().toInt();
    int start_address = ui->start_address_->value();
    int server_address = ui->device_address_->value();
    quint8 function_code = GetClientFunctionCode();
    QList<quint16> values = GetClientRegisterValue();
    SAKModbusFactory *factory = SAKModbusFactory::Instance();
    QModbusReply *reply = factory->SendWriteRequest(modbus_device_,
                                                    registerType,
                                                    start_address,
                                                    values,
                                                    server_address);
    if (SAKModbusFactory::Instance()->IsValidModbusReply(reply)) {
        connect(reply, &QModbusReply::finished, this, [=]() {
            OutputModbusReply(reply, function_code);
            reply->deleteLater();
        });

        QString info = ui->function_code_->currentText();
        outputMessage(info, false, TXCOLOR, TXFLAG);
    }
}

void SAKModbusUi::OnSendClicked()
{
    if (!IsConnected()) {
        return;
    }

    quint8 server_address = ui->device_address_->value();
    QByteArray pdu = GetClientPdu();
    QByteArray data = pdu;
    if (!data.isEmpty()) {
        data = data.remove(0, 1);
    }

    int function_code = int(QModbusDataUnit::Invalid);
    if (!pdu.isEmpty()) {
        function_code = pdu.at(0);
    }
    SAKModbusFactory *factory = SAKModbusFactory::Instance();
    QModbusReply *reply = factory->SendRawRequest(modbus_device_,
                                                  server_address,
                                                  function_code,
                                                  data);

    qCWarning(kLoggingCategory) << "Send raw request:"
                                << "server address:" << server_address
                                << "function code:" << function_code
                                << "data:" << QString(pdu.toHex(' '));
    if (SAKModbusFactory::Instance()->IsValidModbusReply(reply)) {
        connect(reply, &QModbusReply::finished, this, [=]() {
            OutputModbusReply(reply, function_code);
            reply->deleteLater();
        });

        QString info = "pdu(No server address, no crc):";
        info += QString(pdu.toHex(' '));
        outputMessage(info, false, TXCOLOR, TXFLAG);
    }

    // Update settings data.
    int index = settings_->value(key_ctx_->send_history_index).toInt();
    bool ret = WriteSettingsArray(key_ctx_->send_history,
                                  key_ctx_->pdu,
                                  QString(pdu.toHex(' ')),
                                  index,
                                  MAX_HISTORY_INDEX);
    if (!ret) {
        return;
    }

    if (index > ui->pdu_->count()) {
        ui->pdu_->addItem(QString(pdu.toHex(' ')));
    } else {
        ui->pdu_->insertItem(index, QString(pdu.toHex(' ')));
    }

    index = index + 1 > MAX_HISTORY_INDEX ? 0 : index + 1;
    settings_->setValue(key_ctx_->send_history_index, index);
}

void SAKModbusUi::OnDateWritten(QModbusDataUnit::RegisterType table, int address, int size)
{
    qCInfo(kLoggingCategory) << "Data written:"
                             << "table:" << table << "start address:" << address << "size:" << size;
    QTableView *tv = GetTableView(table);
    QStandardItemModel *model = qobject_cast<QStandardItemModel *>(tv->model());
    QModbusServer *server = qobject_cast<QModbusServer *>(modbus_device_);
    QList<quint16> data = SAKModbusFactory::Instance()->GetServerData(server, table, address, size);
    size = qMin<int>(data.count(), size);
    for (int i = 0; i < size; i++) {
        int row = address + i;
        int base = 16;
        int width = base == 2 ? 16 : (base == 10 ? 5 : 4);
        int value = data.at(i);
        QString cooked_str = QString::number(value, base);
        cooked_str = cooked_str.rightJustified(width, '0', true);
        QStandardItem *item = model->item(row, 1);
        if (item) {
            item->setData(cooked_str, Qt::DisplayRole);
            item->setTextAlignment(Qt::AlignCenter);
        }
    }

    tv->viewport()->update();
}

void SAKModbusUi::OnItemChanged(QStandardItem *item)
{
    if (!item) {
        return;
    }

    if (item->column() != 1) {
        return;
    }

    if (SAKModbusFactory::Instance()->IsServerDevice(modbus_device_)) {
        int address = item->row();
        int current_index = ui->server_registers_->currentIndex();
        QModbusDataUnit::RegisterType table = QModbusDataUnit::Invalid;
        if (current_index == 0) {
            table = QModbusDataUnit::Coils;
        } else if (current_index == 1) {
            table = QModbusDataUnit::DiscreteInputs;
        } else if (current_index == 2) {
            table = QModbusDataUnit::InputRegisters;
        } else if (current_index == 3) {
            table = QModbusDataUnit::HoldingRegisters;
        } else {
            Q_ASSERT_X(false, __FUNCTION__, "Unknow table type!");
            return;
        }

        quint16 value = item->text().toInt(Q_NULLPTR, 16);
        SAKModbusFactory::Instance()->SetServerData(modbus_device_, table, address, value);
    }
}

QModbusDevice *SAKModbusUi::CreateModbusDevice()
{
    QModbusDevice *device = Q_NULLPTR;
    int type = ui->device_list_->currentData().toInt();
    if (SAKModbusFactory::Instance()->IsRtuSerialDeviceType(type)) {
        QString port_name = ui->port_name_->currentText();
        int parity = ui->parity_->currentData().toInt();
        int baud_rate = ui->baud_rate_->currentData().toInt();
        int data_bits = ui->data_bits_->currentData().toInt();
        int stop_bits = ui->stop_bits_->currentData().toInt();
        SAKModbusFactory *factory = SAKModbusFactory::Instance();
        device = factory->CreateRtuSerialDevice(type,
                                                port_name,
                                                parity,
                                                baud_rate,
                                                data_bits,
                                                stop_bits);
    } else if (SAKModbusFactory::Instance()->IsTcpDeviceType(type)) {
        QString address = ui->address_combo_box->currentText();
        int port = ui->port_spin_box->value();
        SAKModbusFactory *factory = SAKModbusFactory::Instance();
        device = factory->CreateTcpDevice(type, address, port);
    } else {
        Q_ASSERT_X(false, __FUNCTION__, "Unknown device type");
    }

    return device;
}

QTableView *SAKModbusUi::CreateTableView(int row_count, QTableView *table_view)
{
    if (!table_view) {
        table_view = new QTableView(this);
    }

    QHeaderView *hv = table_view->horizontalHeader();
    QStandardItemModel *model = new QStandardItemModel(table_view);
    QStringList labels = QStringList() << tr("Address") << tr("Value") << tr("Description");
    model->setHorizontalHeaderLabels(labels);
    model->setColumnCount(3);
    model->setRowCount(row_count);
    table_view->setModel(model);
    table_view->verticalHeader()->hide();
    table_view->setItemDelegateForColumn(0, new ReadOnlyDelegate(table_view));
    UpdateClientTableViewAddress(table_view, 0);
    hv->setStretchLastSection(true);

    // Set the default value to 0.
    model->blockSignals(true);
    for (int row = 0; row < row_count; row++) {
        QModelIndex index = model->index(row, 1);
        QMap<int, QVariant> roles;
        roles.insert(Qt::DisplayRole, "0000");
        model->setItemData(index, roles);

        QStandardItem *item = model->item(row, 1);
        item->setTextAlignment(Qt::AlignCenter);
    }
    model->blockSignals(false);

    connect(model, &QStandardItemModel::itemChanged, this, &SAKModbusUi::OnItemChanged);

    return table_view;
}

void SAKModbusUi::UpdateUiState(bool connected)
{
    ui->device_list_->setEnabled(!connected);
    ui->cloese_button_->setEnabled(connected);
    ui->open_button_->setEnabled(!connected);
    ui->networkGroupBox->setEnabled(!connected);
    ui->serialPortGroupBox->setEnabled(!connected);
#if 0
    ui->clientGroupBox->setEnabled(!connected);
    ui->serverGroupBox->setEnabled(!connected);
#endif
}

void SAKModbusUi::UpdateClientTableView()
{
    int number = ui->quantity_->value();
    int rowCount = register_model_->rowCount();
    if (number > rowCount) {
        register_model_->insertRows(rowCount, number - rowCount);
    } else if (number < rowCount) {
        register_model_->removeRows(number, rowCount - number);
    }

    // Update address.
    int start_address = ui->start_address_->value();
    UpdateClientTableViewAddress(ui->client_registers_, start_address);
}

void SAKModbusUi::UpdateClientTableViewData(const QList<quint16> &values)
{
    for (int row = 0; row < values.count(); row++) {
        int value = values.at(row);
        QModelIndex index = register_model_->index(row, 1);
        QMap<int, QVariant> roles;
        QString str = QString("%1").arg(QString::number(value, 16), 4, '0');
        roles.insert(Qt::DisplayRole, str);
        register_model_->setItemData(index, roles);
        QStandardItem *item = register_model_->item(row, 1);
        if (item) {
            item->setTextAlignment(Qt::AlignCenter);
        }
    }

    // Refresh the view, or the new value will not be show.
    ui->client_registers_->viewport()->update();
}

void SAKModbusUi::UpdateClientReadWriteButtonState()
{
    QStringList list = ui->function_code_->currentText().split('-');
    int code = list.length() ? list.first().toInt(Q_NULLPTR, 16) : 0;
    bool is_reading_operation = false;
    if (code == 0x01 || code == 0x02 || code == 0x03 || code == 0x04) {
        is_reading_operation = true;
    }

    if (code == 0x05 || code == 0x06) {
        ui->quantity_->setValue(1);
    }

    ui->read_->setEnabled(is_reading_operation);
    ui->write_->setEnabled(!is_reading_operation);
}

void SAKModbusUi::UpdateClientParameters()
{
    int timeout = ui->timeout_->value();
    int repeat_time = ui->repeat_time_->value();
    SAKModbusFactory::Instance()->SetClientDeviceParameters(modbus_device_, timeout, repeat_time);
}

void SAKModbusUi::UpdateClientTableViewAddress(QTableView *view, int start_address)
{
    auto *model = qobject_cast<QStandardItemModel *>(view->model());
    for (int row = 0; row < model->rowCount(); row++) {
        int address = row + start_address;
        QString text = QString("%1").arg(QString::number(address), 5, '0');
        QModelIndex index = model->index(row, 0);
        QMap<int, QVariant> roles;
        roles.insert(Qt::DisplayRole, text);
        model->setItemData(index, roles);

        QStandardItem *item = model->item(row, 0);
        if (item) {
            item->setTextAlignment(Qt::AlignCenter);
        }
    }
}

void SAKModbusUi::UpdateServerParameters()
{
    bool device_busy = ui->device_busy_->isChecked();
    bool listen_only_mode = ui->listen_only_mode_->isChecked();
    int address = ui->server_address->value();
    SAKModbusFactory::Instance()->SetServerDeviceParameters(modbus_device_,
                                                            address,
                                                            device_busy,
                                                            listen_only_mode);
}

bool SAKModbusUi::UpdateServerMap(QModbusDevice *server)
{
    if (server && qobject_cast<QModbusServer *>(server)) {
        QVector<quint16> values(UINT16_MAX + 1, 0);
        QModbusDataUnit dataUnit(QModbusDataUnit::Coils, 0, values);

        QModbusDataUnitMap map;
        map.insert(QModbusDataUnit::Coils, dataUnit);
        map.insert(QModbusDataUnit::DiscreteInputs, dataUnit);
        map.insert(QModbusDataUnit::HoldingRegisters, dataUnit);
        map.insert(QModbusDataUnit::InputRegisters, dataUnit);

        QModbusServer *cooked_server = qobject_cast<QModbusServer *>(server);
        cooked_server->blockSignals(true);
        bool is_ok = cooked_server->setMap(map);
        cooked_server->blockSignals(false);
        return is_ok;
    }

    return false;
}

void SAKModbusUi::UpdateServerRegistersData()
{
    for (int i = 0; i < 4; i++) {
        QWidget *widget = ui->server_registers_->widget(i);
        QTableView *table_view = qobject_cast<QTableView *>(widget);
        auto *model = qobject_cast<QStandardItemModel *>(table_view->model());
        int type = QModbusDataUnit::Invalid;
        if (i == 0) {
            type = QModbusDataUnit::Coils;
        } else if (i == 1) {
            type = QModbusDataUnit::DiscreteInputs;
        } else if (i == 2) {
            type = QModbusDataUnit::InputRegisters;
        } else if (i == 3) {
            type = QModbusDataUnit::HoldingRegisters;
        } else {
            qCWarning(kLoggingCategory) << "Unknown register type.";
            continue;
        }

        for (int row = 0; row < model->rowCount(); row++) {
            QStandardItem *item = model->item(row, 1);
            quint16 value = item ? item->text().toInt(Q_NULLPTR, 16) : 0;
            auto table = static_cast<QModbusDataUnit::RegisterType>(type);
            SAKModbusFactory::Instance()->SetServerData(modbus_device_, table, row, value, false);
        }
    }
}

quint8 SAKModbusUi::GetClientFunctionCode()
{
    QString txt = ui->function_code_->currentText();
    QStringList list = txt.split('-', Qt::SkipEmptyParts);
    if (list.length()) {
        return list.first().toInt(Q_NULLPTR, 16);
    }

    return 0;
}

QList<quint16> SAKModbusUi::GetClientRegisterValue()
{
    QList<quint16> values;
    for (int row = 0; row < register_model_->rowCount(); row++) {
        QStandardItem *item = register_model_->item(row, 1);
        if (item) {
            QString text = item->text();
            values.append(text.toInt(Q_NULLPTR, 16));
        } else {
            values.append(0);
        }
    }

    return values;
}

QByteArray SAKModbusUi::GetClientPdu()
{
    QString text = ui->pdu_->currentText();
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
    QStringList valueList = text.split(' ', QString::SkipEmptyParts);
#else
    QStringList valueList = text.split(' ', Qt::SkipEmptyParts);
#endif
    QByteArray data;
    for (QString &value : valueList) {
        data.append(char(value.toInt(Q_NULLPTR, 16)));
    }

    return data;
}

QTableView *SAKModbusUi::GetTableView(QModbusDataUnit::RegisterType table)
{
    QWidget *tv = Q_NULLPTR;
    if (table == QModbusDataUnit::Coils) {
        tv = ui->server_registers_->widget(0);
    } else if (table == QModbusDataUnit::DiscreteInputs) {
        tv = ui->server_registers_->widget(1);
    } else if (table == QModbusDataUnit::InputRegisters) {
        tv = ui->server_registers_->widget(2);
    } else if (table == QModbusDataUnit::HoldingRegisters) {
        tv = ui->server_registers_->widget(3);
    } else {
        tv = ui->server_registers_->widget(3);
        qCWarning(kLoggingCategory) << "Get table view failed: unknow register type!";
    }

    return qobject_cast<QTableView *>(tv);
}

QList<quint16> SAKModbusUi::GetTableValues(QTableView *table_view, int row, int count)
{
    if (!table_view) {
        qCWarning(kLoggingCategory) << "Table view can not be null!";
        return QList<quint16>();
    }

    auto *model = qobject_cast<QStandardItemModel *>(table_view->model());
    if (!model) {
        qCWarning(kLoggingCategory) << "Model can not be null!";
        return QList<quint16>();
    }

    QList<quint16> values;
    for (int i = row; i < count; i++) {
        QStandardItem *item = model->item(i, 1);
        if (item) {
            QString text = item->text();
            values.append(text.toInt(Q_NULLPTR, 16));
        } else {
            values.append(0);
        }
    }

    return values;
}

void SAKModbusUi::OutputModbusReply(QModbusReply *reply, int function_code)
{
    if (reply->error() != QModbusDevice::NoError) {
        outputMessage(reply->errorString(), true, "", "error");
        QMessageBox::warning(this, tr("Error occured"), reply->errorString());
        return;
    }

    int server_address = reply->serverAddress();
    if (reply->type() == QModbusReply::ReplyType::Raw) {
        QByteArray data = reply->rawResult().data();
        QString info = QString("(from address: %1, "
                               "function code: %2), "
                               "data unit: %3")
                           .arg(server_address)
                           .arg(function_code)
                           .arg(QString::fromLatin1(data.toHex(' ')));
        outputMessage(info, false, RXCOLOR, RXFLAG);
    } else if (reply->type() == QModbusReply::ReplyType::Common) {
        QString info = ui->function_code_->currentText();
        outputMessage(info, false, RXCOLOR, RXFLAG);
    }
}

void SAKModbusUi::outputMessage(const QString &msg,
                                bool isError,
                                const QString &color,
                                const QString &flag)
{
    QString cookedMsg = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
    cookedMsg = QString("<font color=silver>%1 </font>").arg(cookedMsg);

    QString cookedColor = color;
    if (cookedColor.isEmpty()) {
        if (isError) {
            cookedColor = "red";
        } else {
            cookedColor = "black";
        }
    }

    cookedMsg += flag.isEmpty() ? ""
                                : QString("<font color=%1>[%2]</font> ").arg(cookedColor, flag);
    cookedMsg += msg;
    ui->text_browser_->append(cookedMsg);
}

bool SAKModbusUi::IsConnected()
{
    if (SAKModbusFactory::Instance()->IsConnected(modbus_device_)) {
        return true;
    }

    QMessageBox::warning(this,
                         tr("Device is not Ready"),
                         tr("The modbus device is not ready, "
                            "please check settings and try again later!"));
    return false;
}

bool SAKModbusUi::WriteSettingsArray(
    const QString &group, const QString &key, const QString &value, int index, int max_index)
{
    settings_->beginWriteArray(group);
    for (int i = 0; i < max_index; i++) {
        settings_->setArrayIndex(i);
        QString v = settings_->value(key).toString();
        if (v == value) {
            settings_->endArray();
            return false;
        }
    }

    settings_->setArrayIndex(index);
    settings_->setValue(key, value);
    settings_->endArray();
    return true;
}
