/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#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
