text: #include <string>
  #include <iostream>
  #include <sstream>
  #include <cassert>
  
  #ifndef QCOM
  #include "networking.h"
  #endif
  #include "settings.h"
  #include "widgets/input.h"
  #include "widgets/toggle.h"
  #include "widgets/offroad_alerts.h"
  #include "widgets/scrollview.h"
  #include "widgets/controls.h"
  #include "widgets/ssh_keys.h"
  #include "common/params.h"
  #include "common/util.h"
  #include "selfdrive/hardware/hw.h"
  #include "ui.h"
  
  TogglesPanel::TogglesPanel(QWidget *parent) : QWidget(parent) {
    QVBoxLayout *toggles_list = new QVBoxLayout();
  
    QList<ParamControl*> toggles;
  
    toggles.append(new ParamControl("OpenpilotEnabledToggle",
                                    "Enable openpilot",
                                    "Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.",
                                    "../assets/offroad/icon_openpilot.png",
                                    this));
    toggles.append(new ParamControl("IsLdwEnabled",
                                    "Enable Lane Departure Warnings",
                                    "Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31mph (50kph).",
                                    "../assets/offroad/icon_warning.png",
                                    this));
    toggles.append(new ParamControl("IsRHD",
                                    "Enable Right-Hand Drive",
                                    "Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat.",
                                    "../assets/offroad/icon_openpilot_mirrored.png",
                                    this));
    toggles.append(new ParamControl("IsMetric",
                                    "Use Metric System",
                                    "Display speed in km/h instead of mp/h.",
                                    "../assets/offroad/icon_metric.png",
                                    this));
    toggles.append(new ParamControl("CommunityFeaturesToggle",
                                    "Enable Community Features",
                                    "Use features from the open source community that are not maintained or supported by comma.ai and have not been confirmed to meet the standard safety model. These features include community supported cars and community supported hardware. Be extra cautious when using these features",
                                    "../assets/offroad/icon_shell.png",
                                    this));
  
    if (!Hardware::TICI()) {
      toggles.append(new ParamControl("IsUploadRawEnabled",
                                      "Upload Raw Logs",
                                      "Upload full logs and full resolution video by default while on WiFi. If not enabled, individual logs can be marked for upload at my.comma.ai/useradmin.",
                                      "../assets/offroad/icon_network.png",
                                      this));
    }
  
    ParamControl *record_toggle = new ParamControl("RecordFront",
                                                   "Record and Upload Driver Camera",
                                                  "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
                                                  "../assets/offroad/icon_monitoring.png",
                                                  this);
    toggles.append(record_toggle);
    toggles.append(new ParamControl("EndToEndToggle",
                                     "\U0001f96c Disable use of lanelines (Alpha) \U0001f96c",
                                     "In this mode openpilot will ignore lanelines and just drive how it thinks a human would.",
                                     "../assets/offroad/icon_road.png",
                                     this));
  
    if (Hardware::TICI()) {
      toggles.append(new ParamControl("EnableWideCamera",
                                      "Enable use of Wide Angle Camera",
                                      "Use wide angle camera for driving and ui. Only takes effect after reboot.",
                                      "../assets/offroad/icon_openpilot.png",
                                      this));
      toggles.append(new ParamControl("EnableLteOnroad",
                                      "Enable LTE while onroad",
                                      "",
                                      "../assets/offroad/icon_network.png",
                                      this));
    }
  
    bool record_lock = Params().getBool("RecordFrontLock");
    record_toggle->setEnabled(!record_lock);
  
    for(ParamControl *toggle : toggles){
      if(toggles_list->count() != 0){
        toggles_list->addWidget(horizontal_line());
      }
      toggles_list->addWidget(toggle);
    }
  
    setLayout(toggles_list);
  }
  
  DevicePanel::DevicePanel(QWidget* parent) : QWidget(parent) {
    QVBoxLayout *device_layout = new QVBoxLayout;
  
    Params params = Params();
  
    QString dongle = QString::fromStdString(params.get("DongleId", false));
    device_layout->addWidget(new LabelControl("Dongle ID", dongle));
    device_layout->addWidget(horizontal_line());
  
    QString serial = QString::fromStdString(params.get("HardwareSerial", false));
    device_layout->addWidget(new LabelControl("Serial", serial));
  
    QHBoxLayout *reset_layout = new QHBoxLayout();
    reset_layout->setSpacing(30);
  
    // reset calibration button
    QPushButton *reset_calib_btn = new QPushButton("Reset Calibration and LiveParameters");
    reset_layout->addWidget(reset_calib_btn);
    QObject::connect(reset_calib_btn, &QPushButton::released, [=]() {
      if (ConfirmationDialog::confirm("Are you sure you want to reset calibration and live params?", this)) {
        Params().remove("CalibrationParams");
        Params().remove("LiveParameters");
        QTimer::singleShot(1000, []() {
          Hardware::reboot();
        });
      }
    });
  
    device_layout->addWidget(horizontal_line());
    device_layout->addLayout(reset_layout);
  
    // offroad-only buttons
    QList<ButtonControl*> offroad_btns;
  
    offroad_btns.append(new ButtonControl("Driver Camera", "PREVIEW",
                                          "Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)",
                                          [=]() {
                                             Params().putBool("IsDriverViewEnabled", true);
                                             QUIState::ui_state.scene.driver_view = true;
                                          }, "", this));
  
    QString resetCalibDesc = "openpilot requires the device to be mounted within 4° left or right and within 5° up or down. openpilot is continuously calibrating, resetting is rarely required.";
    ButtonControl *resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", resetCalibDesc, [=]() {
      if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) {
        Params().remove("CalibrationParams");
      }
    }, "", this);
    connect(resetCalibBtn, &ButtonControl::showDescription, [=]() {
      QString desc = resetCalibDesc;
      std::string calib_bytes = Params().get("CalibrationParams");
      if (!calib_bytes.empty()) {
        try {
          AlignedBuffer aligned_buf;
          capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size()));
          auto calib = cmsg.getRoot<cereal::Event>().getLiveCalibration();
          if (calib.getCalStatus() != 0) {
            double pitch = calib.getRpyCalib()[1] * (180 / M_PI);
            double yaw = calib.getRpyCalib()[2] * (180 / M_PI);
            desc += QString(" Your device is pointed %1° %2 and %3° %4.")
                                  .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down",
                                       QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left");
          }
        } catch (kj::Exception) {
          qInfo() << "invalid CalibrationParams";
        }
      }
      resetCalibBtn->setDescription(desc);
    });
    offroad_btns.append(resetCalibBtn);
  
    offroad_btns.append(new ButtonControl("Review Training Guide", "REVIEW",
                                          "Review the rules, features, and limitations of openpilot", [=]() {
      if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) {
        Params().remove("CompletedTrainingVersion");
        emit reviewTrainingGuide();
      }
    }, "", this));
  
    QString brand = params.getBool("Passive") ? "dashcam" : "openpilot";
    offroad_btns.append(new ButtonControl("Uninstall " + brand, "UNINSTALL", "", [=]() {
      if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) {
        Params().putBool("DoUninstall", true);
      }
    }, "", this));
  
    for(auto &btn : offroad_btns){
      device_layout->addWidget(horizontal_line());
      QObject::connect(parent, SIGNAL(offroadTransition(bool)), btn, SLOT(setEnabled(bool)));
      device_layout->addWidget(btn);
    }
  
    // power buttons
    QHBoxLayout *power_layout = new QHBoxLayout();
    power_layout->setSpacing(30);
  
    QPushButton *reboot_btn = new QPushButton("Reboot");
    power_layout->addWidget(reboot_btn);
    QObject::connect(reboot_btn, &QPushButton::released, [=]() {
      if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) {
        Hardware::reboot();
      }
    });
  
    QPushButton *poweroff_btn = new QPushButton("Power Off");
    poweroff_btn->setStyleSheet("background-color: #E22C2C;");
    power_layout->addWidget(poweroff_btn);
    QObject::connect(poweroff_btn, &QPushButton::released, [=]() {
      if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) {
        Hardware::poweroff();
      }
    });
  
    device_layout->addLayout(power_layout);
  
    setLayout(device_layout);
    setStyleSheet(R"(
      QPushButton {
        padding: 0;
        height: 120px;
        border-radius: 15px;
        background-color: #393939;
      }
    )");
  }
  
  DeveloperPanel::DeveloperPanel(QWidget* parent) : QFrame(parent) {
    QVBoxLayout *main_layout = new QVBoxLayout(this);
    setLayout(main_layout);
    setStyleSheet(R"(QLabel {font-size: 50px;})");
  }
  
  void DeveloperPanel::showEvent(QShowEvent *event) {
    Params params = Params();
    std::string brand = params.getBool("Passive") ? "dashcam" : "openpilot";
    QList<QPair<QString, std::string>> dev_params = {
      {"Version", brand + " v" + params.get("Version", false).substr(0, 14)},
      {"Git Branch", params.get("GitBranch", false)},
      {"Git Commit", params.get("GitCommit", false).substr(0, 10)},
      {"Panda Firmware", params.get("PandaFirmwareHex", false)},
      {"OS Version", Hardware::get_os_version()},
    };
  
    for (int i = 0; i < dev_params.size(); i++) {
      const auto &[name, value] = dev_params[i];
      QString val = QString::fromStdString(value).trimmed();
      if (labels.size() > i) {
        labels[i]->setText(val);
      } else {
        labels.push_back(new LabelControl(name, val));
        layout()->addWidget(labels[i]);
        if (i < (dev_params.size() - 1)) {
          layout()->addWidget(horizontal_line());
        }
      }
    }
  }
  
  QWidget * network_panel(QWidget * parent) {
  #ifdef QCOM
    QVBoxLayout *layout = new QVBoxLayout;
    layout->setSpacing(30);
  
    // wifi + tethering buttons
    layout->addWidget(new ButtonControl("WiFi Settings", "OPEN", "",
                                        [=]() { HardwareEon::launch_wifi(); }));
    layout->addWidget(horizontal_line());
  
    layout->addWidget(new ButtonControl("Tethering Settings", "OPEN", "",
                                        [=]() { HardwareEon::launch_tethering(); }));
    layout->addWidget(horizontal_line());
  
    // SSH key management
    layout->addWidget(new SshToggle());
    layout->addWidget(horizontal_line());
    layout->addWidget(new SshControl());
  
    layout->addStretch(1);
  
    QWidget *w = new QWidget;
    w->setLayout(layout);
  #else
    Networking *w = new Networking(parent);
  #endif
    return w;
  }
  
  QWidget * community_panel() {
    QVBoxLayout *toggles_list = new QVBoxLayout();
    //toggles_list->setMargin(50);
  
    toggles_list->addWidget(new ParamControl("UseClusterSpeed",
                                              "Use Cluster Speed",
                                              "Use cluster speed instead of wheel speed.",
                                              "../assets/offroad/icon_road.png"
                                                ));
  
    toggles_list->addWidget(horizontal_line());
    toggles_list->addWidget(new ParamControl("LongControlEnabled",
                                              "Enable HKG Long Control",
                                              "warnings: it is beta, be careful!! Openpilot will control the speed of your car",
                                              "../assets/offroad/icon_road.png"
                                                ));
    toggles_list->addWidget(horizontal_line());
    toggles_list->addWidget(new ParamControl("MadModeEnabled",
                                              "Enable HKG MAD mode",
                                              "Openpilot will engage when turn cruise control on",
                                              "../assets/offroad/icon_openpilot.png"
                                                ));
    toggles_list->addWidget(horizontal_line());
    toggles_list->addWidget(new ParamControl("AutoLaneChangeEnabled",
                                              "Enable Auto Lane Change Assist",
                                              "warnings: it is beta, be careful!!",
                                              "../assets/offroad/icon_road.png"
                                                ));
    toggles_list->addWidget(horizontal_line());
    toggles_list->addWidget(new ParamControl("SccSmootherSlowOnCurves",
                                              "Enable Slow On Curves",
                                              "",
                                              "../assets/offroad/icon_road.png"
                                              ));
    toggles_list->addWidget(horizontal_line());
    toggles_list->addWidget(new ParamControl("SccSmootherSyncGasPressed",
                                              "Sync set speed on gas pressed",
                                              "",
                                              "../assets/offroad/icon_road.png"
                                              ));
    toggles_list->addWidget(horizontal_line());
    toggles_list->addWidget(new ParamControl("ShowDebugUI",
                                              "Show Debug UI",
                                              "",
                                              "../assets/offroad/icon_shell.png"
                                              ));
  
    QWidget *widget = new QWidget;
    widget->setLayout(toggles_list);
    return widget;
  }
  
  void SettingsWindow::showEvent(QShowEvent *event) {
    if (layout()) {
      panel_widget->setCurrentIndex(0);
      nav_btns->buttons()[0]->setChecked(true);
      return;
    }
  
    // setup two main layouts
    QVBoxLayout *sidebar_layout = new QVBoxLayout();
    sidebar_layout->setMargin(0);
    panel_widget = new QStackedWidget();
    panel_widget->setStyleSheet(R"(
      border-radius: 30px;
      background-color: #292929;
    )");
  
    // close button
    QPushButton *close_btn = new QPushButton("X");
    close_btn->setStyleSheet(R"(
      font-size: 90px;
      font-weight: bold;
      border 1px grey solid;
      border-radius: 100px;
      background-color: #292929;
    )");
    close_btn->setFixedSize(200, 200);
    sidebar_layout->addSpacing(45);
    sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter);
    QObject::connect(close_btn, &QPushButton::released, this, &SettingsWindow::closeSettings);
  
    // setup panels
    DevicePanel *device = new DevicePanel(this);
    QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide);
  
    QPair<QString, QWidget *> panels[] = {
      {"Device", device},
      {"Network", network_panel(this)},
      {"Toggles", new TogglesPanel(this)},
      {"Developer", new DeveloperPanel()},
      {"Community", community_panel()},
    };
  
    sidebar_layout->addSpacing(45);
    nav_btns = new QButtonGroup();
    for (auto &[name, panel] : panels) {
      QPushButton *btn = new QPushButton(name);
      btn->setCheckable(true);
      btn->setStyleSheet(R"(
        QPushButton {
          color: grey;
          border: none;
          background: none;
          font-size: 65px;
          font-weight: 500;
          padding-top: 35px;
          padding-bottom: 35px;
        }
        QPushButton:checked {
          color: white;
        }
      )");
  
      nav_btns->addButton(btn);
      sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
  
      panel->setContentsMargins(50, 25, 50, 25);
  
      ScrollView *panel_frame = new ScrollView(panel, this);
      panel_widget->addWidget(panel_frame);
  
      QObject::connect(btn, &QPushButton::released, [=, w = panel_frame]() {
        panel_widget->setCurrentWidget(w);
      });
    }
    sidebar_layout->setContentsMargins(50, 50, 100, 50);
  
    // main settings layout, sidebar + main panel
    QHBoxLayout *settings_layout = new QHBoxLayout();
  
    sidebar_widget = new QWidget;
    sidebar_widget->setLayout(sidebar_layout);
    sidebar_widget->setFixedWidth(500);
    settings_layout->addWidget(sidebar_widget);
    settings_layout->addWidget(panel_widget);
  
    setLayout(settings_layout);
    setStyleSheet(R"(
      * {
        color: white;
        font-size: 50px;
      }
      SettingsWindow {
        background-color: black;
      }
    )");
  }
  
  void SettingsWindow::hideEvent(QHideEvent *event){
  #ifdef QCOM
    HardwareEon::close_activities();
  #endif
  
    // TODO: this should be handled by the Dialog classes
    QList<QWidget*> children = findChildren<QWidget *>();
    for(auto &w : children){
      if(w->metaObject()->superClass()->className() == QString("QDialog")){
        w->close();
      }
    }
  }
  