/*
 * Copyright 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//--------------------------------------------------------------------------------
// GLContext.cpp
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// includes
//--------------------------------------------------------------------------------
#include "GLContext.h"

#include <string>
#include <unistd.h>

#include "gl3stub.h"

#include <android/log.h>

#define LOGI(...)                                                           \
  ((void)__android_log_print(                                               \
      ANDROID_LOG_INFO, "gltest", \
      __VA_ARGS__))
#define LOGW(...)                                                           \
  ((void)__android_log_print(                                               \
      ANDROID_LOG_WARN, "gltest", \
      __VA_ARGS__))
#define LOGE(...)                                                            \
  ((void)__android_log_print(                                                \
      ANDROID_LOG_ERROR, "gltest", \
      __VA_ARGS__))

namespace ndk_helper {

//--------------------------------------------------------------------------------
// eGLContext
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
// Ctor
//--------------------------------------------------------------------------------
GLContext::GLContext()
    : window_(nullptr),
      display_(EGL_NO_DISPLAY),
      surface_(EGL_NO_SURFACE),
      context_(EGL_NO_CONTEXT),
      screen_width_(0),
      screen_height_(0),
      gles_initialized_(false),
      egl_context_initialized_(false),
      es3_supported_(false) {}

void GLContext::InitGLES() {
  if (gles_initialized_) return;
  //
  // Initialize OpenGL ES 3 if available
  //
  const char* versionStr = (const char*)glGetString(GL_VERSION);
  if (strstr(versionStr, "OpenGL ES 3.") && gl3stubInit()) {
    es3_supported_ = true;
    gl_version_ = 3.0f;
  } else {
    gl_version_ = 2.0f;
  }

  gles_initialized_ = true;
}

//--------------------------------------------------------------------------------
// Dtor
//--------------------------------------------------------------------------------
GLContext::~GLContext() { Terminate(); }

bool GLContext::Init(ANativeWindow* window) {
  LOGE(__func__);
  if (egl_context_initialized_) return true;

  //
  // Initialize EGL
  //
  window_ = window;
  InitEGLSurface();
  InitEGLContext();
  InitGLES();

  egl_context_initialized_ = true;

  return true;
}

bool GLContext::InitEGLSurface() {
  display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  eglInitialize(display_, 0, 0);

  /*
   * Here specify the attributes of the desired configuration.
   * Below, we select an EGLConfig with at least 8 bits per color
   * component compatible with on-screen windows
   */
  const EGLint attribs[] = {EGL_RENDERABLE_TYPE,
                            EGL_OPENGL_ES2_BIT,  // Request opengl ES2.0
                            EGL_SURFACE_TYPE,
                            EGL_WINDOW_BIT,
                            EGL_BLUE_SIZE,
                            8,
                            EGL_GREEN_SIZE,
                            8,
                            EGL_RED_SIZE,
                            8,
                            EGL_DEPTH_SIZE,
                            24,
                            EGL_NONE};
  color_size_ = 8;
  depth_size_ = 24;

  EGLint num_configs;
  eglChooseConfig(display_, attribs, &config_, 1, &num_configs);

  if (!num_configs) {
    // Fall back to 16bit depth buffer
    const EGLint attribs[] = {EGL_RENDERABLE_TYPE,
                              EGL_OPENGL_ES2_BIT,  // Request opengl ES2.0
                              EGL_SURFACE_TYPE,
                              EGL_WINDOW_BIT,
                              EGL_BLUE_SIZE,
                              8,
                              EGL_GREEN_SIZE,
                              8,
                              EGL_RED_SIZE,
                              8,
                              EGL_DEPTH_SIZE,
                              16,
                              EGL_NONE};
    eglChooseConfig(display_, attribs, &config_, 1, &num_configs);
    depth_size_ = 16;
  }

  if (!num_configs) {
    LOGW("Unable to retrieve EGL config");
    return false;
  }

  surface_ = eglCreateWindowSurface(display_, config_, window_, NULL);
  eglQuerySurface(display_, surface_, EGL_WIDTH, &screen_width_);
  eglQuerySurface(display_, surface_, EGL_HEIGHT, &screen_height_);

  return true;
}

bool GLContext::InitEGLContext() {
  const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION,
                                    2,  // Request opengl ES2.0
                                    EGL_NONE};
  context_ = eglCreateContext(display_, config_, NULL, context_attribs);

  if (eglMakeCurrent(display_, surface_, surface_, context_) == EGL_FALSE) {
    LOGW("Unable to eglMakeCurrent");
    return false;
  }

  context_valid_ = true;
  return true;
}

EGLint GLContext::Swap() {
  bool b = eglSwapBuffers(display_, surface_);
  if (!b) {
    EGLint err = eglGetError();
    if (err == EGL_BAD_SURFACE) {
      // Recreate surface
      InitEGLSurface();
      return EGL_SUCCESS;  // Still consider glContext is valid
    } else if (err == EGL_CONTEXT_LOST || err == EGL_BAD_CONTEXT) {
      // Context has been lost!!
      context_valid_ = false;
      Terminate();
      InitEGLContext();
    }
    return err;
  }
  return EGL_SUCCESS;
}

void GLContext::Terminate() {
  if (display_ != EGL_NO_DISPLAY) {
    eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    if (context_ != EGL_NO_CONTEXT) {
      eglDestroyContext(display_, context_);
    }

    if (surface_ != EGL_NO_SURFACE) {
      eglDestroySurface(display_, surface_);
    }
    eglTerminate(display_);
  }

  display_ = EGL_NO_DISPLAY;
  context_ = EGL_NO_CONTEXT;
  surface_ = EGL_NO_SURFACE;
  window_ = nullptr;
  context_valid_ = false;
}

EGLint GLContext::Resume(ANativeWindow* window) {
  LOGE(__func__);
  if (egl_context_initialized_ == false) {
    Init(window);
    return EGL_SUCCESS;
  }

  int32_t original_widhth = screen_width_;
  int32_t original_height = screen_height_;

  LOGE("Create surface");
  // Create surface
  window_ = window;
  surface_ = eglCreateWindowSurface(display_, config_, window_, NULL);
  eglQuerySurface(display_, surface_, EGL_WIDTH, &screen_width_);
  eglQuerySurface(display_, surface_, EGL_HEIGHT, &screen_height_);

  if (screen_width_ != original_widhth || screen_height_ != original_height) {
    // Screen resized
    LOGI("Screen resized");
  }

  if (eglMakeCurrent(display_, surface_, surface_, context_) == EGL_TRUE)
    return EGL_SUCCESS;

  EGLint err = eglGetError();
  LOGW("Unable to eglMakeCurrent %d", err);

  if (err == EGL_CONTEXT_LOST) {
    LOGE("Recreate context");
    // Recreate context
    LOGI("Re-creating egl context");
    InitEGLContext();
  } else {
    LOGE("Recreate surface");
    // Recreate surface
    Terminate();
    InitEGLSurface();
    InitEGLContext();
  }

  return err;
}

void GLContext::Suspend() {
  if (surface_ != EGL_NO_SURFACE) {
    eglDestroySurface(display_, surface_);
    surface_ = EGL_NO_SURFACE;
  }
}

bool GLContext::Invalidate() {
  Terminate();

  egl_context_initialized_ = false;
  return true;
}

bool GLContext::CheckExtension(const char* extension) {
  if (extension == NULL) return false;

  std::string extensions = std::string((char*)glGetString(GL_EXTENSIONS));
  std::string str = std::string(extension);
  str.append(" ");

  size_t pos = 0;
  if (extensions.find(extension, pos) != std::string::npos) {
    return true;
  }

  return false;
}

}  // namespace ndkHelper
