// 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_Buffer.hxx>

#include <OpenGl_GlCore30.hxx>
#include <OpenGl_ShaderManager.hxx>
#include <Standard_Assert.hxx>

IMPLEMENT_STANDARD_RTTIEXT(OpenGl_Buffer, OpenGl_Resource)

//=================================================================================================

size_t OpenGl_Buffer::sizeOfGlType(unsigned int theType)
{
  switch (theType)
  {
    case GL_BYTE:
    case GL_UNSIGNED_BYTE:
      return sizeof(uint8_t);
    case GL_SHORT:
    case GL_UNSIGNED_SHORT:
      return sizeof(unsigned short);
#ifdef GL_INT
    case GL_INT:
#endif
    case GL_UNSIGNED_INT:
      return sizeof(unsigned int);
    case GL_FLOAT:
      return sizeof(float);
#ifdef GL_DOUBLE
    case GL_DOUBLE:
      return sizeof(double);
#endif
    default:
      return 0;
  }
}

//=================================================================================================

TCollection_AsciiString OpenGl_Buffer::FormatTarget(unsigned int theTarget)
{
  switch (theTarget)
  {
    case GL_ARRAY_BUFFER:
      return "GL_ARRAY_BUFFER";
    case GL_ELEMENT_ARRAY_BUFFER:
      return "GL_ELEMENT_ARRAY_BUFFER";
    case GL_PIXEL_UNPACK_BUFFER:
      return "GL_PIXEL_UNPACK_BUFFER";
    case GL_PIXEL_PACK_BUFFER:
      return "GL_PIXEL_PACK_BUFFER";
    case GL_UNIFORM_BUFFER:
      return "GL_UNIFORM_BUFFER";
    case GL_TEXTURE_BUFFER:
      return "GL_TEXTURE_BUFFER";
    case GL_COPY_READ_BUFFER:
      return "GL_COPY_READ_BUFFER";
    case GL_COPY_WRITE_BUFFER:
      return "GL_COPY_WRITE_BUFFER";
    case GL_TRANSFORM_FEEDBACK_BUFFER:
      return "GL_TRANSFORM_FEEDBACK_BUFFER";
#ifdef GL_QUERY_BUFFER
    case GL_QUERY_BUFFER:
      return "GL_QUERY_BUFFER";
    case GL_DRAW_INDIRECT_BUFFER:
      return "GL_DRAW_INDIRECT_BUFFER";
    case GL_ATOMIC_COUNTER_BUFFER:
      return "GL_ATOMIC_COUNTER_BUFFER";
    case GL_DISPATCH_INDIRECT_BUFFER:
      return "GL_DISPATCH_INDIRECT_BUFFER";
    case GL_SHADER_STORAGE_BUFFER:
      return "GL_SHADER_STORAGE_BUFFER";
#endif
  }
  return OpenGl_Context::FormatGlEnumHex(theTarget);
}

//=================================================================================================

OpenGl_Buffer::OpenGl_Buffer()
    : myOffset(nullptr),
      myBufferId(NO_BUFFER),
      myComponentsNb(4),
      myElemsNb(0),
      myDataType(GL_FLOAT)
{
  //
}

//=================================================================================================

OpenGl_Buffer::~OpenGl_Buffer()
{
  Release(nullptr);
}

//=================================================================================================

bool OpenGl_Buffer::Create(const occ::handle<OpenGl_Context>& theGlCtx)
{
  if (myBufferId == NO_BUFFER && theGlCtx->core15fwd != nullptr)
  {
    theGlCtx->core15fwd->glGenBuffers(1, &myBufferId);
  }
  return myBufferId != NO_BUFFER;
}

//=================================================================================================

void OpenGl_Buffer::Release(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 != nullptr,
    "OpenGl_Buffer destroyed without GL context! Possible GPU memory leakage...",
    Standard_VOID_RETURN);

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

//=================================================================================================

void OpenGl_Buffer::Bind(const occ::handle<OpenGl_Context>& theGlCtx) const
{
  theGlCtx->core15fwd->glBindBuffer(GetTarget(), myBufferId);
}

//=================================================================================================

void OpenGl_Buffer::Unbind(const occ::handle<OpenGl_Context>& theGlCtx) const
{
  theGlCtx->core15fwd->glBindBuffer(GetTarget(), NO_BUFFER);
}

//=================================================================================================

void OpenGl_Buffer::BindBufferBase(const occ::handle<OpenGl_Context>& theGlCtx,
                                   unsigned int                       theIndex)
{
  theGlCtx->core30->glBindBufferBase(GetTarget(), theIndex, myBufferId);
}

//=================================================================================================

void OpenGl_Buffer::UnbindBufferBase(const occ::handle<OpenGl_Context>& theGlCtx,
                                     unsigned int                       theIndex)
{
  theGlCtx->core30->glBindBufferBase(GetTarget(), theIndex, NO_BUFFER);
}

//=================================================================================================

void OpenGl_Buffer::BindBufferRange(const occ::handle<OpenGl_Context>& theGlCtx,
                                    unsigned int                       theIndex,
                                    const intptr_t                     theOffset,
                                    const size_t                       theSize)
{
  theGlCtx->core30->glBindBufferRange(GetTarget(),
                                      theIndex,
                                      myBufferId,
                                      (GLintptr)theOffset,
                                      (GLsizeiptr)theSize);
}

//=================================================================================================

bool OpenGl_Buffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                         const unsigned int                 theComponentsNb,
                         const int                          theElemsNb,
                         const float*                       theData)
{
  return init(theGlCtx, theComponentsNb, theElemsNb, theData, GL_FLOAT);
}

//=================================================================================================

bool OpenGl_Buffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                         const unsigned int                 theComponentsNb,
                         const int                          theElemsNb,
                         const unsigned int*                theData)
{
  return init(theGlCtx, theComponentsNb, theElemsNb, theData, GL_UNSIGNED_INT);
}

//=================================================================================================

bool OpenGl_Buffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                         const unsigned int                 theComponentsNb,
                         const int                          theElemsNb,
                         const unsigned short*              theData)
{
  return init(theGlCtx, theComponentsNb, theElemsNb, theData, GL_UNSIGNED_SHORT);
}

//=================================================================================================

bool OpenGl_Buffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                         const unsigned int                 theComponentsNb,
                         const int                          theElemsNb,
                         const uint8_t*                     theData)
{
  return init(theGlCtx, theComponentsNb, theElemsNb, theData, GL_UNSIGNED_BYTE);
}

//=================================================================================================

bool OpenGl_Buffer::init(const occ::handle<OpenGl_Context>& theGlCtx,
                         const unsigned int                 theComponentsNb,
                         const int                          theElemsNb,
                         const void*                        theData,
                         const unsigned int                 theDataType,
                         const int                          theStride)
{
  if (!Create(theGlCtx))
  {
    return false;
  }

  Bind(theGlCtx);
  myDataType     = theDataType;
  myComponentsNb = theComponentsNb;
  myElemsNb      = theElemsNb;
  theGlCtx->core15fwd->glBufferData(GetTarget(),
                                    GLsizeiptr(myElemsNb) * theStride,
                                    theData,
                                    GL_STATIC_DRAW);
  const int anErr = theGlCtx->core15fwd->glGetError();
  if (anErr != GL_NO_ERROR
      && anErr != GL_OUT_OF_MEMORY) // pass-through out-of-memory error, but log unexpected errors
  {
    theGlCtx->PushMessage(GL_DEBUG_SOURCE_APPLICATION,
                          GL_DEBUG_TYPE_ERROR,
                          0,
                          GL_DEBUG_SEVERITY_HIGH,
                          TCollection_AsciiString("Error: glBufferData (")
                            + FormatTarget(GetTarget()) + ","
                            + OpenGl_Context::FormatSize(GLsizeiptr(myElemsNb) * theStride) + ","
                            + OpenGl_Context::FormatPointer(theData) + ") Id: " + (int)myBufferId
                            + " failed with " + OpenGl_Context::FormatGlError(anErr));
  }
  Unbind(theGlCtx);
  return anErr == GL_NO_ERROR;
}

//=================================================================================================

bool OpenGl_Buffer::SubData(const occ::handle<OpenGl_Context>& theGlCtx,
                            const int                          theElemFrom,
                            const int                          theElemsNb,
                            const float*                       theData)
{
  return subData(theGlCtx, theElemFrom, theElemsNb, theData, GL_FLOAT);
}

//=================================================================================================

bool OpenGl_Buffer::SubData(const occ::handle<OpenGl_Context>& theGlCtx,
                            const int                          theElemFrom,
                            const int                          theElemsNb,
                            const unsigned int*                theData)
{
  return subData(theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_INT);
}

//=================================================================================================

bool OpenGl_Buffer::SubData(const occ::handle<OpenGl_Context>& theGlCtx,
                            const int                          theElemFrom,
                            const int                          theElemsNb,
                            const unsigned short*              theData)
{
  return subData(theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_SHORT);
}

//=================================================================================================

bool OpenGl_Buffer::SubData(const occ::handle<OpenGl_Context>& theGlCtx,
                            const int                          theElemFrom,
                            const int                          theElemsNb,
                            const uint8_t*                     theData)
{
  return subData(theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_BYTE);
}

//=================================================================================================

bool OpenGl_Buffer::subData(const occ::handle<OpenGl_Context>& theGlCtx,
                            const int                          theElemFrom,
                            const int                          theElemsNb,
                            const void*                        theData,
                            const unsigned int                 theDataType)
{
  if (!IsValid() || myDataType != theDataType || theElemFrom < 0
      || ((theElemFrom + theElemsNb) > myElemsNb))
  {
    return false;
  }

  Bind(theGlCtx);
  const size_t aDataSize = sizeOfGlType(theDataType);
  theGlCtx->core15fwd->glBufferSubData(
    GetTarget(),
    GLintptr(theElemFrom) * GLintptr(myComponentsNb) * aDataSize,    // offset in bytes
    GLsizeiptr(theElemsNb) * GLsizeiptr(myComponentsNb) * aDataSize, // size   in bytes
    theData);
  const int anErr = theGlCtx->core15fwd->glGetError();
  if (anErr != GL_NO_ERROR)
  {
    theGlCtx->PushMessage(
      GL_DEBUG_SOURCE_APPLICATION,
      GL_DEBUG_TYPE_ERROR,
      0,
      GL_DEBUG_SEVERITY_HIGH,
      TCollection_AsciiString("Error: glBufferSubData (") + FormatTarget(GetTarget()) + ","
        + OpenGl_Context::FormatSize(GLintptr(theElemFrom) * GLintptr(myComponentsNb) * aDataSize)
        + ","
        + OpenGl_Context::FormatSize(GLsizeiptr(theElemsNb) * GLsizeiptr(myComponentsNb)
                                     * aDataSize)
        + "," + OpenGl_Context::FormatPointer(theData) + ") Id: " + (int)myBufferId
        + " failed with " + OpenGl_Context::FormatGlError(anErr));
  }
  Unbind(theGlCtx);
  return anErr == GL_NO_ERROR;
}

//=================================================================================================

bool OpenGl_Buffer::GetSubData(const occ::handle<OpenGl_Context>& theGlCtx,
                               const int                          theElemFrom,
                               const int                          theElemsNb,
                               float*                             theData)
{
  return getSubData(theGlCtx, theElemFrom, theElemsNb, theData, GL_FLOAT);
}

//=================================================================================================

bool OpenGl_Buffer::GetSubData(const occ::handle<OpenGl_Context>& theGlCtx,
                               const int                          theElemFrom,
                               const int                          theElemsNb,
                               unsigned short*                    theData)
{
  return getSubData(theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_SHORT);
}

//=================================================================================================

bool OpenGl_Buffer::GetSubData(const occ::handle<OpenGl_Context>& theGlCtx,
                               const int                          theElemFrom,
                               const int                          theElemsNb,
                               unsigned int*                      theData)
{
  return getSubData(theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_INT);
}

//=================================================================================================

bool OpenGl_Buffer::GetSubData(const occ::handle<OpenGl_Context>& theGlCtx,
                               const int                          theElemFrom,
                               const int                          theElemsNb,
                               uint8_t*                           theData)
{
  return getSubData(theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_BYTE);
}

//=================================================================================================

bool OpenGl_Buffer::getSubData(const occ::handle<OpenGl_Context>& theGlCtx,
                               const int                          theElemFrom,
                               const int                          theElemsNb,
                               void*                              theData,
                               const unsigned int                 theDataType)
{
  if (!IsValid() || myDataType != theDataType || theElemFrom < 0
      || ((theElemFrom + theElemsNb) > myElemsNb) || !theGlCtx->hasGetBufferData)
  {
    return false;
  }

  Bind(theGlCtx);
  const size_t     aDataSize = sizeOfGlType(theDataType);
  const GLintptr   anOffset  = GLintptr(theElemFrom) * GLintptr(myComponentsNb) * aDataSize;
  const GLsizeiptr aSize     = GLsizeiptr(theElemsNb) * GLsizeiptr(myComponentsNb) * aDataSize;
  bool             isDone    = theGlCtx->GetBufferSubData(GetTarget(), anOffset, aSize, theData);
  isDone                     = isDone && (theGlCtx->core15fwd->glGetError() == GL_NO_ERROR);
  Unbind(theGlCtx);
  return isDone;
}

//=================================================================================================

void OpenGl_Buffer::DumpJson(Standard_OStream& theOStream, int theDepth) const
{
  OCCT_DUMP_TRANSIENT_CLASS_BEGIN(theOStream)
  OCCT_DUMP_BASE_CLASS(theOStream, theDepth, OpenGl_Resource)

  OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, GetTarget())
  OCCT_DUMP_FIELD_VALUE_POINTER(theOStream, myOffset)

  OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myBufferId)
  OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myComponentsNb)
  OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myElemsNb)
  OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myDataType)
}
