text: #pragma once
  
  #include <atomic>
  #include <map>
  #include <memory>
  #include <string>
  
  #include <QObject>
  #include <QTimer>
  #include <QColor>
  
  #include "nanovg.h"
  
  #include "cereal/messaging/messaging.h"
  #include "cereal/visionipc/visionipc.h"
  #include "cereal/visionipc/visionipc_client.h"
  #include "common/transformations/orientation.hpp"
  #include "selfdrive/camerad/cameras/camera_common.h"
  #include "selfdrive/common/glutil.h"
  #include "selfdrive/common/mat.h"
  #include "selfdrive/common/modeldata.h"
  #include "selfdrive/common/params.h"
  #include "selfdrive/common/util.h"
  #include "selfdrive/common/visionimg.h"
  
  #define COLOR_BLACK nvgRGBA(0, 0, 0, 255)
  #define COLOR_BLACK_ALPHA(x) nvgRGBA(0, 0, 0, x)
  #define COLOR_WHITE nvgRGBA(255, 255, 255, 255)
  #define COLOR_WHITE_ALPHA(x) nvgRGBA(255, 255, 255, x)
  #define COLOR_RED_ALPHA(x) nvgRGBA(201, 34, 49, x)
  #define COLOR_YELLOW nvgRGBA(218, 202, 37, 255)
  #define COLOR_RED nvgRGBA(201, 34, 49, 255)
  
  typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert;
  
  // TODO: this is also hardcoded in common/transformations/camera.py
  // TODO: choose based on frame input size
  const float y_offset = Hardware::TICI() ? 150.0 : 0.0;
  const float ZOOM = Hardware::TICI() ? 2912.8 : 2138.5;
  
  typedef struct Rect {
    int x, y, w, h;
    int centerX() const { return x + w / 2; }
    int centerY() const { return y + h / 2; }
    int right() const { return x + w; }
    int bottom() const { return y + h; }
    bool ptInRect(int px, int py) const {
      return px >= x && px < (x + w) && py >= y && py < (y + h);
    }
  } Rect;
  
  typedef struct Alert {
    QString text1;
    QString text2;
    QString type;
    cereal::ControlsState::AlertSize size;
    AudibleAlert sound;
    bool equal(Alert a2) {
      return text1 == a2.text1 && text2 == a2.text2 && type == a2.type;
    }
  } Alert;
  
  const Alert CONTROLS_WAITING_ALERT = {"openpilot Unavailable", "Waiting for controls to start",
                                        "controlsWaiting", cereal::ControlsState::AlertSize::MID,
                                        AudibleAlert::NONE};
  
  const Alert CONTROLS_UNRESPONSIVE_ALERT = {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive",
                                             "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL,
                                             AudibleAlert::CHIME_WARNING_REPEAT};
  const int CONTROLS_TIMEOUT = 5;
  
  const int bdr_s = 30;
  const int header_h = 420;
  const int footer_h = 280;
  
  const int UI_FREQ = 20;   // Hz
  
  typedef enum UIStatus {
    STATUS_DISENGAGED,
    STATUS_ENGAGED,
    STATUS_WARNING,
    STATUS_ALERT,
  } UIStatus;
  
  const QColor bg_colors [] = {
    [STATUS_DISENGAGED] =  QColor(0xe, 0xe, 0xe, 0xc8),
    [STATUS_ENGAGED] = QColor(0x0, 0xFF, 0x0, 0xf1),
    [STATUS_WARNING] = QColor(0xE, 0x17, 0x1F, 0xf1),
    [STATUS_ALERT] = QColor(0xC9, 0x22, 0x31, 0xf1),
  };
  
  typedef struct {
    float x, y;
  } vertex_data;
  
  typedef struct {
    vertex_data v[TRAJECTORY_SIZE * 2];
    int cnt;
  } line_vertices_data;
  
  typedef struct UIScene {
  
    int dfButtonStatus = 0;
    int lsButtonStatus = 0;
    bool mlButtonEnabled = false;
  
    mat3 view_from_calib;
    bool world_objects_visible;
  
    cereal::PandaState::PandaType pandaType;
  
    // modelV2
    float lane_line_probs[4];
    float road_edge_stds[2];
    line_vertices_data track_vertices;
    line_vertices_data lane_line_vertices[4];
    line_vertices_data road_edge_vertices[2];
  
    bool dm_active, engageable;
  
    // lead
    vertex_data lead_vertices[2];
  
    float light_sensor, accel_sensor, gyro_sensor;
    bool started, ignition, is_metric, longitudinal_control, end_to_end;
    uint64_t started_frame;
  } UIScene;
  
  typedef struct UIState {
    VisionIpcClient * vipc_client;
    VisionIpcClient * vipc_client_rear;
    VisionIpcClient * vipc_client_wide;
    VisionBuf * last_frame;
  
    // framebuffer
    int fb_w, fb_h;
  
    // NVG
    NVGcontext *vg;
  
    // images
    std::map<std::string, int> images;
  
    std::unique_ptr<SubMaster> sm;
    std::unique_ptr<PubMaster> pm;
  
    UIStatus status;
    UIScene scene;
  
    // graphics
    std::unique_ptr<GLShader> gl_shader;
    std::unique_ptr<EGLImageTexture> texture[UI_BUF_COUNT];
  
    GLuint frame_vao, frame_vbo, frame_ibo;
    mat4 rear_frame_mat;
  
    bool awake;
  
    float car_space_transform[6];
    bool wide_camera;
  } UIState;
  
  
  class QUIState : public QObject {
    Q_OBJECT
  
  public:
    QUIState(QObject* parent = 0);
  
    // TODO: get rid of this, only use signal
    inline static UIState ui_state = {0};
  
  signals:
    void uiUpdate(const UIState &s);
    void offroadTransition(bool offroad);
  
  private slots:
    void update();
  
  private:
    QTimer *timer;
    bool started_prev = true;
    void saInit(const UIState &s);
  };
  
  
  // device management class
  
  class Device : public QObject {
    Q_OBJECT
  
  public:
    Device(QObject *parent = 0);
  
  private:
    // auto brightness
    const float accel_samples = 5*UI_FREQ;
  
    bool awake;
    int awake_timeout = 0;
    float accel_prev = 0;
    float gyro_prev = 0;
    float last_brightness = 0;
    FirstOrderFilter brightness_filter;
  
    QTimer *timer;
  
    void updateBrightness(const UIState &s);
    void updateWakefulness(const UIState &s);
  
  signals:
    void displayPowerChanged(bool on);
  
  public slots:
    void setAwake(bool on, bool reset);
    void update(const UIState &s);
  };
  