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

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

IMPLEMENT_STANDARD_RTTIEXT(OpenGl_TextureBuffer, OpenGl_Buffer)

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

OpenGl_TextureBuffer::OpenGl_TextureBuffer()
    : myTextureId(NO_TEXTURE),
      myTexFormat(GL_RGBA32F)
{
  //
}

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

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

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

unsigned int OpenGl_TextureBuffer::GetTarget() const
{
  return GL_TEXTURE_BUFFER; // GL_TEXTURE_BUFFER for OpenGL 3.1+, OpenGL ES 3.2
}

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

void OpenGl_TextureBuffer::Release(OpenGl_Context* theGlCtx)
{
  if (myTextureId != NO_TEXTURE)
  {
    // application can not handle this case by exception - this is bug in code
    Standard_ASSERT_RETURN(
      theGlCtx != nullptr,
      "OpenGl_TextureBuffer destroyed without GL context! Possible GPU memory leakage...",
      Standard_VOID_RETURN);

    if (theGlCtx->IsValid())
    {
      theGlCtx->core20fwd->glDeleteTextures(1, &myTextureId);
    }
    myTextureId = NO_TEXTURE;
  }
  base_type::Release(theGlCtx);
}

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

bool OpenGl_TextureBuffer::Create(const occ::handle<OpenGl_Context>& theGlCtx)
{
  if (!base_type::Create(theGlCtx))
  {
    return false;
  }

  if (myTextureId == NO_TEXTURE)
  {
    theGlCtx->core20fwd->glGenTextures(1, &myTextureId);
  }
  return myTextureId != NO_TEXTURE;
}

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

bool OpenGl_TextureBuffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                                const unsigned int                 theComponentsNb,
                                const int                          theElemsNb,
                                const float*                       theData)
{
  if (theGlCtx->arbTBO == nullptr)
  {
    return false;
  }
  else if (theComponentsNb < 1 || theComponentsNb > 4)
  {
    // unsupported format
    return false;
  }
  else if (theComponentsNb == 3 && !theGlCtx->arbTboRGB32)
  {
    return false;
  }
  else if (!Create(theGlCtx) || !base_type::Init(theGlCtx, theComponentsNb, theElemsNb, theData))
  {
    return false;
  }

  switch (theComponentsNb)
  {
    case 1:
      myTexFormat = GL_R32F;
      break;
    case 2:
      myTexFormat = GL_RG32F;
      break;
    case 3:
      myTexFormat = GL_RGB32F;
      break; // GL_ARB_texture_buffer_object_rgb32
    case 4:
      myTexFormat = GL_RGBA32F;
      break;
  }

  Bind(theGlCtx);
  BindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  theGlCtx->arbTBO->glTexBuffer(GetTarget(), myTexFormat, myBufferId);
  UnbindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  Unbind(theGlCtx);
  return true;
}

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

bool OpenGl_TextureBuffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                                const unsigned int                 theComponentsNb,
                                const int                          theElemsNb,
                                const unsigned int*                theData)
{
  if (theGlCtx->arbTBO == nullptr)
  {
    return false;
  }
  else if (theComponentsNb < 1 || theComponentsNb > 4)
  {
    // unsupported format
    return false;
  }
  else if (theComponentsNb == 3 && !theGlCtx->arbTboRGB32)
  {
    return false;
  }
  else if (!Create(theGlCtx) || !base_type::Init(theGlCtx, theComponentsNb, theElemsNb, theData))
  {
    return false;
  }

  switch (theComponentsNb)
  {
    case 1:
      myTexFormat = GL_R32I;
      break;
    case 2:
      myTexFormat = GL_RG32I;
      break;
    case 3:
      myTexFormat = GL_RGB32I;
      break;
    case 4:
      myTexFormat = GL_RGBA32I;
      break;
  }

  Bind(theGlCtx);
  BindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  theGlCtx->arbTBO->glTexBuffer(GetTarget(), myTexFormat, myBufferId);
  UnbindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  Unbind(theGlCtx);
  return true;
}

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

bool OpenGl_TextureBuffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                                const unsigned int                 theComponentsNb,
                                const int                          theElemsNb,
                                const unsigned short*              theData)
{
  if (theGlCtx->arbTBO == nullptr)
  {
    return false;
  }
  else if (theComponentsNb < 1 || theComponentsNb > 4)
  {
    // unsupported format
    return false;
  }
  else if (!Create(theGlCtx) || !base_type::Init(theGlCtx, theComponentsNb, theElemsNb, theData))
  {
    return false;
  }

  switch (theComponentsNb)
  {
    case 1:
      myTexFormat = GL_R16I;
      break;
    case 2:
      myTexFormat = GL_RG16I;
      break;
    case 3:
      myTexFormat = GL_RGB16I;
      break;
    case 4:
      myTexFormat = GL_RGBA16I;
      break;
  }

  Bind(theGlCtx);
  BindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  theGlCtx->arbTBO->glTexBuffer(GetTarget(), myTexFormat, myBufferId);
  UnbindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  Unbind(theGlCtx);
  return true;
}

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

bool OpenGl_TextureBuffer::Init(const occ::handle<OpenGl_Context>& theGlCtx,
                                const unsigned int                 theComponentsNb,
                                const int                          theElemsNb,
                                const uint8_t*                     theData)
{
  if (theGlCtx->arbTBO == nullptr)
  {
    return false;
  }
  else if (theComponentsNb < 1 || theComponentsNb > 4)
  {
    // unsupported format
    return false;
  }
  else if (!Create(theGlCtx) || !base_type::Init(theGlCtx, theComponentsNb, theElemsNb, theData))
  {
    return false;
  }

  switch (theComponentsNb)
  {
    case 1:
      myTexFormat = GL_R8;
      break;
    case 2:
      myTexFormat = GL_RG8;
      break;
    case 3:
      myTexFormat = GL_RGB8;
      break;
    case 4:
      myTexFormat = GL_RGBA8;
      break;
  }

  Bind(theGlCtx);
  BindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  theGlCtx->arbTBO->glTexBuffer(GetTarget(), myTexFormat, myBufferId);
  UnbindTexture(theGlCtx, Graphic3d_TextureUnit_0);
  Unbind(theGlCtx);
  return true;
}

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

void OpenGl_TextureBuffer::BindTexture(const occ::handle<OpenGl_Context>& theGlCtx,
                                       const Graphic3d_TextureUnit        theTextureUnit) const
{
  theGlCtx->core20fwd->glActiveTexture(GL_TEXTURE0 + theTextureUnit);
  theGlCtx->core20fwd->glBindTexture(GetTarget(), myTextureId);
}

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

void OpenGl_TextureBuffer::UnbindTexture(const occ::handle<OpenGl_Context>& theGlCtx,
                                         const Graphic3d_TextureUnit        theTextureUnit) const
{
  theGlCtx->core20fwd->glActiveTexture(GL_TEXTURE0 + theTextureUnit);
  theGlCtx->core20fwd->glBindTexture(GetTarget(), NO_TEXTURE);
}
