// Created by: Kirill GAVRILOV
// Copyright (c) 2013-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <OpenGl_VertexBuffer.hxx>

#include <OpenGl_Context.hxx>
#include <Standard_Assert.hxx>

IMPLEMENT_STANDARD_HANDLE (OpenGl_VertexBuffer, OpenGl_Resource)
IMPLEMENT_STANDARD_RTTIEXT(OpenGl_VertexBuffer, OpenGl_Resource)

// =======================================================================
// function : OpenGl_VertexBuffer
// purpose  :
// =======================================================================
OpenGl_VertexBuffer::OpenGl_VertexBuffer()
: OpenGl_Resource(),
  myBufferId (NO_BUFFER),
  myComponentsNb (4),
  myElemsNb (0),
  myDataType (GL_FLOAT)
{
  //
}

// =======================================================================
// function : ~OpenGl_VertexBuffer
// purpose  :
// =======================================================================
OpenGl_VertexBuffer::~OpenGl_VertexBuffer()
{
  Release (NULL);
}

// =======================================================================
// function : GetTarget
// purpose  :
// =======================================================================
GLenum OpenGl_VertexBuffer::GetTarget() const
{
  return GL_ARRAY_BUFFER;
}

// =======================================================================
// function : Create
// purpose  :
// =======================================================================
bool OpenGl_VertexBuffer::Create (const Handle(OpenGl_Context)& theGlCtx)
{
  if (myBufferId == NO_BUFFER)
  {
    theGlCtx->core15->glGenBuffers (1, &myBufferId);
  }
  return myBufferId != NO_BUFFER;
}

// =======================================================================
// function : Release
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::Release (const OpenGl_Context* theGlCtx)
{
  if (myBufferId == NO_BUFFER)
  {
    return;
  }

  // application can not handle this case by exception - this is bug in code
  Standard_ASSERT_RETURN (theGlCtx != NULL,
    "OpenGl_VertexBuffer destroyed without GL context! Possible GPU memory leakage...",);

  if (theGlCtx->IsValid())
  {
    theGlCtx->core15->glDeleteBuffers (1, &myBufferId);
  }
  myBufferId = NO_BUFFER;
}

// =======================================================================
// function : Bind
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::Bind (const Handle(OpenGl_Context)& theGlCtx) const
{
  theGlCtx->core15->glBindBuffer (GetTarget(), myBufferId);
}

// =======================================================================
// function : Unbind
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::Unbind (const Handle(OpenGl_Context)& theGlCtx) const
{
  theGlCtx->core15->glBindBuffer (GetTarget(), NO_BUFFER);
}

// =======================================================================
// function : init
// purpose  :
// =======================================================================
bool OpenGl_VertexBuffer::init (const Handle(OpenGl_Context)& theGlCtx,
                                const GLuint   theComponentsNb,
                                const GLsizei  theElemsNb,
                                const void*    theData,
                                const GLenum   theDataType,
                                const GLsizei  theStride)
{
  if (!Create (theGlCtx))
  {
    return false;
  }

  Bind (theGlCtx);
  myDataType     = theDataType;
  myComponentsNb = theComponentsNb;
  myElemsNb      = theElemsNb;
  theGlCtx->core15->glBufferData (GetTarget(), GLsizeiptr(myElemsNb) * theStride, theData, GL_STATIC_DRAW);
  bool isDone = (glGetError() == GL_NO_ERROR); // GL_OUT_OF_MEMORY
  Unbind (theGlCtx);
  return isDone;
}

// =======================================================================
// function : subData
// purpose  :
// =======================================================================
bool OpenGl_VertexBuffer::subData (const Handle(OpenGl_Context)& theGlCtx,
                                   const GLsizei  theElemFrom,
                                   const GLsizei  theElemsNb,
                                   const void*    theData,
                                   const GLenum   theDataType)
{
  if (!IsValid() || myDataType != theDataType ||
      theElemFrom < 0 || ((theElemFrom + theElemsNb) > myElemsNb))
  {
    return false;
  }

  Bind (theGlCtx);
  const size_t aDataSize = sizeOfGlType (theDataType);
  theGlCtx->core15->glBufferSubData (GetTarget(),
                                     GLintptr(theElemFrom)  * GLintptr  (myComponentsNb) * aDataSize, // offset in bytes
                                     GLsizeiptr(theElemsNb) * GLsizeiptr(myComponentsNb) * aDataSize, // size   in bytes
                                     theData);
  bool isDone = (glGetError() == GL_NO_ERROR); // some dummy error
  Unbind (theGlCtx);
  return isDone;
}

// =======================================================================
// function : BindVertexAttrib
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::BindVertexAttrib (const Handle(OpenGl_Context)& theGlCtx,
                                            const GLuint                  theAttribLoc) const
{
  if (!IsValid() || theAttribLoc == GLuint (-1))
  {
    return;
  }
  Bind (theGlCtx);
  theGlCtx->core20->glEnableVertexAttribArray (theAttribLoc);
  theGlCtx->core20->glVertexAttribPointer (theAttribLoc, GLint (myComponentsNb), myDataType, GL_FALSE, 0, NULL);
}

// =======================================================================
// function : UnbindVertexAttrib
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::UnbindVertexAttrib (const Handle(OpenGl_Context)& theGlCtx,
                                              const GLuint                  theAttribLoc) const
{
  if (!IsValid() || theAttribLoc == GLuint (-1))
  {
    return;
  }
  theGlCtx->core20->glDisableVertexAttribArray (theAttribLoc);
  Unbind (theGlCtx);
}

// =======================================================================
// function : BindFixed
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::BindFixed (const Handle(OpenGl_Context)& theGlCtx,
                                     const GLenum                  theMode) const
{
  if (!IsValid())
  {
    return;
  }

  Bind (theGlCtx);
  glEnableClientState (theMode);
  switch (theMode)
  {
    case GL_VERTEX_ARRAY:
    {
      glVertexPointer (static_cast<GLint> (myComponentsNb), myDataType, 0, NULL);
      break;
    }
    case GL_NORMAL_ARRAY:
    {
      glNormalPointer (myDataType, 0, NULL);
      break;
    }
    case GL_TEXTURE_COORD_ARRAY:
    {
      glTexCoordPointer (static_cast<GLint> (myComponentsNb), myDataType, 0, NULL);
      break;
    }
    case GL_COLOR_ARRAY:
    {
      glColorPointer (static_cast<GLint> (myComponentsNb), myDataType, 0, NULL);
      glColorMaterial (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
      glEnable (GL_COLOR_MATERIAL);
      break;
    }
    default: break;
  }
}

// =======================================================================
// function : UnbindFixed
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::UnbindFixed (const Handle(OpenGl_Context)& theGlCtx,
                                       const GLenum                  theMode) const
{
  if (!IsValid())
  {
    return;
  }
  Unbind (theGlCtx);
  glDisableClientState (theMode);
  if (theMode == GL_COLOR_ARRAY)
  {
    glDisable (GL_COLOR_MATERIAL);
  }
}

// =======================================================================
// function : BindFixed
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::BindFixed (const Handle(OpenGl_Context)& ) const
{
  //
}

// =======================================================================
// function : BindFixedPosition
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::BindFixedPosition (const Handle(OpenGl_Context)& ) const
{
  //
}

// =======================================================================
// function : UnbindFixed
// purpose  :
// =======================================================================
void OpenGl_VertexBuffer::UnbindFixed (const Handle(OpenGl_Context)& ) const
{
    //
}

// =======================================================================
// function : HasColorAttribute
// purpose  :
// =======================================================================
bool OpenGl_VertexBuffer::HasColorAttribute() const
{
  return false;
}