#include "mapview/mapview.h"

MapView::MapView(QWidget *parent) {
  connect(this, SIGNAL(update_GUI()), this, SLOT(update()));

  vertices_len = 144;
  vertices = (Vertex *)malloc(vertices_len);
  memset(vertices, 0, vertices_len);

  map_texture = NULL;
  blinking = 0;
  count = 0;
  map_redraw = 0;
}

MapView::~MapView() {
  stop_blinking();
  makeCurrent();
  teardownGL();
}

void MapView::run() {
  usleep(500000);

  uint fps = 0;
  uint sq_state = 0;
  uint pre_sq_state = 0;

  while (blinking) {

    auto time = std::chrono::high_resolution_clock::now();

    count = 0;
    int changed = 0;

    fps++;

    for (int i = 0; i < 4; i++) {
      if (!(fps % (i + 4))) {
        sq_state = sq_state ^ (0b1 << i);
        changed = 1;
        if (((sq_state >> i) & 0b1))
          startingElements[count++] = i * 4;
      }
    }

    if (changed) {

      sleep_control = 0;
      emit(update_GUI());
      while (!sleep_control)
        sleep(0);

      pre_sq_state = sq_state;
    }

    usleep(16665 - std::chrono::duration_cast<std::chrono::microseconds>(
                       std::chrono::high_resolution_clock::now() - time)
                       .count());
  }
}

void MapView::stop_blinking() {
  if (!blinking)
    return;
  blinking = 0;
  count = 0;
  sleep_control = 1;
  t->join();
  free(t);
}

void MapView::toggle_blinking() {
  if (blinking)
    stop_blinking();
  else
    start_blinking();
}

void MapView::start_blinking() {

  if (blinking)
    return;
  blinking = 1;
  t = new std::thread(&MapView::run, this);
  sleep(0);
}

static const Vertex sg_vertexes[] = {
    Vertex(QVector3D(-0.55f, 0.55f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(-0.45f, 0.55f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(-0.45f, 0.45f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(-0.55f, 0.45f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),

    Vertex(QVector3D(-0.55f, -0.55f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(-0.45f, -0.55f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(-0.45f, -0.45f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(-0.55f, -0.45f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),

    Vertex(QVector3D(0.55f, 0.55f, 0.0f), QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(0.45f, 0.55f, 0.0f), QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(0.45f, 0.45f, 0.0f), QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(0.55f, 0.45f, 0.0f), QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),

    Vertex(QVector3D(0.55f, -0.55f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(0.45f, -0.55f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(0.45f, -0.45f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),
    Vertex(QVector3D(0.55f, -0.45f, 0.0f),
           QVector4D(0.75f, 0.75f, 0.75f, 1.0f)),

    Vertex(QVector3D(-0.55f, 0.55f, 0.0f), QVector4D(1.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(-0.45f, 0.55f, 0.0f), QVector4D(1.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(-0.45f, 0.45f, 0.0f), QVector4D(1.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(-0.55f, 0.45f, 0.0f), QVector4D(1.0f, 0.0f, 0.0f, 0.0f)),

    Vertex(QVector3D(-0.55f, -0.55f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(-0.45f, -0.55f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(-0.45f, -0.45f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(-0.55f, -0.45f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),

    Vertex(QVector3D(0.55f, 0.55f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(0.45f, 0.55f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(0.45f, 0.45f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(0.55f, 0.45f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),

    Vertex(QVector3D(0.55f, -0.55f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(0.45f, -0.55f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(0.45f, -0.45f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f)),
    Vertex(QVector3D(0.55f, -0.45f, 0.0f), QVector4D(0.0f, 0.0f, 0.0f, 0.0f))};

void MapView::initializeGL() {
  initializeOpenGLFunctions();
  printContextInformation();

  glClearColor(0, 0, 0, 1);
  {
    // Create Shader (Do not release until VAO is created)
    map_program = new QOpenGLShaderProgram();
    map_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
                                         ":/shaders/map.vert");
    map_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
                                         ":/shaders/map.frag");
    map_program->link();
    map_program->bind();
    map_vertex.create();

    map_vertex.bind();
    map_vertex.setUsagePattern(QOpenGLBuffer::DynamicDraw);
    map_vertex.allocate(vertices, vertices_len);
    // Create Vertex Array Object
    map_object.create();
    map_object.bind();
    map_program->enableAttributeArray(0);
    map_program->enableAttributeArray(1);
    map_program->enableAttributeArray(2);

    map_program->setAttributeBuffer(0, GL_FLOAT, Vertex::positionOffset(),
                                    Vertex::PositionTupleSize,
                                    Vertex::stride());
    map_program->setAttributeBuffer(1, GL_FLOAT, Vertex::colorOffset(),
                                    Vertex::ColorTupleSize, Vertex::stride());
    map_program->setAttributeBuffer(2, GL_FLOAT, Vertex::texCoordOffset(),
                                    Vertex::TexCoordTupleSize,
                                    Vertex::stride());

    // map_program->setUniformValue("ourTexture", 0);

    map_object.release();
    map_vertex.release();
    map_program->release();

    m_program = new QOpenGLShaderProgram();
    m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
                                       ":/shaders/blink.vert");
    m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
                                       ":/shaders/blink.frag");
    m_program->link();
    m_program->bind();

    // Create Buffer (Do not release until VAO is created)
    m_vertex.create();
    m_vertex.bind();
    m_vertex.setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_vertex.allocate(sg_vertexes, sizeof(sg_vertexes));

    // Create Vertex Array Object
    m_object.create();
    m_object.bind();
    m_program->enableAttributeArray(0);
    m_program->enableAttributeArray(1);
    m_program->setAttributeBuffer(0, GL_FLOAT, Vertex::positionOffset(),
                                  Vertex::PositionTupleSize, Vertex::stride());
    m_program->setAttributeBuffer(1, GL_FLOAT, Vertex::colorOffset(),
                                  Vertex::ColorTupleSize, Vertex::stride());

    // Release (unbind) all
    m_object.release();
    m_vertex.release();
    m_program->release();
  }
}

void MapView::paintGL() {

  glClear(GL_COLOR_BUFFER_BIT);

  if (map_redraw == 1) {
    map_program->bind();
    map_object.bind();
    map_texture->bind();
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    map_texture->release();
    map_object.release();
    map_program->release();
  }

  m_program->bind();
  {
    m_object.bind();
    glMultiDrawArrays(GL_TRIANGLE_FAN, startingElements, counts, count);
    m_object.release();
  }
  m_program->release();

  sleep_control = 1;
}

void MapView::resizeGL(int w, int h) {

  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void MapView::printContextInformation() {
  QString glType;
  QString glVersion;
  QString glProfile;

  // Get Version Information
  glType = (context()->isOpenGLES()) ? "OpenGL ES" : "OpenGL";
  glVersion = reinterpret_cast<const char *>(glGetString(GL_VERSION));

  // Get Profile Information
#define CASE(c)                                                                \
  case QSurfaceFormat::c:                                                      \
    glProfile = #c;                                                            \
    break
  switch (format().profile()) {
    CASE(NoProfile);
    CASE(CoreProfile);
    CASE(CompatibilityProfile);
  }
#undef CASE

  // qPrintable() will print our QString w/o quotes around it.
  qDebug() << qPrintable(glType) << qPrintable(glVersion) << "("
           << qPrintable(glProfile) << ")";
}

void MapView::teardownGL() {
  map_object.destroy();
  map_vertex.destroy();
  delete map_program;
  m_object.destroy();
  m_vertex.destroy();
  delete m_program;
}

void MapView::load_texture(int len, Vertex *ver, QImage *map_img) {

  map_redraw = 0;
  if (map_texture != NULL)
    map_texture->destroy();
  map_texture = new QOpenGLTexture(*map_img);
  vertices_len = len;
  memcpy(vertices, ver, len);
  map_vertex.bind();
  map_vertex.write(0, vertices, vertices_len);
  emit(map_loaded());

  map_redraw = 1;
  this->update();
}

void MapView::keyPressEvent(QKeyEvent *key) { emit(keyPress(key)); }
void MapView::mousePressEvent(QMouseEvent *event) { emit(mouse_press(event)); }
