#include <QApplication>
#include <QWidget>
#include <QString>
#include <QTimer>
#include <QPushButton>
#include <QGraphicsScene>
#include <QPainter>
#include <QThread>
#include <QMouseEvent>
#include <QReadWriteLock>
#include <QLineEdit>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <stdlib.h>
#include <QTextStream>

#include "FileReader.hpp"
#include "Unlogger.hpp"
#include "FrameReader.hpp"

class Window : public QWidget {
  public:
    Window(QString route_, int seek, int use_api);
    bool addSegment(int i);
    QJsonArray camera_paths;
    QJsonArray log_paths;
    int use_api;
  protected:
    void keyPressEvent(QKeyEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    uint64_t ct;
    Unlogger *unlogger;
  private:
    int timeToPixel(uint64_t ns);
    uint64_t pixelToTime(int px);
    QString route;

    QReadWriteLock events_lock;
    Events events;
    int last_event_size = 0;

    QMap<int, LogReader*> lrs;
    QMap<int, FrameReader*> frs;


    // cache the bar
    QPixmap *px = NULL;
    int seg_add = 0;

    QLineEdit *timeLE;
};

Window::Window(QString route_, int seek, int use_api_) : route(route_), use_api(use_api_) {
  timeLE = new QLineEdit(this);
  timeLE->setPlaceholderText("Placeholder Text");
  timeLE->move(50, 650);

  QThread* thread = new QThread;
  unlogger = new Unlogger(&events, &events_lock, &frs, seek);
  unlogger->moveToThread(thread);
  connect(thread, SIGNAL (started()), unlogger, SLOT (process()));
  connect(unlogger, SIGNAL (elapsed()), this, SLOT (update()));
  thread->start();

  if (use_api) {
    QString settings;
    QFile file;
    file.setFileName("routes.json");
    file.open(QIODevice::ReadOnly | QIODevice::Text);
    settings = file.readAll();
    file.close();

    QJsonDocument sd = QJsonDocument::fromJson(settings.toUtf8());
    qWarning() << sd.isNull(); // <- print false :)
    QJsonObject sett2 = sd.object();

    this->camera_paths = sett2.value("camera").toArray();
    this->log_paths = sett2.value("logs").toArray();
  }

  this->setFocusPolicy(Qt::StrongFocus);

  // add the first segment
  addSegment(seek/60);
}

bool Window::addSegment(int i) {
  if (lrs.find(i) == lrs.end()) {
    QString fn = QString("http://data.comma.life/%1/%2/rlog.bz2").arg(route).arg(i);


    QThread* thread = new QThread;
    if (!use_api) {
      lrs.insert(i, new LogReader(fn, &events, &events_lock, &unlogger->eidx));
    } else {
      QString log_fn = this->log_paths.at(i).toString();
      lrs.insert(i, new LogReader(log_fn, &events, &events_lock, &unlogger->eidx));

    }

    lrs[i]->moveToThread(thread);
    connect(thread, SIGNAL (started()), lrs[i], SLOT (process()));
    thread->start();
    //connect(lrs[i], SIGNAL (finished()), this, SLOT (update()));

    QString frn = QString("http://data.comma.life/%1/%2/fcamera.hevc").arg(route).arg(i);

    if (!use_api) {
      frs.insert(i, new FrameReader(qPrintable(frn)));
    } else {
      QString camera_fn = this->camera_paths.at(i).toString();
      frs.insert(i, new FrameReader(qPrintable(camera_fn)));
    }


    return true;
  }
  return false;
}

#define PIXELS_PER_SEC 0.5

int Window::timeToPixel(uint64_t ns) {
  // TODO: make this dynamic
  return int(ns*1e-9*PIXELS_PER_SEC+0.5);
}

uint64_t Window::pixelToTime(int px) {
  // TODO: make this dynamic
  //printf("%d\n", px);
  return ((px+0.5)/PIXELS_PER_SEC) * 1e9;
}

void Window::keyPressEvent(QKeyEvent *event) {
  printf("keypress: %x\n", event->key());
  if (event->key() == Qt::Key_Space) unlogger->togglePause();
}

void Window::mousePressEvent(QMouseEvent *event) {
  //printf("mouse event\n");
  if (event->button() == Qt::LeftButton) {
    uint64_t t0 = events.begin().key();
    uint64_t tt = pixelToTime(event->x());
    int seg = int((tt*1e-9)/60);
    printf("segment %d\n", seg);
    addSegment(seg);

    //printf("seek to %lu\n", t0+tt);
    unlogger->setSeekRequest(t0+tt);
  }
  this->update();
}

void Window::paintEvent(QPaintEvent *event) {
  if (events.size() == 0) return;

  QElapsedTimer timer;
  timer.start();

  uint64_t t0 = events.begin().key();

  //p.drawRect(0, 0, 600, 100);

  // TODO: we really don't have to redraw this every time, only on updates to events
  float vEgo = 0.;
  int this_event_size = events.size();
  if (last_event_size != this_event_size) {
    if (px != NULL) delete px;
    px = new QPixmap(1920, 600);
    px->fill(QColor(0xd8, 0xd8, 0xd8));

    QPainter tt(px);
    tt.setBrush(Qt::cyan);

    int lt = -1;
    int lvv = 0;
    for (auto e : events) {
      auto type = e.which();
      //printf("%lld %d\n", e.getLogMonoTime()-t0, type);
      if (type == cereal::Event::CAR_STATE) {
        vEgo = e.getCarState().getVEgo();
      } else if (type == cereal::Event::CONTROLS_STATE) {
        auto controlsState = e.getControlsState();
        uint64_t t = (e.getLogMonoTime()-t0);
        int enabled = controlsState.getState() == cereal::ControlsState::OpenpilotState::ENABLED;
        int rt = timeToPixel(t); // 250 ms per pixel
        if (rt != lt) {
          int vv = vEgo*8.0;
          if (lt != -1) {
            tt.setPen(Qt::red);
            tt.drawLine(lt, 300-lvv, rt, 300-vv);

            if (enabled) {
              tt.setPen(Qt::green);
            } else {
              tt.setPen(Qt::blue);
            }

            tt.drawLine(rt, 300, rt, 600);
          }
          lt = rt;
          lvv = vv;
        }
      }
    }
    tt.end();
    last_event_size = this_event_size;
    if (lrs.find(seg_add) != lrs.end() && lrs[seg_add]->is_done) {
      while (!addSegment(++seg_add));
    }
  }

  QPainter p(this);
  if (px != NULL) p.drawPixmap(0, 0, 1920, 600, *px);

  p.setBrush(Qt::cyan);

  uint64_t ct = unlogger->getCurrentTime();
  if (ct != 0) {
    addSegment((((ct-t0)*1e-9)/60)+1);
    int rrt = timeToPixel(ct-t0);
    p.drawRect(rrt-1, 0, 2, 600);

    timeLE->setText(QString("%1").arg((ct-t0)*1e-9, '8', 'f', 2));
  }

  p.end();

  if (timer.elapsed() > 50) {
    qDebug() << "paint in" << timer.elapsed() << "ms";
  }
}

int main(int argc, char *argv[]) {
  QApplication app(argc, argv);

  QString route(argv[1]);

  int use_api = QString::compare(QString("use_api"), route, Qt::CaseInsensitive) == 0;
  int seek = QString(argv[2]).toInt();
  printf("seek: %d\n", seek);
  route = route.replace("|", "/");
  if (route == "") {
    printf("usage %s: <route>\n", argv[0]);
    exit(0);
    //route = "3a5d6ac1c23e5536/2019-10-29--10-06-58";
    //route = "0006c839f32a6f99/2019-02-18--06-21-29";
    //route = "02ec6bea180a4d36/2019-10-25--10-18-09";
  }

  Window window(route, seek, use_api);

  window.resize(1920, 800);
  window.setWindowTitle("nui unlogger");
  window.show();

  return app.exec();
}

