// Copyright 2013 The Flutter Authors. All rights reserved.

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <epoxy/egl.h>
#include <epoxy/gl.h>

typedef struct {
  EGLint config_id;
  EGLint buffer_size;
  EGLint color_buffer_type;
  EGLint transparent_type;
  EGLint level;
  EGLint red_size;
  EGLint green_size;
  EGLint blue_size;
  EGLint alpha_size;
  EGLint depth_size;
  EGLint stencil_size;
  EGLint samples;
  EGLint sample_buffers;
  EGLint native_visual_id;
  EGLint native_visual_type;
  EGLint native_renderable;
  EGLint config_caveat;
  EGLint bind_to_texture_rgb;
  EGLint bind_to_texture_rgba;
  EGLint renderable_type;
  EGLint conformant;
  EGLint surface_type;
  EGLint max_pbuffer_width;
  EGLint max_pbuffer_height;
  EGLint max_pbuffer_pixels;
  EGLint min_swap_interval;
  EGLint max_swap_interval;
} MockConfig;

typedef struct {
} MockDisplay;

typedef struct {
} MockContext;

typedef struct {
} MockSurface;

static bool display_initialized = false;
static MockDisplay mock_display;
static MockConfig mock_config;
static MockContext mock_context;
static MockSurface mock_surface;

static EGLint mock_error = EGL_SUCCESS;

static bool check_display(EGLDisplay dpy) {
  if (dpy == nullptr) {
    mock_error = EGL_BAD_DISPLAY;
    return false;
  }

  return true;
}

static bool check_initialized(EGLDisplay dpy) {
  if (!display_initialized) {
    mock_error = EGL_NOT_INITIALIZED;
    return false;
  }

  return true;
}

static bool check_config(EGLConfig config) {
  if (config == nullptr) {
    mock_error = EGL_BAD_CONFIG;
    return false;
  }

  return true;
}

static EGLBoolean bool_success() {
  mock_error = EGL_SUCCESS;
  return EGL_TRUE;
}

static EGLBoolean bool_failure(EGLint error) {
  mock_error = error;
  return EGL_FALSE;
}

EGLBoolean _eglBindAPI(EGLenum api) {
  return bool_success();
}

EGLBoolean _eglChooseConfig(EGLDisplay dpy,
                            const EGLint* attrib_list,
                            EGLConfig* configs,
                            EGLint config_size,
                            EGLint* num_config) {
  if (!check_display(dpy) || !check_initialized(dpy)) {
    return EGL_FALSE;
  }

  if (configs == nullptr) {
    if (num_config != nullptr) {
      *num_config = 1;
    }
    return bool_success();
  }

  EGLint n_returned = 0;
  if (config_size >= 1) {
    configs[0] = &mock_config;
    n_returned++;
  }

  if (num_config != nullptr) {
    *num_config = n_returned;
  }

  return bool_success();
}

EGLContext _eglCreateContext(EGLDisplay dpy,
                             EGLConfig config,
                             EGLContext share_context,
                             const EGLint* attrib_list) {
  if (!check_display(dpy) || !check_initialized(dpy) || !check_config(config)) {
    return EGL_NO_CONTEXT;
  }

  mock_error = EGL_SUCCESS;
  return &mock_context;
}

EGLSurface _eglCreatePbufferSurface(EGLDisplay dpy,
                                    EGLConfig config,
                                    const EGLint* attrib_list) {
  if (!check_display(dpy) || !check_initialized(dpy) || !check_config(config)) {
    return EGL_NO_SURFACE;
  }

  mock_error = EGL_SUCCESS;
  return &mock_surface;
}

EGLSurface _eglCreateWindowSurface(EGLDisplay dpy,
                                   EGLConfig config,
                                   EGLNativeWindowType win,
                                   const EGLint* attrib_list) {
  if (!check_display(dpy) || !check_initialized(dpy) || !check_config(config)) {
    return EGL_NO_SURFACE;
  }

  mock_error = EGL_SUCCESS;
  return &mock_surface;
}

EGLBoolean _eglGetConfigAttrib(EGLDisplay dpy,
                               EGLConfig config,
                               EGLint attribute,
                               EGLint* value) {
  if (!check_display(dpy) || !check_initialized(dpy) || !check_config(config)) {
    return EGL_FALSE;
  }

  MockConfig* c = static_cast<MockConfig*>(config);
  switch (attribute) {
    case EGL_CONFIG_ID:
      *value = c->config_id;
      return bool_success();
    case EGL_BUFFER_SIZE:
      *value = c->buffer_size;
      return bool_success();
    case EGL_COLOR_BUFFER_TYPE:
      *value = c->color_buffer_type;
      return bool_success();
    case EGL_TRANSPARENT_TYPE:
      *value = c->transparent_type;
      return bool_success();
    case EGL_LEVEL:
      *value = c->level;
      return bool_success();
    case EGL_RED_SIZE:
      *value = c->red_size;
      return bool_success();
    case EGL_GREEN_SIZE:
      *value = c->green_size;
      return bool_success();
    case EGL_BLUE_SIZE:
      *value = c->blue_size;
      return bool_success();
    case EGL_ALPHA_SIZE:
      *value = c->alpha_size;
      return bool_success();
    case EGL_DEPTH_SIZE:
      *value = c->depth_size;
      return bool_success();
    case EGL_STENCIL_SIZE:
      *value = c->stencil_size;
      return bool_success();
    case EGL_SAMPLES:
      *value = c->samples;
      return bool_success();
    case EGL_SAMPLE_BUFFERS:
      *value = c->sample_buffers;
      return bool_success();
    case EGL_NATIVE_VISUAL_ID:
      *value = c->native_visual_id;
      return bool_success();
    case EGL_NATIVE_VISUAL_TYPE:
      *value = c->native_visual_type;
      return bool_success();
    case EGL_NATIVE_RENDERABLE:
      *value = c->native_renderable;
      return bool_success();
    case EGL_CONFIG_CAVEAT:
      *value = c->config_caveat;
      return bool_success();
    case EGL_BIND_TO_TEXTURE_RGB:
      *value = c->bind_to_texture_rgb;
      return bool_success();
    case EGL_BIND_TO_TEXTURE_RGBA:
      *value = c->bind_to_texture_rgba;
      return bool_success();
    case EGL_RENDERABLE_TYPE:
      *value = c->renderable_type;
      return bool_success();
    case EGL_CONFORMANT:
      *value = c->conformant;
      return bool_success();
    case EGL_SURFACE_TYPE:
      *value = c->surface_type;
      return bool_success();
    case EGL_MAX_PBUFFER_WIDTH:
      *value = c->max_pbuffer_width;
      return bool_success();
    case EGL_MAX_PBUFFER_HEIGHT:
      *value = c->max_pbuffer_height;
      return bool_success();
    case EGL_MAX_PBUFFER_PIXELS:
      *value = c->max_pbuffer_pixels;
      return bool_success();
    case EGL_MIN_SWAP_INTERVAL:
      *value = c->min_swap_interval;
      return bool_success();
    case EGL_MAX_SWAP_INTERVAL:
      *value = c->max_swap_interval;
      return bool_success();
    default:
      return bool_failure(EGL_BAD_ATTRIBUTE);
  }
}

EGLDisplay _eglGetDisplay(EGLNativeDisplayType display_id) {
  return &mock_display;
}

EGLint _eglGetError() {
  EGLint error = mock_error;
  mock_error = EGL_SUCCESS;
  return error;
}

void (*_eglGetProcAddress(const char* procname))(void) {
  mock_error = EGL_SUCCESS;
  return nullptr;
}

EGLBoolean _eglInitialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
  if (!check_display(dpy)) {
    return EGL_FALSE;
  }

  if (!display_initialized) {
    mock_config.config_id = 1;
    mock_config.buffer_size = 32;
    mock_config.color_buffer_type = EGL_RGB_BUFFER;
    mock_config.transparent_type = EGL_NONE;
    mock_config.level = 1;
    mock_config.red_size = 8;
    mock_config.green_size = 8;
    mock_config.blue_size = 8;
    mock_config.alpha_size = 0;
    mock_config.depth_size = 0;
    mock_config.stencil_size = 0;
    mock_config.samples = 0;
    mock_config.sample_buffers = 0;
    mock_config.native_visual_id = 1;
    mock_config.native_visual_type = 0;
    mock_config.native_renderable = EGL_TRUE;
    mock_config.config_caveat = EGL_NONE;
    mock_config.bind_to_texture_rgb = EGL_TRUE;
    mock_config.bind_to_texture_rgba = EGL_FALSE;
    mock_config.renderable_type = EGL_OPENGL_ES2_BIT;
    mock_config.conformant = EGL_OPENGL_ES2_BIT;
    mock_config.surface_type = EGL_WINDOW_BIT | EGL_PBUFFER_BIT;
    mock_config.max_pbuffer_width = 1024;
    mock_config.max_pbuffer_height = 1024;
    mock_config.max_pbuffer_pixels = 1024 * 1024;
    mock_config.min_swap_interval = 0;
    mock_config.max_swap_interval = 1000;
    display_initialized = true;
  }

  if (major != nullptr) {
    *major = 1;
  }
  if (minor != nullptr) {
    *minor = 5;
  }

  return bool_success();
}

EGLBoolean _eglMakeCurrent(EGLDisplay dpy,
                           EGLSurface draw,
                           EGLSurface read,
                           EGLContext ctx) {
  if (!check_display(dpy) || !check_initialized(dpy)) {
    return EGL_FALSE;
  }

  return bool_success();
}

EGLBoolean _eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) {
  if (!check_display(dpy) || !check_initialized(dpy)) {
    return EGL_FALSE;
  }

  return bool_success();
}

static void _glBindFramebuffer(GLenum target, GLuint framebuffer) {}

static void _glBindTexture(GLenum target, GLuint texture) {}

void _glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers) {}

void _glDeleteTextures(GLsizei n, const GLuint* textures) {}

static void _glFramebufferTexture2D(GLenum target,
                                    GLenum attachment,
                                    GLenum textarget,
                                    GLuint texture,
                                    GLint level) {}

static void _glGenTextures(GLsizei n, GLuint* textures) {
  for (GLsizei i = 0; i < n; i++) {
    textures[i] = 0;
  }
}

static void _glGenFramebuffers(GLsizei n, GLuint* framebuffers) {
  for (GLsizei i = 0; i < n; i++) {
    framebuffers[i] = 0;
  }
}

static void _glTexParameterf(GLenum target, GLenum pname, GLfloat param) {}

static void _glTexParameteri(GLenum target, GLenum pname, GLint param) {}

static void _glTexImage2D(GLenum target,
                          GLint level,
                          GLint internalformat,
                          GLsizei width,
                          GLsizei height,
                          GLint border,
                          GLenum format,
                          GLenum type,
                          const void* pixels) {}

static GLenum _glGetError() {
  return GL_NO_ERROR;
}

bool epoxy_has_gl_extension(const char* extension) {
  return false;
}

bool epoxy_is_desktop_gl(void) {
  return false;
}

int epoxy_gl_version(void) {
  return 0;
}

#ifdef __GNUC__
#define CONSTRUCT(_func) static void _func(void) __attribute__((constructor));
#define DESTRUCT(_func) static void _func(void) __attribute__((destructor));
#elif defined(_MSC_VER) && (_MSC_VER >= 1500)
#define CONSTRUCT(_func)                                   \
  static void _func(void);                                 \
  static int _func##_wrapper(void) {                       \
    _func();                                               \
    return 0;                                              \
  }                                                        \
  __pragma(section(".CRT$XCU", read)) __declspec(allocate( \
      ".CRT$XCU")) static int (*_array##_func)(void) = _func##_wrapper;

#else
#error "You will need constructor support for your compiler"
#endif

CONSTRUCT(library_init)

EGLBoolean (*epoxy_eglBindAPI)(EGLenum api);
EGLBoolean (*epoxy_eglChooseConfig)(EGLDisplay dpy,
                                    const EGLint* attrib_list,
                                    EGLConfig* configs,
                                    EGLint config_size,
                                    EGLint* num_config);
EGLContext (*epoxy_eglCreateContext)(EGLDisplay dpy,
                                     EGLConfig config,
                                     EGLContext share_context,
                                     const EGLint* attrib_list);
EGLSurface (*epoxy_eglCreatePbufferSurface)(EGLDisplay dpy,
                                            EGLConfig config,
                                            const EGLint* attrib_list);
EGLSurface (*epoxy_eglCreateWindowSurface)(EGLDisplay dpy,
                                           EGLConfig config,
                                           EGLNativeWindowType win,
                                           const EGLint* attrib_list);
EGLBoolean (*epoxy_eglGetConfigAttrib)(EGLDisplay dpy,
                                       EGLConfig config,
                                       EGLint attribute,
                                       EGLint* value);
EGLDisplay (*epoxy_eglGetDisplay)(EGLNativeDisplayType display_id);
EGLint (*epoxy_eglGetError)();
void (*(*epoxy_eglGetProcAddress)(const char* procname))(void);
EGLBoolean (*epoxy_eglInitialize)(EGLDisplay dpy, EGLint* major, EGLint* minor);
EGLBoolean (*epoxy_eglMakeCurrent)(EGLDisplay dpy,
                                   EGLSurface draw,
                                   EGLSurface read,
                                   EGLContext ctx);
EGLBoolean (*epoxy_eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);

void (*epoxy_glBindFramebuffer)(GLenum target, GLuint framebuffer);
void (*epoxy_glBindTexture)(GLenum target, GLuint texture);
void (*epoxy_glDeleteFramebuffers)(GLsizei n, const GLuint* framebuffers);
void (*epoxy_glDeleteTextures)(GLsizei n, const GLuint* textures);
void (*epoxy_glFramebufferTexture2D)(GLenum target,
                                     GLenum attachment,
                                     GLenum textarget,
                                     GLuint texture,
                                     GLint level);
void (*epoxy_glGenFramebuffers)(GLsizei n, GLuint* framebuffers);
void (*epoxy_glGenTextures)(GLsizei n, GLuint* textures);
void (*epoxy_glTexParameterf)(GLenum target, GLenum pname, GLfloat param);
void (*epoxy_glTexParameteri)(GLenum target, GLenum pname, GLint param);
void (*epoxy_glTexImage2D)(GLenum target,
                           GLint level,
                           GLint internalformat,
                           GLsizei width,
                           GLsizei height,
                           GLint border,
                           GLenum format,
                           GLenum type,
                           const void* pixels);
GLenum (*epoxy_glGetError)();

static void library_init() {
  epoxy_eglBindAPI = _eglBindAPI;
  epoxy_eglChooseConfig = _eglChooseConfig;
  epoxy_eglCreateContext = _eglCreateContext;
  epoxy_eglCreatePbufferSurface = _eglCreatePbufferSurface;
  epoxy_eglCreateWindowSurface = _eglCreateWindowSurface;
  epoxy_eglGetConfigAttrib = _eglGetConfigAttrib;
  epoxy_eglGetDisplay = _eglGetDisplay;
  epoxy_eglGetError = _eglGetError;
  epoxy_eglGetProcAddress = _eglGetProcAddress;
  epoxy_eglInitialize = _eglInitialize;
  epoxy_eglMakeCurrent = _eglMakeCurrent;
  epoxy_eglSwapBuffers = _eglSwapBuffers;

  epoxy_glBindFramebuffer = _glBindFramebuffer;
  epoxy_glBindTexture = _glBindTexture;
  epoxy_glDeleteFramebuffers = _glDeleteFramebuffers;
  epoxy_glDeleteTextures = _glDeleteTextures;
  epoxy_glFramebufferTexture2D = _glFramebufferTexture2D;
  epoxy_glGenFramebuffers = _glGenFramebuffers;
  epoxy_glGenTextures = _glGenTextures;
  epoxy_glTexParameterf = _glTexParameterf;
  epoxy_glTexParameteri = _glTexParameteri;
  epoxy_glTexImage2D = _glTexImage2D;
  epoxy_glGetError = _glGetError;
}
