/* Copyright (c) 2010 - 2023 Advanced Micro Devices, Inc.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE. */

#pragma once

#ifdef _WIN32
#include <windows.h>
#else  //!_WIN32
#include <dlfcn.h>
#endif  //!_WIN32

#include <GL/gl.h>
#include <GL/glext.h>
#include "CL/cl_gl.h"
#ifndef _WIN32
#include <GL/glx.h>
#endif  //!_WIN32

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>

#include "platform/context.hpp"
#include "platform/command.hpp"

namespace amd {

//! Class GLObject keeps all the info about the GL object
//! from which the CL object is created
class GLObject : public InteropObject {
 protected:
  cl_gl_object_type clGLType_;  //!< CL GL object type
  GLenum glTarget_;
  GLuint gluiName_;
  GLint gliMipLevel_;
  GLenum glInternalFormat_;
  GLint gliWidth_;
  GLint gliHeight_;
  GLint gliDepth_;
  GLenum glCubemapFace_;
  GLsizei glNumSamples_;

 public:
  //! GLObject constructor initializes member variables
  GLObject(GLenum glTarget, GLuint gluiName, GLint gliMipLevel, GLenum glInternalFormat,
           GLint gliWidth, GLint gliHeight, GLint gliDepth, cl_gl_object_type clGLType,
           GLenum glCubemapFace,
           GLsizei glNumSamples)
      :  // Initialization of member variables
        clGLType_(clGLType),
        glTarget_(glTarget),
        gluiName_(gluiName),
        gliMipLevel_(gliMipLevel),
        glInternalFormat_(glInternalFormat),
        gliWidth_(gliWidth),
        gliHeight_(gliHeight),
        gliDepth_(gliDepth),
        glCubemapFace_(glCubemapFace),
        glNumSamples_(glNumSamples) {}

  virtual ~GLObject() {}
  virtual GLObject* asGLObject() { return this; }

  //! GLObject query functions to get GL info from member variables
  GLenum getGLTarget() const { return glTarget_; }
  GLuint getGLName() const { return gluiName_; }
  GLint getGLMipLevel() const { return gliMipLevel_; }
  GLenum getGLInternalFormat() const { return glInternalFormat_; }
  GLint getGLSize() const { return gliWidth_; }
  GLint getGLWidth() const { return gliWidth_; }
  GLint getGLHeight() const { return gliHeight_; }
  GLint getGLDepth() const { return gliDepth_; }
  cl_gl_object_type getCLGLObjectType() const { return clGLType_; }
  GLenum getCubemapFace() const { return glCubemapFace_; }
  GLsizei getNumSamples() const { return glNumSamples_; }
};


//! Class BufferGL is drived from classes Buffer and GLObject
//! where the former keeps all data for CL object and
//! the latter keeps all data for GL object
class BufferGL : public Buffer, public GLObject {
 protected:
  //! Initializes the device memory array which is nested
  // after'BufferGL' object in memory layout.
  virtual void initDeviceMemory();

 public:
  //! BufferGL constructor just calls constructors of base classes
  //! to pass down the parameters
  BufferGL(Context& amdContext, cl_mem_flags clFlags, size_t uiSizeInBytes, GLenum glTarget,
           GLuint gluiName)
      :  // Call base classes constructors
        Buffer(amdContext, clFlags, uiSizeInBytes),
        GLObject(glTarget, gluiName,
                 0,                // Mipmap level default
                 GL_ARRAY_BUFFER,  // Just init to some value
                 (GLint)uiSizeInBytes, 1, 1, CL_GL_OBJECT_BUFFER, 0, 0) {
    setInteropObj(this);
  }
  virtual ~BufferGL() {}

  virtual BufferGL* asBufferGL() { return this; }
};


//! Class ImageGL is derived from classes Image and GLObject
//! where the former keeps all data for CL object and
//! the latter keeps all data for GL object
class ImageGL : public Image, public GLObject {
 public:
  //! ImageGL constructor just calls constructors of base classes
  //! to pass down the parameters
  ImageGL(Context& amdContext, cl_mem_object_type clType, cl_mem_flags clFlags,
          const Format& format, size_t width, size_t height, size_t depth, GLenum glTarget,
          GLuint gluiName, GLint gliMipLevel, GLenum glInternalFormat, cl_gl_object_type clGLType,
          GLsizei numSamples, GLenum glCubemapFace = 0)
      : Image(amdContext, clType, clFlags, format, width, height, depth,
              Format(format).getElementSize() * width,
              Format(format).getElementSize() * width * height),
        GLObject(glTarget, gluiName, gliMipLevel, glInternalFormat, static_cast<GLint>(width),
                 static_cast<GLint>(height), static_cast<GLint>(depth), clGLType, glCubemapFace,
                 numSamples) {
    setInteropObj(this);
  }

  //! ImageGL constructor for mipmapped image,
  //! just calls constructors of base classes to pass down the parameters
  ImageGL(Context& amdContext, cl_mem_object_type clType, cl_mem_flags clFlags,
          const Format& format, size_t width, size_t height, size_t depth, GLenum glTarget,
          GLuint gluiName, GLint gliMipLevel, GLenum glInternalFormat, cl_gl_object_type clGLType,
          GLsizei numSamples, cl_int num_mip_levels, GLenum glCubemapFace = 0)
      : Image(amdContext, clType, clFlags, format, width, height, depth,
              Format(format).getElementSize() * width,
              Format(format).getElementSize() * width * depth, num_mip_levels),
        GLObject(glTarget, gluiName, gliMipLevel, glInternalFormat, static_cast<GLint>(width),
                 static_cast<GLint>(height), static_cast<GLint>(depth), clGLType, glCubemapFace,
                 numSamples) {
    setInteropObj(this);
  }

  virtual ~ImageGL() {}

 protected:
  //! Initializes the device memory array which is nested
  // after'BufferGL' object in memory layout.
  virtual void initDeviceMemory();
};

typedef EGLContext (*PFN_eglGetCurrentContext)();
#ifdef _WIN32
#define APICALL WINAPI
#define GETPROCADDRESS GetProcAddress
#define API_GETPROCADDR "wglGetProcAddress"
#define FCN_STR_TYPE LPCSTR
typedef PROC(WINAPI* PFN_xxxGetProcAddress)(LPCSTR fcnName);
typedef HGLRC(APICALL* PFN_wglCreateContext)(HDC hdc);
typedef HGLRC(APICALL* PFN_wglGetCurrentContext)(void);
typedef HDC(APICALL* PFN_wglGetCurrentDC)(void);
typedef BOOL(APICALL* PFN_wglDeleteContext)(HGLRC hglrc);
typedef BOOL(APICALL* PFN_wglMakeCurrent)(HDC hdc, HGLRC hglrc);
typedef BOOL(APICALL* PFN_wglShareLists)(HGLRC hglrc1, HGLRC hglrc2);
#else            //!_WIN32
#define APICALL  // __stdcall   //??? todo odintsov
#define API_GETPROCADDR "glXGetProcAddress"
#define GETPROCADDRESS dlsym
#define FCN_STR_TYPE const GLubyte*
#define WINAPI
#define PROC void*
typedef void* (*PFN_xxxGetProcAddress)(const GLubyte* procName);
// X11 typedef
typedef Display* (*PFNXOpenDisplay)(_Xconst char* display_name);
typedef int (*PFNXCloseDisplay)(Display* display);
typedef int (*PFNXFree)(void* data);

// glx typedefs
typedef GLXDrawable (*PFNglXGetCurrentDrawable)();
typedef Display* (*PFNglXGetCurrentDisplay)();
typedef GLXContext (*PFNglXGetCurrentContext)(void);
typedef XVisualInfo* (*PFNglXChooseVisual)(Display* dpy, int screen, int* attribList);
typedef GLXContext (*PFNglXCreateContext)(Display* dpy, XVisualInfo* vis, GLXContext shareList,
                                          Bool direct);
typedef void (*PFNglXDestroyContext)(Display* dpy, GLXContext ctx);
typedef Bool (*PFNglXMakeCurrent)(Display* dpy, GLXDrawable drawable, GLXContext ctx);
typedef void* HMODULE;
#endif  //!_WIN32

#define GLPREFIX(rtype, fcn, dclargs) typedef rtype(APICALL* PFN_##fcn) dclargs;

// Declare prototypes for GL functions
#include "gl_functions.hpp"

class GLFunctions {
 public:
  //! Locks any access to the virtual GPUs
  class SetIntEnv : public amd::StackObject {
   public:
    //! Default constructor
    SetIntEnv(GLFunctions* env);

    //! Destructor
    ~SetIntEnv();

    //! Checks if the environment setup was successful
    bool isValid() const { return isValid_; }

   private:
    GLFunctions* env_;  //!< GL environment
    bool isValid_;      //!< If TRUE, then it's a valid setup
  };

  //! Locks any access to the virtual GPUs
  class Lock : public amd::StackObject {
   public:
    //! Default constructor
    Lock(GLFunctions* env);

    //! Destructor
    ~Lock();

   private:
    GLFunctions* env_;  //!< GL environment
  };

 private:
  HMODULE libHandle_;
  int missed_;  // Indicates how many GL functions not init'ed, if any

  amd::Monitor lock_;

  EGLDisplay eglDisplay_;
  EGLContext eglOriginalContext_;
  EGLContext eglInternalContext_;
  EGLContext eglTempContext_;
  bool isEGL_;
  PFN_eglGetCurrentContext eglGetCurrentContext_;

#ifdef _WIN32
  HGLRC hOrigGLRC_;
  HDC hDC_;
  HGLRC hIntGLRC_;  // handle for internal GLRC to access shared context
  HDC tempDC_;
  HGLRC tempGLRC_;

 public:
  PFN_wglCreateContext wglCreateContext_;
  PFN_wglGetCurrentContext wglGetCurrentContext_;
  PFN_wglGetCurrentDC wglGetCurrentDC_;
  PFN_wglDeleteContext wglDeleteContext_;
  PFN_wglMakeCurrent wglMakeCurrent_;
  PFN_wglShareLists wglShareLists_;
#else
 public:
  Display* Dpy_;
  GLXDrawable Drawable_;
  GLXContext origCtx_;
  Display* intDpy_;
  Window intDrawable_;
  GLXContext intCtx_;
  Display* tempDpy_;
  GLXDrawable tempDrawable_;
  GLXContext tempCtx_;

  // pointers to X11 functions
  PFNXOpenDisplay XOpenDisplay_;
  PFNXCloseDisplay XCloseDisplay_;
  PFNXFree XFree_;

  // pointers to GLX functions
  PFNglXGetCurrentDrawable glXGetCurrentDrawable_;
  PFNglXGetCurrentDisplay glXGetCurrentDisplay_;
  PFNglXGetCurrentContext glXGetCurrentContext_;
  PFNglXChooseVisual glXChooseVisual_;
  PFNglXCreateContext glXCreateContext_;
  PFNglXDestroyContext glXDestroyContext_;
  PFNglXMakeCurrent glXMakeCurrent_;
#endif
 public:
  GLFunctions(HMODULE h, bool isEGL);
  ~GLFunctions();

  bool update(intptr_t hglrc);
  bool IsCurrentGlContext(const amd::Context::Info& info) const {
    if (isEGL_) {
      return ((info.hCtx_ != nullptr) && (eglGetCurrentContext_ != nullptr) &&
              (info.hCtx_ == eglGetCurrentContext_()));
    } else {
#ifdef _WIN32
      return ((info.hCtx_ != nullptr) && (info.hCtx_ == wglGetCurrentContext_()));
#else
      return ((info.hCtx_ != nullptr) && (info.hCtx_ == glXGetCurrentContext_()));
#endif  // _WIN32
    }
  }

  void WaitCurrentGlContext(const amd::Context::Info& info) const;

  // Query CL-GL context association
  bool isAssociated() const {
    if (isEGL_ && eglDisplay_ && eglOriginalContext_) return true;
#ifdef _WIN32
    if (hDC_ && hOrigGLRC_) return true;
#else   //!_WIN32
    if (Dpy_ && origCtx_) return true;
#endif  //!_WIN32
    return false;
  }
  bool isEGL() const { return isEGL_; }
  // Accessor methods
#ifdef _WIN32
  HGLRC getOrigGLRC() const { return hOrigGLRC_; }
  HDC getDC() const { return hDC_; }
  HGLRC getIntGLRC() const { return hIntGLRC_; }
#else   //!_WIN32
  Display* getDpy() const { return Dpy_; }
  GLXDrawable getDrawable() const { return Drawable_; }
  GLXContext getOrigCtx() const { return origCtx_; }

  Display* getIntDpy() const { return intDpy_; }
  GLXDrawable getIntDrawable() const { return intDrawable_; }
  GLXContext getIntCtx() const { return intCtx_; }

  EGLDisplay getEglDpy() const { return eglDisplay_; }
  EGLContext getEglOrigCtx() const { return eglOriginalContext_; }
#endif  //!_WIN32

  // Initialize GL dynamic library and function pointers
  bool init(intptr_t hdc, intptr_t hglrc);

  // Return true if successful, false - if error occurred
  bool setIntEnv();
  bool restoreEnv();

  amd::Monitor& getLock() { return lock_; }

  PFN_xxxGetProcAddress GetProcAddress_;

#define GLPREFIX(rtype, fcn, dclargs) PFN_##fcn fcn##_;
// Declare pointers to GL functions
#include "gl_functions.hpp"
};

//! Functions for executing the GL related stuff
cl_mem clCreateFromGLBufferAMD(Context& amdContext, cl_mem_flags flags, GLuint bufobj,
                               cl_int* errcode_ret);
cl_mem clCreateFromGLTextureAMD(Context& amdContext, cl_mem_flags flags, GLenum target,
                                GLint miplevel, GLuint texture, int* errcode_ret);
cl_mem clCreateFromGLRenderbufferAMD(Context& amdContext, cl_mem_flags flags, GLuint renderbuffer,
                                     int* errcode_ret);

bool getCLFormatFromGL(const Context& amdContext, GLint gliInternalFormat,
                       cl_image_format* pclImageFormat, int* piBytesPerPixel, cl_mem_flags flags);

}  // namespace amd
