#pragma once

#include <tuple>
#include <utility>
#include <vector>

#include <QMenu>
#include <QGraphicsPixmapItem>
#include <QGraphicsProxyWidget>
#include <QtCharts/QChartView>
#include <QtCharts/QLegendMarker>
#include <QtCharts/QLineSeries>
#include <QtCharts/QScatterSeries>
#include <QtCharts/QValueAxis>
using namespace QtCharts;

#include "tools/cabana/chart/tiplabel.h"
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"

enum class SeriesType {
  Line = 0,
  StepLine,
  Scatter
};

class ChartsWidget;
class ChartView : public QChartView {
  Q_OBJECT

public:
  ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent = nullptr);
  void addSignal(const MessageId &msg_id, const cabana::Signal *sig);
  bool hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const;
  void updateSeries(const cabana::Signal *sig = nullptr, const MessageEventsMap *msg_new_events = nullptr);
  void updatePlot(double cur, double min, double max);
  void setSeriesType(SeriesType type);
  void updatePlotArea(int left, bool force = false);
  void showTip(double sec);
  void hideTip();
  void startAnimation();
  double secondsAtPoint(const QPointF &pt) const { return chart()->mapToValue(pt).x(); }

  struct SigItem {
    MessageId msg_id;
    const cabana::Signal *sig = nullptr;
    QXYSeries *series = nullptr;
    std::vector<QPointF> vals;
    std::vector<QPointF> step_vals;
    QPointF track_pt{};
    SegmentTree segment_tree;
    double min = 0;
    double max = 0;
  };

signals:
  void axisYLabelWidthChanged(int w);

private slots:
  void signalUpdated(const cabana::Signal *sig);
  void manageSignals();
  void handleMarkerClicked();
  void msgUpdated(MessageId id);
  void msgRemoved(MessageId id) { removeIf([=](auto &s) { return s.msg_id.address == id.address && !dbc()->msg(id); }); }
  void signalRemoved(const cabana::Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); }

private:
  void appendCanEvents(const cabana::Signal *sig, const std::vector<const CanEvent *> &events,
                       std::vector<QPointF> &vals, std::vector<QPointF> &step_vals);
  void createToolButtons();
  void addSeries(QXYSeries *series);
  void contextMenuEvent(QContextMenuEvent *event) override;
  void mousePressEvent(QMouseEvent *event) override;
  void mouseReleaseEvent(QMouseEvent *event) override;
  void mouseMoveEvent(QMouseEvent *ev) override;
  void dragEnterEvent(QDragEnterEvent *event) override;
  void dragLeaveEvent(QDragLeaveEvent *event) override { drawDropIndicator(false); }
  void dragMoveEvent(QDragMoveEvent *event) override;
  void dropEvent(QDropEvent *event) override;
  void resizeEvent(QResizeEvent *event) override;
  QSize sizeHint() const override;
  void updateAxisY();
  void updateTitle();
  void resetChartCache();
  void setTheme(QChart::ChartTheme theme);
  void paintEvent(QPaintEvent *event) override;
  void drawForeground(QPainter *painter, const QRectF &rect) override;
  void drawBackground(QPainter *painter, const QRectF &rect) override;
  void drawDropIndicator(bool draw) { if (std::exchange(can_drop, draw) != can_drop) viewport()->update(); }
  void drawSignalValue(QPainter *painter);
  void drawTimeline(QPainter *painter);
  void drawRubberBandTimeRange(QPainter *painter);
  std::tuple<double, double, int> getNiceAxisNumbers(qreal min, qreal max, int tick_count);
  qreal niceNumber(qreal x, bool ceiling);
  QXYSeries *createSeries(SeriesType type, QColor color);
  void setSeriesColor(QXYSeries *, QColor color);
  void updateSeriesPoints();
  void removeIf(std::function<bool(const SigItem &)> predicate);
  inline void clearTrackPoints() { for (auto &s : sigs) s.track_pt = {}; }

  int y_label_width = 0;
  int align_to = 0;
  QValueAxis *axis_x;
  QValueAxis *axis_y;
  QMenu *menu;
  QAction *split_chart_act;
  QAction *close_act;
  QGraphicsPixmapItem *move_icon;
  QGraphicsProxyWidget *close_btn_proxy;
  QGraphicsProxyWidget *manage_btn_proxy;
  TipLabel *tip_label;
  std::vector<SigItem> sigs;
  double cur_sec = 0;
  SeriesType series_type = SeriesType::Line;
  bool is_scrubbing = false;
  bool resume_after_scrub = false;
  QPixmap chart_pixmap;
  bool can_drop = false;
  double tooltip_x = -1;
  QFont signal_value_font;
  ChartsWidget *charts_widget;
  friend class ChartsWidget;
};
