/*
 * Copyright (C) 2011 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.
 */
// #define LOG_NDEBUG 0

#include "base/logging.h"
#include "base/utilities.h"
#include "core/gl_env.h"
#include "core/shader_program.h"
#include "core/vertex_frame.h"
#include "system/window.h"

#include <map>
#include <string>
#include <EGL/eglext.h>

namespace android {
namespace filterfw {

GLEnv::GLEnv()
  : display_(EGL_NO_DISPLAY),
    context_id_(0),
    surface_id_(0),
    max_surface_id_(0),
    created_context_(false),
    created_surface_(false),
    initialized_(false) {
}

GLEnv::~GLEnv() {
  // Destroy surfaces
  for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin();
       it != surfaces_.end();
       ++it) {
    if (it->first != 0 || created_surface_) {
      eglDestroySurface(display(), it->second.first);
      if (it->second.second) {
        it->second.second->Destroy();
        delete it->second.second;
      }
    }
  }

  // Destroy contexts
  for (std::map<int, EGLContext>::iterator it = contexts_.begin();
       it != contexts_.end();
       ++it) {
    if (it->first != 0 || created_context_)
      eglDestroyContext(display(), it->second);
  }

  // Destroy attached shaders and frames
  STLDeleteValues(&attached_shaders_);
  STLDeleteValues(&attached_vframes_);

  // Destroy display
  if (initialized_)
    eglTerminate(display());

  // Log error if this did not work
  if (CheckEGLError("TearDown!"))
    LOGE("GLEnv: Error tearing down GL Environment!");
}

bool GLEnv::IsInitialized() const {
  return (contexts_.size() > 0 &&
          surfaces_.size() > 0 &&
          display_ != EGL_NO_DISPLAY);
}

bool GLEnv::Deactivate() {
  eglMakeCurrent(display(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
  return !CheckEGLError("eglMakeCurrent");
}

bool GLEnv::Activate() {
  LOGV("Activate()");
  if (display()   != eglGetCurrentDisplay() ||
      context()   != eglGetCurrentContext() ||
      surface()   != eglGetCurrentSurface(EGL_DRAW)) {
    // Make sure we are initialized
    if (context() == EGL_NO_CONTEXT || surface() == EGL_NO_SURFACE)
      return false;

    // Make our context current
    LOGV("eglMakeCurrent");
    eglMakeCurrent(display(), surface(), surface(), context());

    return !CheckEGLMakeCurrentError();
  }
  return true;
}

bool GLEnv::SwapBuffers() {
  const bool result = eglSwapBuffers(display(), surface()) == EGL_TRUE;
  return !CheckEGLError("eglSwapBuffers") && result;
}

bool GLEnv::InitWithCurrentContext() {
  if (IsInitialized())
    return true;

  display_     = eglGetCurrentDisplay();
  contexts_[0] = eglGetCurrentContext();
  surfaces_[0] = SurfaceWindowPair(eglGetCurrentSurface(EGL_DRAW), NULL);

  return (context() != EGL_NO_CONTEXT) &&
         (display() != EGL_NO_DISPLAY) &&
         (surface() != EGL_NO_SURFACE);
}

bool GLEnv::InitWithNewContext() {
  if (IsInitialized()) {
    LOGE("GLEnv: Attempting to reinitialize environment!");
    return false;
  }

  display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  if (CheckEGLError("eglGetDisplay")) return false;

  EGLint majorVersion;
  EGLint minorVersion;
  eglInitialize(display(), &majorVersion, &minorVersion);
  if (CheckEGLError("eglInitialize")) return false;
  initialized_ = true;

  // Configure context/surface
  EGLConfig config;
  EGLint numConfigs = -1;

  // TODO(renn): Do we need the window bit here?
  // TODO: Currently choosing the config that includes all
  // This is not needed if the encoding is not being used
  EGLint configAttribs[] = {
    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    EGL_RED_SIZE, 8,
    EGL_GREEN_SIZE, 8,
    EGL_BLUE_SIZE, 8,
    EGL_RECORDABLE_ANDROID, EGL_TRUE,
    EGL_NONE
  };

  eglChooseConfig(display(), configAttribs, &config, 1, &numConfigs);
  if (numConfigs < 1) {
    LOGE("GLEnv::Init: No suitable EGL configuration found!");
    return false;
  }

  // Create dummy surface using a SurfaceTexture
  sp<SurfaceTexture> st = new SurfaceTexture(0, false);
  window_ = new SurfaceTextureClient(st);

  surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
  if (CheckEGLError("eglCreateWindowSurface")) return false;

  // Create context
  EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
  contexts_[0] = eglCreateContext(display(),
                                  config,
                                  EGL_NO_CONTEXT,
                                  context_attribs);
  if (CheckEGLError("eglCreateContext")) return false;

  created_context_ = created_surface_ = true;

  return true;
}

bool GLEnv::IsActive() const {
  LOGV("IsActive()");
  return context() == eglGetCurrentContext()
    &&   display() == eglGetCurrentDisplay()
    &&   surface() == eglGetCurrentSurface(EGL_DRAW);
}

bool GLEnv::IsContextActive() const {
  return context() == eglGetCurrentContext();
}

bool GLEnv::IsAnyContextActive() {
  return eglGetCurrentContext() != EGL_NO_CONTEXT;
}

int GLEnv::AddWindowSurface(const EGLSurface& surface, WindowHandle* window_handle) {
  const int id = ++max_surface_id_;
  surfaces_[id] = SurfaceWindowPair(surface, window_handle);
  return id;
}

int GLEnv::AddSurface(const EGLSurface& surface) {
  return AddWindowSurface(surface, NULL);
}

bool GLEnv::SwitchToSurfaceId(int surface_id) {
  LOGV("SwitchToSurfaceId");
  if (surface_id_ != surface_id) {
    const SurfaceWindowPair* surface = FindOrNull(surfaces_, surface_id);
    if (surface) {
      bool wasActive = IsActive();
      surface_id_ = surface_id;
      return wasActive ? Activate() : true;
    }
    return false;
  }
  return true;
}

bool GLEnv::ReleaseSurfaceId(int surface_id) {
  if (surface_id > 0) {
    const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_, surface_id);
    if (surface_window_pair) {
      if (surface_id_ == surface_id)
        SwitchToSurfaceId(0);
      eglDestroySurface(display(), surface_window_pair->first);
      if (surface_window_pair->second) {
        surface_window_pair->second->Destroy();
        delete surface_window_pair->second;
      }
      surfaces_.erase(surface_id);
      return true;
    }
  }
  return false;
}

bool GLEnv::SetSurfaceTimestamp(int64_t timestamp) {
  if (surface_id_ > 0) {
    const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_,
            surface_id_);
    if (surface_window_pair) {
      ANativeWindow *window = static_cast<ANativeWindow*>(
              surface_window_pair->second->InternalHandle());
      native_window_set_buffers_timestamp(window, timestamp);
      return true;
    }
  }
  return false;
}

int GLEnv::FindSurfaceIdForWindow(const WindowHandle* window_handle) {
  for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin();
       it != surfaces_.end();
       ++it) {
    const WindowHandle* my_handle = it->second.second;
    if (my_handle && my_handle->Equals(window_handle)) {
      return it->first;
    }
  }
  return -1;
}


int GLEnv::AddContext(const EGLContext& context) {
  const int id = contexts_.size();
  contexts_[id] = context;
  return id;
}

bool GLEnv::SwitchToContextId(int context_id) {
  const EGLContext* context = FindOrNull(contexts_, context_id);
  if (context) {
    if (context_id_ != context_id) {
      context_id_ = context_id;
      return Activate();
    }
    return true;
  }
  return false;
}

void GLEnv::ReleaseContextId(int context_id) {
  if (context_id > 0) {
    const EGLContext* context = FindOrNull(contexts_, context_id);
    if (context) {
      contexts_.erase(context_id);
      if (context_id_ == context_id && IsActive())
        SwitchToContextId(0);
      eglDestroyContext(display(), *context);
    }
  }
}

bool GLEnv::CheckGLError(const std::string& op) {
  bool err = false;
  for (GLint error = glGetError(); error; error = glGetError()) {
    LOGE("GL Error: Operation '%s' caused GL error (0x%x)\n",
         op.c_str(),
         error);
    err = true;
  }
  return err;
}

bool GLEnv::CheckEGLError(const std::string& op) {
  bool err = false;
  for (EGLint error = eglGetError();
       error != EGL_SUCCESS;
       error = eglGetError()) {
    LOGE("EGL Error: Operation '%s' caused EGL error (0x%x)\n",
         op.c_str(),
         error);
    err = true;
  }
  return err;
}

bool GLEnv::CheckEGLMakeCurrentError() {
  bool err = false;
  for (EGLint error = eglGetError();
       error != EGL_SUCCESS;
       error = eglGetError()) {
    switch (error) {
      case EGL_BAD_DISPLAY:
        LOGE("EGL Error: Attempting to activate context with bad display!");
        break;
      case EGL_BAD_SURFACE:
        LOGE("EGL Error: Attempting to activate context with bad surface!");
        break;
      case EGL_BAD_ACCESS:
        LOGE("EGL Error: Attempting to activate context, which is "
             "already active in another thread!");
        break;
      default:
        LOGE("EGL Error: Making EGL rendering context current caused "
             "error: 0x%x\n", error);
    }
    err = true;
  }
  return err;
}

GLuint GLEnv::GetCurrentProgram() {
  GLint result;
  glGetIntegerv(GL_CURRENT_PROGRAM, &result);
  LOG_ASSERT(result >= 0);
  return static_cast<GLuint>(result);
}

EGLDisplay GLEnv::GetCurrentDisplay() {
  return eglGetCurrentDisplay();
}

int GLEnv::NumberOfComponents(GLenum type) {
  switch (type) {
    case GL_BOOL:
    case GL_FLOAT:
    case GL_INT:
      return 1;
    case GL_BOOL_VEC2:
    case GL_FLOAT_VEC2:
    case GL_INT_VEC2:
      return 2;
    case GL_INT_VEC3:
    case GL_FLOAT_VEC3:
    case GL_BOOL_VEC3:
      return 3;
    case GL_BOOL_VEC4:
    case GL_FLOAT_VEC4:
    case GL_INT_VEC4:
    case GL_FLOAT_MAT2:
      return 4;
    case GL_FLOAT_MAT3:
      return 9;
    case GL_FLOAT_MAT4:
      return 16;
    default:
      return 0;
  }
}

void GLEnv::AttachShader(int key, ShaderProgram* shader) {
  ShaderProgram* existingShader = ShaderWithKey(key);
  if (existingShader)
    delete existingShader;
  attached_shaders_[key] = shader;
}

void GLEnv::AttachVertexFrame(int key, VertexFrame* frame) {
  VertexFrame* existingFrame = VertexFrameWithKey(key);
  if (existingFrame)
    delete existingFrame;
  attached_vframes_[key] = frame;
}

ShaderProgram* GLEnv::ShaderWithKey(int key) {
  return FindPtrOrNull(attached_shaders_, key);
}

VertexFrame* GLEnv::VertexFrameWithKey(int key) {
  return FindPtrOrNull(attached_vframes_, key);
}

} // namespace filterfw
} // namespace android
