#ifndef VERTEX_ARRAY_H
#define VERTEX_ARRAY_H


#include "OpenGL.h"
#include "GLProgram.h"


typedef GLProgram::ProgramCallback VertexBindFunc;


////////////////////////////////////////////////////////////////////
// Declaration

template <typename VertexType>
class VertexArray
{
public:
  void createVertexBuffer();
  void destroyVertexBuffer();
  void update();
  void draw();
  void sync();
  VertexBindFunc bindAttributesFunction();

  GLuint                  m_vertexArray = 0;
  GLuint                  m_vertexBuffer = 0;
  std::vector<VertexType> m_vertexData;
  size_t                  m_gpuAllocatedSize = 0;
  size_t                  m_vertexDataSize = 0;
};


////////////////////////////////////////////////////////////////////
// Implementation

template <typename VertexType>
void VertexArray<VertexType>::createVertexBuffer()
{
  glGenVertexArraysOES(1, &m_vertexArray);
  glBindVertexArrayOES(m_vertexArray);
  glGenBuffers(1, &m_vertexBuffer);
  glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
  VertexType::visit(AttribPointerVisitor);
  glBindVertexArrayOES(0);

  // init data
  update();
}


template <typename VertexType>
void VertexArray<VertexType>::destroyVertexBuffer()
{
  glDeleteBuffers(1, &m_vertexBuffer);
  glDeleteVertexArraysOES(1, &m_vertexArray);
  m_vertexData.clear();
  m_vertexDataSize = 0;
}


template <typename VertexType>
void VertexArray<VertexType>::update()
{
  // update data
  glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
  
  m_vertexDataSize = m_vertexData.size();
  //  Seems like sub-loading isn't really an improvement (need to test more)
  //if (m_vertexDataSize > m_gpuAllocatedSize) {
    glBufferData(GL_ARRAY_BUFFER, sizeof(VertexType) * m_vertexDataSize, (void*)m_vertexData.data(), GL_STATIC_DRAW);
    m_gpuAllocatedSize = m_vertexDataSize;
  /*
  } else {
    if (m_vertexDataSize)
      glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(VertexType) * m_vertexDataSize, (void*)m_vertexData.data());
  }
   */
}


template <typename VertexType>
void VertexArray<VertexType>::sync()
{
  if (m_vertexData.size())
  {
    update();
    m_vertexData.clear();
    m_vertexData.shrink_to_fit();
  }
}


template <typename VertexType>
void VertexArray<VertexType>::draw()
{
  if (!m_vertexDataSize)
    return;
  glBindVertexArrayOES(m_vertexArray);
  
  //m_program.useProgram();
  //glCullFace(GL_BACK);
  // setup uniforms
  //Log(LL_Debug, "Vertex", "size: %i", int(m_vertexData.size()));
  
  glDrawArrays(GL_TRIANGLES, 0, GLsizei(m_vertexDataSize)); // tri-strips are better
}


template <typename VertexType>
VertexBindFunc VertexArray<VertexType>::bindAttributesFunction()
{
  return [](GLuint a_program) {
    auto bindAttribFunc = [a_program](const char* name, GLuint index, GLsizei, GLenum, GLboolean, GLsizei, const void*) {
      glBindAttribLocation(a_program, index, name);
    };
    VertexType::visit(bindAttribFunc);
  };
}


#endif // VERTEX_ARRAY_H

