// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef NO_OPENGL3
#include "tiny_opengl_fontstashcallbacks.h"
#include "tiny_gl_prim_internal_data.h"
#include "tiny_gl_primitive_renderer.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "stb_image/stb_image_write.h"
#include "tiny_font_stash.h"
#include "tiny_opengl_include.h"

static unsigned int s_indexData[INDEX_COUNT];
static GLuint s_indexArrayObject, s_indexBuffer;
static GLuint s_vertexArrayObject, s_vertexBuffer;

OpenGL2RenderCallbacks::OpenGL2RenderCallbacks(
    TinyGLPrimitiveRenderer* primRender)
    : m_primRender2(primRender) {}
OpenGL2RenderCallbacks::~OpenGL2RenderCallbacks() {}

PrimInternalData* OpenGL2RenderCallbacks::getData() {
  return m_primRender2->get_data();
}
InternalOpenGL2RenderCallbacks::~InternalOpenGL2RenderCallbacks() {}

void InternalOpenGL2RenderCallbacks::display2() {
  assert(glGetError() == GL_NO_ERROR);
  // glViewport(0,0,10,10);

  // const float timeScale = 0.008f;
  PrimInternalData* data = getData();

  glUseProgram(data->m_shaderProg);
  glBindBuffer(GL_ARRAY_BUFFER, s_vertexBuffer);
  glBindVertexArray(s_vertexArrayObject);

  assert(glGetError() == GL_NO_ERROR);

  //   glBindTexture(GL_TEXTURE_2D,m_texturehandle);

  assert(glGetError() == GL_NO_ERROR);
  float identity[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
  glUniformMatrix4fv(data->m_viewmatUniform, 1, false, identity);
  glUniformMatrix4fv(data->m_projMatUniform, 1, false, identity);

  vec2 p(0.f, 0.f);  //?b?0.5f * sinf(timeValue), 0.5f * cosf(timeValue) );
  glUniform2fv(data->m_positionUniform, 1, (const GLfloat*)&p);

  assert(glGetError() == GL_NO_ERROR);

  glEnableVertexAttribArray(data->m_positionAttribute);
  assert(glGetError() == GL_NO_ERROR);

  glEnableVertexAttribArray(data->m_colourAttribute);
  assert(glGetError() == GL_NO_ERROR);

  glEnableVertexAttribArray(data->m_textureAttribute);

  glVertexAttribPointer(data->m_positionAttribute, 4, GL_FLOAT, GL_FALSE,
                        sizeof(Vertex), (const GLvoid*)0);
  glVertexAttribPointer(data->m_colourAttribute, 4, GL_FLOAT, GL_FALSE,
                        sizeof(Vertex), (const GLvoid*)sizeof(vec4));
  glVertexAttribPointer(data->m_textureAttribute, 2, GL_FLOAT, GL_FALSE,
                        sizeof(Vertex),
                        (const GLvoid*)(sizeof(vec4) + sizeof(vec4)));
  assert(glGetError() == GL_NO_ERROR);
  /*

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
//glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
int indexCount = 6;
err = glGetError();
assert(err==GL_NO_ERROR);

glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);
err = glGetError();
assert(err==GL_NO_ERROR);
*/

  //	glutSwapBuffers();
}

void InternalOpenGL2RenderCallbacks::update_texture(sth_texture* texture,
                                                    sth_glyph* glyph,
                                                    int textureWidth,
                                                    int textureHeight) {
  assert(glGetError() == GL_NO_ERROR);

  if (glyph) {
    // Update texture (entire texture, could use glyph to update partial texture
    // using glTexSubImage2D)
    GLuint* gltexture = (GLuint*)texture->m_userData;

    glBindTexture(GL_TEXTURE_2D, *gltexture);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    assert(glGetError() == GL_NO_ERROR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, textureWidth, textureHeight, 0,
                 GL_RED, GL_UNSIGNED_BYTE, texture->m_texels);

    assert(glGetError() == GL_NO_ERROR);
  } else {
    if (textureWidth && textureHeight) {
      GLuint* texId = new GLuint;
      texture->m_userData = texId;

      // create new texture
      glGenTextures(1, texId);
      assert(glGetError() == GL_NO_ERROR);

      glBindTexture(GL_TEXTURE_2D, *texId);
      texture->m_texels = (unsigned char*)malloc(textureWidth * textureHeight);
      memset(texture->m_texels, 0, textureWidth * textureHeight);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, textureWidth, textureHeight, 0,
                   GL_RED, GL_UNSIGNED_BYTE, texture->m_texels);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                      GL_LINEAR_MIPMAP_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      assert(glGetError() == GL_NO_ERROR);

      ////////////////////////////
      // create the other data
      {
        glGenVertexArrays(1, &s_vertexArrayObject);
        glBindVertexArray(s_vertexArrayObject);

        glGenBuffers(1, &s_vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, s_vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, VERT_COUNT * sizeof(Vertex),
                     texture->newverts, GL_DYNAMIC_DRAW);
        assert(glGetError() == GL_NO_ERROR);

        for (int i = 0; i < INDEX_COUNT; i++) {
          s_indexData[i] = i;
        }

        glGenBuffers(1, &s_indexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_COUNT * sizeof(int),
                     s_indexData, GL_STATIC_DRAW);

        assert(glGetError() == GL_NO_ERROR);
      }
    } else {
      // delete texture
      if (texture->m_userData) {
        GLuint* id = (GLuint*)texture->m_userData;

        glDeleteTextures(1, id);
        // delete id;
        delete id;  // texture->m_userData;
        texture->m_userData = 0;
      }
    }
  }
}

void InternalOpenGL2RenderCallbacks::render(sth_texture* texture) {
  display2();

  GLuint* texId = (GLuint*)texture->m_userData;

  assert(glGetError() == GL_NO_ERROR);

  glActiveTexture(GL_TEXTURE0);
  assert(glGetError() == GL_NO_ERROR);

  glBindTexture(GL_TEXTURE_2D, *texId);
  bool useFiltering = false;
  if (useFiltering) {
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  } else {
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  }
  assert(glGetError() == GL_NO_ERROR);
  glBindBuffer(GL_ARRAY_BUFFER, s_vertexBuffer);
  glBindVertexArray(s_vertexArrayObject);
  glBufferData(GL_ARRAY_BUFFER, texture->nverts * sizeof(Vertex),
               &texture->newverts[0].position.p[0], GL_DYNAMIC_DRAW);

  assert(glGetError() == GL_NO_ERROR);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_indexBuffer);

  // glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
  int indexCount = texture->nverts;
  assert(glGetError() == GL_NO_ERROR);

  glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);
  assert(glGetError() == GL_NO_ERROR);

  glBindVertexArray(0);

  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  //	 glDisableVertexAttribArray(m_textureAttribute);
  glUseProgram(0);
}

void dumpTextureToPng(int textureWidth, int textureHeight,
                      const char* fileName) {
  glPixelStorei(GL_PACK_ALIGNMENT, 1);
  unsigned char* pixels = (unsigned char*)malloc(textureWidth * textureHeight);
  glReadPixels(0, 0, textureWidth, textureHeight, GL_RED, GL_UNSIGNED_BYTE,
               pixels);
  // swap the pixels
  unsigned char* tmp = (unsigned char*)malloc(textureWidth);
  for (int j = 0; j < textureHeight; j++) {
    pixels[j * textureWidth + j] = 255;
  }
  if (0) {
    for (int j = 0; j < textureHeight / 2; j++) {
      for (int i = 0; i < textureWidth; i++) {
        tmp[i] = pixels[j * textureWidth + i];
        pixels[j * textureWidth + i] =
            pixels[(textureHeight - j - 1) * textureWidth + i];
        pixels[(textureHeight - j - 1) * textureWidth + i] = tmp[i];
      }
    }
  }

  int comp = 1;  // 1=Y
  stbi_write_png(fileName, textureWidth, textureHeight, comp, pixels,
                 textureWidth);

  free(pixels);
}
#endif
