text: #include <cmath>
  #include <fstream>
  #include <iostream>
  #include <thread>
  #include <exception>
  
  #include <QDateTime>
  #include <QHBoxLayout>
  #include <QMouseEvent>
  #include <QVBoxLayout>
  
  #include "common/util.h"
  #include "common/params.h"
  #include "common/timing.h"
  #include "common/swaglog.h"
  #include "common/watchdog.h"
  #include "selfdrive/hardware/hw.h"
  
  #include "home.hpp"
  #include "paint.hpp"
  #include "qt_window.hpp"
  #include "widgets/drive_stats.hpp"
  #include "widgets/setup.hpp"
  
  #define BACKLIGHT_DT 0.25
  #define BACKLIGHT_TS 2.00
  #define BACKLIGHT_OFFROAD 50
  
  // HomeWindow: the container for the offroad (OffroadHome) and onroad (GLWindow) UIs
  
  HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
    layout = new QStackedLayout();
    layout->setStackingMode(QStackedLayout::StackAll);
  
    // onroad UI
    glWindow = new GLWindow(this);
    layout->addWidget(glWindow);
  
    // draw offroad UI on top of onroad UI
    home = new OffroadHome();
    layout->addWidget(home);
  
    QObject::connect(glWindow, SIGNAL(offroadTransition(bool)), home, SLOT(setVisible(bool)));
    QObject::connect(glWindow, SIGNAL(offroadTransition(bool)), this, SIGNAL(offroadTransition(bool)));
    QObject::connect(glWindow, SIGNAL(screen_shutoff()), this, SIGNAL(closeSettings()));
    QObject::connect(this, SIGNAL(openSettings()), home, SLOT(refresh()));
  
    setLayout(layout);
  }
  
  void HomeWindow::mousePressEvent(QMouseEvent* e) {
    UIState* ui_state = &glWindow->ui_state;
    if (GLWindow::ui_state.scene.driver_view) {
      Params().write_db_value("IsDriverViewEnabled", "0", 1);
      return;
    }
  
    glWindow->wake();
  
    // Settings button click
    if (!ui_state->sidebar_collapsed && settings_btn.ptInRect(e->x(), e->y())) {
      emit openSettings();
    }
  
    // Handle sidebar collapsing
    if (ui_state->scene.started && (e->x() >= ui_state->viz_rect.x - bdr_s)) {
      ui_state->sidebar_collapsed = !ui_state->sidebar_collapsed;
    }
  }
  
  
  // OffroadHome: the offroad home page
  
  OffroadHome::OffroadHome(QWidget* parent) : QWidget(parent) {
    QVBoxLayout* main_layout = new QVBoxLayout();
    main_layout->setContentsMargins(sbr_w + 50, 50, 50, 50);
  
    // top header
    QHBoxLayout* header_layout = new QHBoxLayout();
  
    date = new QLabel();
    date->setStyleSheet(R"(font-size: 55px;)");
    header_layout->addWidget(date, 0, Qt::AlignHCenter | Qt::AlignLeft);
  
    alert_notification = new QPushButton();
    alert_notification->setVisible(false);
    QObject::connect(alert_notification, SIGNAL(released()), this, SLOT(openAlerts()));
    header_layout->addWidget(alert_notification, 0, Qt::AlignHCenter | Qt::AlignRight);
  
    std::string brand = Params().read_db_bool("Passive") ? "dashcam" : "openpilot";
    QLabel* version = new QLabel(QString::fromStdString(brand + " v" + Params().get("Version")));
    version->setStyleSheet(R"(font-size: 55px;)");
    header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight);
  
    main_layout->addLayout(header_layout);
  
    // main content
    main_layout->addSpacing(25);
    center_layout = new QStackedLayout();
  
    QHBoxLayout* statsAndSetup = new QHBoxLayout();
  
    DriveStats* drive = new DriveStats;
    drive->setFixedSize(800, 800);
    statsAndSetup->addWidget(drive);
  
    SetupWidget* setup = new SetupWidget;
    //setup->setFixedSize(700, 700);
    statsAndSetup->addWidget(setup);
  
    QWidget* statsAndSetupWidget = new QWidget();
    statsAndSetupWidget->setLayout(statsAndSetup);
  
    center_layout->addWidget(statsAndSetupWidget);
  
    alerts_widget = new OffroadAlert();
    QObject::connect(alerts_widget, SIGNAL(closeAlerts()), this, SLOT(closeAlerts()));
    center_layout->addWidget(alerts_widget);
    center_layout->setAlignment(alerts_widget, Qt::AlignCenter);
  
    main_layout->addLayout(center_layout, 1);
  
    // set up refresh timer
    timer = new QTimer(this);
    QObject::connect(timer, SIGNAL(timeout()), this, SLOT(refresh()));
    refresh();
    timer->start(10 * 1000);
  
    setLayout(main_layout);
    setStyleSheet(R"(
      * {
       color: white;
      }
    )");
  }
  
  void OffroadHome::openAlerts() {
    center_layout->setCurrentIndex(1);
  }
  
  void OffroadHome::closeAlerts() {
    center_layout->setCurrentIndex(0);
  }
  
  void OffroadHome::refresh() {
    bool first_refresh = !date->text().size();
    if (!isVisible() && !first_refresh) {
      return;
    }
  
    date->setText(QDateTime::currentDateTime().toString("dddd, MMMM d"));
  
    // update alerts
  
    alerts_widget->refresh();
    if (!alerts_widget->alerts.size() && !alerts_widget->updateAvailable) {
      emit closeAlerts();
      alert_notification->setVisible(false);
      return;
    }
  
    if (alerts_widget->updateAvailable) {
      alert_notification->setText("UPDATE");
    } else {
      int alerts = alerts_widget->alerts.size();
      alert_notification->setText(QString::number(alerts) + " ALERT" + (alerts == 1 ? "" : "S"));
    }
  
    if (!alert_notification->isVisible() && !first_refresh) {
      emit openAlerts();
    }
    alert_notification->setVisible(true);
  
    // Red background for alerts, blue for update available
    QString style = QString(R"(
      padding: 15px;
      padding-left: 30px;
      padding-right: 30px;
      border: 1px solid;
      border-radius: 5px;
      font-size: 40px;
      font-weight: 500;
      background-color: #E22C2C;
    )");
    if (alerts_widget->updateAvailable) {
      style.replace("#E22C2C", "#364DEF");
    }
    alert_notification->setStyleSheet(style);
  }
  
  
  // GLWindow: the onroad UI
  
  static void handle_display_state(UIState* s, bool user_input) {
    static int awake_timeout = 0;
    awake_timeout = std::max(awake_timeout - 1, 0);
  
    constexpr float accel_samples = 5*UI_FREQ;
    static float accel_prev = 0., gyro_prev = 0.;
  
    bool should_wake = s->scene.started || s->scene.ignition || user_input;
    if (!should_wake) {
      // tap detection while display is off
      bool accel_trigger = abs(s->scene.accel_sensor - accel_prev) > 0.2;
      bool gyro_trigger = abs(s->scene.gyro_sensor - gyro_prev) > 0.15;
      should_wake = accel_trigger && gyro_trigger;
      gyro_prev = s->scene.gyro_sensor;
      accel_prev = (accel_prev * (accel_samples - 1) + s->scene.accel_sensor) / accel_samples;
    }
  
    if (should_wake) {
      awake_timeout = 30 * UI_FREQ;
    } else if (awake_timeout > 0) {
      should_wake = true;
    }
  
    // handle state transition
    if (s->awake != should_wake) {
      s->awake = should_wake;
      Hardware::set_display_power(s->awake);
      LOGD("setting display power %d", s->awake);
    }
  }
  
  GLWindow::GLWindow(QWidget* parent) : QOpenGLWidget(parent) {
    timer = new QTimer(this);
    QObject::connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate()));
  
    backlight_timer = new QTimer(this);
    QObject::connect(backlight_timer, SIGNAL(timeout()), this, SLOT(backlightUpdate()));
  
    int result = read_param(&brightness_b, "BRIGHTNESS_B", true);
    result += read_param(&brightness_m, "BRIGHTNESS_M", true);
    if (result != 0) {
      brightness_b = 10.0;
      brightness_m = 0.1;
    }
    smooth_brightness = BACKLIGHT_OFFROAD;
  }
  
  GLWindow::~GLWindow() {
    makeCurrent();
    doneCurrent();
  }
  
  void GLWindow::initializeGL() {
    initializeOpenGLFunctions();
    std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;
    std::cout << "OpenGL vendor: " << glGetString(GL_VENDOR) << std::endl;
    std::cout << "OpenGL renderer: " << glGetString(GL_RENDERER) << std::endl;
    std::cout << "OpenGL language version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
  
    ui_state.sound = &sound;
    ui_state.fb_w = vwp_w;
    ui_state.fb_h = vwp_h;
    ui_init(&ui_state);
  
    wake();
  
    prev_draw_t = millis_since_boot();
    timer->start(1000 / UI_FREQ);
    backlight_timer->start(BACKLIGHT_DT * 1000);
  }
  
  void GLWindow::backlightUpdate() {
    // Update brightness
    float k = (BACKLIGHT_DT / BACKLIGHT_TS) / (1.0f + BACKLIGHT_DT / BACKLIGHT_TS);
  
    float clipped_brightness = std::min(100.0f, (ui_state.scene.light_sensor * brightness_m) + brightness_b);
    if (!ui_state.scene.started) {
      clipped_brightness = BACKLIGHT_OFFROAD;
    }
  
    smooth_brightness = clipped_brightness * k + smooth_brightness * (1.0f - k);
  
    int brightness = smooth_brightness;
    if (!ui_state.awake) {
      brightness = 0;
      emit screen_shutoff();
    }
  
    if (brightness != last_brightness) {
      std::thread{Hardware::set_brightness, brightness}.detach();
    }
    last_brightness = brightness;
  }
  
  void GLWindow::timerUpdate() {
    // Connecting to visionIPC requires opengl to be current
    if (!ui_state.vipc_client->connected){
      makeCurrent();
    }
  
    if (ui_state.scene.started != onroad) {
      onroad = ui_state.scene.started;
      emit offroadTransition(!onroad);
  
      // Change timeout to 0 when onroad, this will call timerUpdate continously.
      // This puts visionIPC in charge of update frequency, reducing video latency
      timer->start(onroad ? 0 : 1000 / UI_FREQ);
    }
  
    handle_display_state(&ui_state, false);
  
    // scale volume with speed
    sound.volume = util::map_val(ui_state.scene.car_state.getVEgo(), 0.f, 20.f,
                                 Hardware::MIN_VOLUME, Hardware::MAX_VOLUME);
  
    ui_update(&ui_state);
    repaint();
    watchdog_kick();
  }
  
  void GLWindow::resizeGL(int w, int h) {
    std::cout << "resize " << w << "x" << h << std::endl;
  }
  
  void GLWindow::paintGL() {
    if(GLWindow::ui_state.awake){
      ui_draw(&ui_state);
  
      double cur_draw_t = millis_since_boot();
      double dt = cur_draw_t - prev_draw_t;
      if (dt > 66 && onroad){
        // warn on sub 15fps
        LOGW("slow frame(%llu) time: %.2f", ui_state.sm->frame, dt);
      }
      prev_draw_t = cur_draw_t;
    }
  }
  
  void GLWindow::wake() {
    handle_display_state(&ui_state, true);
  }
  