#include "CratePluginCEU.h"
#include "LtrCrateInfo.h"
#include "LtrdConnection.h"
#include "QLbootDialog.h"

#include <QMessageBox>
#include <QFileDialog>
#include <QSettings>
#include <QTimer>

#include "ltrceu_defs.h"
#include "Crates/ConfigDialog/CrateConfigDialog.h"




static INT f_crate_open(LtrManagerEnv *env, TLTR *hcrate, LtrdConnection *&con, LtrCrateInfo *&crate) {
    con = env->currentLtrdCon();
    crate = env->currentCrate();

    LTR_Init(hcrate);
    INT err = crate->openConnection(hcrate);
    if (err != LTR_OK) {
        QMessageBox::critical(env->mainWidget(), CratePluginCEU::tr("Error"),
                              CratePluginCEU::tr("Cannot open crate connection") + ": " +
                              LtrdConnection::getErrorString(err));
    }
    return err;
}



CratePluginCEU::CratePluginCEU(LtrManagerEnv *env) : ActionPlugin(env) {
    env->registerActionPlugin(LtrManagerEnv::ActionTypeCrateReset, this);
    env->registerActionPlugin(LtrManagerEnv::ActionTypeCrateSyncCtl, this);
    env->registerActionPlugin(LtrManagerEnv::ActionTypeCrateBoot, this);
    env->registerActionPlugin(LtrManagerEnv::ActionTypeCrateConfig, this);
}

bool CratePluginCEU::actionEnabled(LtrManagerEnv *env, LtrManagerEnv::ActionType type) {
    bool en = false;
    LtrCrateInfo* curCrate = env->currentCrate();
    if (curCrate) {
        if ((type == LtrManagerEnv::ActionTypeCrateBoot)
            || (type == LtrManagerEnv::ActionTypeCrateReset)) {

            en = (curCrate->type()==LTR_CRATE_TYPE_LTR_CU_1) ||
                 (curCrate->type()==LTR_CRATE_TYPE_LTR_CEU_1);
        } else if (type == LtrManagerEnv::ActionTypeCrateConfig) {
            en = curCrate->type()==LTR_CRATE_TYPE_LTR_CEU_1;
        }
    }
    return en;
}

void CratePluginCEU::actionActivate(LtrManagerEnv *env, LtrManagerEnv::ActionType type) {
    if (type == LtrManagerEnv::ActionTypeCrateBoot) {
        updateFirmware(env);
    } else if (type == LtrManagerEnv::ActionTypeCrateReset) {
        reset(env);
    } else if (type == LtrManagerEnv::ActionTypeCrateConfig) {
        config(env);
    }
}

void CratePluginCEU::updateFirmware(LtrManagerEnv *env) {
    QSettings set;
    QString dir;
    set.beginGroup("LTR-CEU");
    /* используем сохранненную начальную директорию для открытия файла */
    dir = set.value("firmwareDir").toString();

    QString filename = QFileDialog::getOpenFileName(env->mainWidget(), tr("Choice of firmware file"),
                                                    dir, tr("Firmware files") + "(*.bin);;" + tr("All files") +  "(*.*)");
    if (!filename.isEmpty()) {
        /* если файл выбрали, то сохраняем его директорию, чтобы в следующий раз
           открыть диалог на ней */
        set.setValue("firmwareDir", QFileInfo(filename).absolutePath());


        QStringList args;
        TLTR hcrate;
        int iface=LTR_CRATE_IFACE_UNKNOWN;
        LtrdConnection *con;
        LtrCrateInfo   *crate;
        quint32 ipv4_addr {0};
        INT err = f_crate_open(env, &hcrate, con, crate);
        if (err == LTR_OK) {
            QString serial = crate->serial();
            if (!serial.isEmpty()) {
                args.append("--serial=" + serial);
            }
            /* проверяем интерфейс крейта для определения нужных параметров для загрузчика */
            iface = crate->crateInterface();
            if (iface==LTR_CRATE_IFACE_USB) {
                args.append("usb");
            } else if (iface==LTR_CRATE_IFACE_TCPIP) {
                QString addr;
                /* ищем запись, соответствующую ip-адресу крейта */
                const QList<TLTR_CRATE_IP_ENTRY> ipList {crate->ltrd()->ipList()};
                foreach (TLTR_CRATE_IP_ENTRY entry, ipList) {
                    if (QString::fromLatin1(entry.serial_number) == serial) {
                        ipv4_addr = entry.ip_addr;
                        addr = 	QHostAddress(ipv4_addr).toString();
                        break;
                    }
                }

                args.append("--tftp-ip-addr=" + addr);
                args.append("tftp");
            }

            /* передаем команду на переход в режим загрузчика */
            quint32 cmd = LTR021M_CMD_GO_BOOTLDR;
            err = LTR_CratePutArray(&hcrate, IOADDR_SYS_COMMAND, (BYTE*)&cmd, sizeof(cmd) );
            if (err != LTR_OK) {
                QMessageBox::critical(env->mainWidget(), tr("Error"), tr("Cannot switch crate to bootloader mode: ") + LtrdConnection::getErrorString(err));
            }
            LTR_Close(&hcrate);
            /* если подключение по ip то отключаемся от крейта на время обновления
             * прошивки, чтобы не вызывать ошибку подключения */
            if (ipv4_addr != 0) {
                con->ipDisconnect(ipv4_addr);
            }
        }

        if (err == LTR_OK) {
            args << "-r" << "--recovery" << "--hash" << "--con-time=5000" << "--devname=LTR021M";
            args.append(filename);

            /* для Ethtenet нужно подождать, чтобы быть уверенным, что устройство
             * успело перейти в загрузчик до того, как начнем передавать файл по
             * TFTP. В противном случае приложение может послать
             * Destination unreachable из-за того, что не прослушивает этот порт */
            if (iface==LTR_CRATE_IFACE_TCPIP) {
                LTRAPI_SLEEP_MS(1000);
            }

            QLbootDialog dlg(env->mainWidget(), args);
            dlg.exec();


            if (ipv4_addr != 0) {
                /* если подключение было по ip, то пробуем восстановить соединение после
                 * завершения обновления */
                con->ipConnect(ipv4_addr);
            }
        }
    }
}

void CratePluginCEU::reset(LtrManagerEnv *env) {
    TLTR hcrate;
    LtrdConnection *con;
    LtrCrateInfo   *crate;
    INT err = f_crate_open(env, &hcrate, con, crate);
    if (err == LTR_OK) {
        quint32 cmd = LTR021M_CMD_REBOOT;
        LTR_CratePutArray(&hcrate, IOADDR_SYS_COMMAND, (BYTE*)&cmd, sizeof(cmd) );
        if (err != LTR_OK) {
            QMessageBox::critical(env->mainWidget(), tr("Error"), tr("Cannot reset crate") +
                                  ": " + LtrdConnection::getErrorString(err));
        }
        LTR_Close(&hcrate);

        if (err == LTR_OK) {
            ipReconnect(env, con, crate);
        }
    }
}

void CratePluginCEU::config(LtrManagerEnv *env) {
    TLTR hcrate;
    LtrdConnection *con;
    LtrCrateInfo   *crate;
    INT err = f_crate_open(env, &hcrate, con, crate);
    if (err == LTR_OK) {
        ltr021m_conf_fact_t fact_cfg;
        ltr021m_conf_user_t usr_cfg;
        err = LTR_CrateGetArray(&hcrate, IOADDR_CONF_FACT, (BYTE*)&fact_cfg, sizeof(fact_cfg));
        if (err != LTR_OK) {
            QMessageBox::critical(env->mainWidget(), tr("Error"),
                                  tr("Cannot read factory configuration") + ": " +
                                  LtrdConnection::getErrorString(err));
        } else {
            err = LTR_CrateGetArray(&hcrate, IOADDR_CONF_USER, (BYTE*)&usr_cfg, sizeof(usr_cfg));
            if (err != LTR_OK) {
                QMessageBox::critical(env->mainWidget(), tr("Error"),
                                      tr("Cannot read user configuration") + ": " +
                                      LtrdConnection::getErrorString(err));
            } else {
                CrateConfigDialog::Config cfg;
                cfg.iface = usr_cfg.iface;
                cfg.ip_addr =   ((DWORD)usr_cfg.ip_addr[0] << 24) |
                                ((DWORD)usr_cfg.ip_addr[1] << 16) |
                                ((DWORD)usr_cfg.ip_addr[2] << 8)  |
                                ((DWORD)usr_cfg.ip_addr[3]);
                cfg.ip_netmask = ((DWORD)usr_cfg.ip_netmask[0] << 24) |
                        ((DWORD)usr_cfg.ip_netmask[1] << 16) |
                        ((DWORD)usr_cfg.ip_netmask[2] << 8)  |
                        ((DWORD)usr_cfg.ip_netmask[3]);
                cfg.ip_gateway = ((DWORD)usr_cfg.ip_gateway[0] << 24) |
                        ((DWORD)usr_cfg.ip_gateway[1] << 16) |
                        ((DWORD)usr_cfg.ip_gateway[2] << 8)  |
                        ((DWORD)usr_cfg.ip_gateway[3]);

                cfg.dhcp_en = usr_cfg.net_options & CONF_NETOPT_DHCP;

                memcpy(cfg.factory_mac, fact_cfg.mac_addr, sizeof(fact_cfg.mac_addr));
                memcpy(cfg.user_mac, usr_cfg.mac_addr, sizeof(usr_cfg.mac_addr));
                cfg.user_mac_en = usr_cfg.net_options & CONF_NETOPT_USERMAC;


                CrateConfigDialog dlg(cfg, env->mainWidget(),
                                      CrateConfigDialog::FlagPasswordRequired |
                                      CrateConfigDialog::FlagShowCrateReset |
                                      CrateConfigDialog::FlagShowFactoryMac |
                                      CrateConfigDialog::FlagShowUserMac |
                                      CrateConfigDialog::FlagShowDHCP |
                                      CrateConfigDialog::FlagShowSetPassword
                                      );
                if (dlg.exec() == QDialog::Accepted) {
                    usr_cfg.iface = dlg.crateInterface();
                    usr_cfg.ip_addr[0] = (dlg.ipAddr() >> 24) & 0xFF;
                    usr_cfg.ip_addr[1] = (dlg.ipAddr() >> 16) & 0xFF;
                    usr_cfg.ip_addr[2] = (dlg.ipAddr() >>  8) & 0xFF;
                    usr_cfg.ip_addr[3] = dlg.ipAddr() & 0xFF;

                    usr_cfg.ip_netmask[0] = (dlg.ipMask() >> 24) & 0xFF;
                    usr_cfg.ip_netmask[1] = (dlg.ipMask() >> 16) & 0xFF;
                    usr_cfg.ip_netmask[2] = (dlg.ipMask() >>  8) & 0xFF;
                    usr_cfg.ip_netmask[3] = dlg.ipMask() & 0xFF;

                    usr_cfg.ip_gateway[0] = (dlg.ipGate() >> 24) & 0xFF;
                    usr_cfg.ip_gateway[1] = (dlg.ipGate() >> 16) & 0xFF;
                    usr_cfg.ip_gateway[2] = (dlg.ipGate() >>  8) & 0xFF;
                    usr_cfg.ip_gateway[3] = dlg.ipGate() & 0xFF;

                    if (dlg.useDhcp()) {
                        usr_cfg.net_options |= CONF_NETOPT_DHCP;
                    } else {
                        usr_cfg.net_options &= ~CONF_NETOPT_DHCP;
                    }

                    if (dlg.setUserMac()) {
                        usr_cfg.net_options |= CONF_NETOPT_USERMAC;
                    } else {
                        usr_cfg.net_options &= ~CONF_NETOPT_USERMAC;
                    }

                    if (dlg.userMac().size() == CONF_MAC_SIZE)  {
                        memcpy(usr_cfg.mac_addr, dlg.userMac().data(), CONF_MAC_SIZE);
                    }

                    if (!dlg.passwd().isEmpty()) {
                        strncpy(usr_cfg.passwd, dlg.passwd().toLatin1().data(), sizeof(usr_cfg.passwd));
                    }

                    if (!dlg.setNewPass()) {
                        err = LTR_CratePutArray(&hcrate, IOADDR_CONF_USER, (BYTE*)&usr_cfg, sizeof(usr_cfg));
                    } else {
                        QByteArray usr_cfg_with_pass;
                        usr_cfg_with_pass.resize(sizeof(usr_cfg) + CONF_PASSWD_SIZE);
                        memcpy(usr_cfg_with_pass.data(), &usr_cfg, sizeof(usr_cfg));
                        strncpy(&usr_cfg_with_pass.data()[sizeof(usr_cfg)], dlg.newPass().toLatin1().data(), CONF_PASSWD_SIZE);
                        err = LTR_CratePutArray(&hcrate, IOADDR_CONF_USER, (BYTE*)usr_cfg_with_pass.data(), usr_cfg_with_pass.size());
                    }
                    if (err != LTR_OK) {
                        QMessageBox::critical(env->mainWidget(), tr("Error"),
                                              tr("Cannot write new configuration") + ": " +
                                              LtrdConnection::getErrorString(err));
                    } else {
                        if (QMessageBox::information(env->mainWidget(), tr("Done sucessfully"),
                                                 tr("Crate settings were written successfully!") +
                                                 tr("New settings take effect after crate reset. Reset crate yet?"),
                                                 QMessageBox::Yes | QMessageBox::No)==QMessageBox::Yes) {
                            quint32 cmd = LTR021M_CMD_REBOOT;
                            err = LTR_CratePutArray(&hcrate, IOADDR_SYS_COMMAND, (BYTE*)&cmd, sizeof(cmd) );
                            if (err != LTR_OK) {
                                QMessageBox::critical(env->mainWidget(), tr("Error"),
                                                      tr("Cannot reset crate") + ": " +
                                                      LtrdConnection::getErrorString(err));
                            } else {
                                ipReconnect(env, con, crate);
                            }
                        }
                    }
                }
            }
        }



        LTR_Close(&hcrate);
    }
}

void CratePluginCEU::ipReconnect(LtrManagerEnv *env, LtrdConnection *con, LtrCrateInfo *crate) {
    const QString &serial {crate->serial()};
    const QList<TLTR_CRATE_IP_ENTRY> ipList {con->ipEntryList()};
    for (const TLTR_CRATE_IP_ENTRY &ipEntry : ipList) {
        if (QString::fromLatin1(reinterpret_cast<const char*>(&ipEntry.serial_number[0])) == serial) {
            con->ipDisconnect(ipEntry.ip_addr);
            QMessageBox *msgbox{new QMessageBox{QMessageBox::Icon::Information,
                                                tr("Reconnect ip crate"),
                                                tr("IP Crate reconnection"),
                                                QMessageBox::StandardButton::NoButton,
                                                env->mainWidget()}};

            QTimer tmr;
            msgbox->setStandardButtons(QMessageBox::NoButton);
            connect(&tmr, &QTimer::timeout, msgbox, &QMessageBox::deleteLater);
            tmr.start(3000);
            msgbox->exec();
            con->ipConnect(ipEntry.ip_addr);
        }

    }
}
