// Copyright 2018 Google LLC
//
// 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
//
//     https://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.

// This file contains various utility methods related to SDL and OpenGL,
// primarily RAII wrappers around the C types used by those.

#ifndef THIRD_PARTY_QUIC_TRACE_TOOLS_SDL_UTIL_H_
#define THIRD_PARTY_QUIC_TRACE_TOOLS_SDL_UTIL_H_

#include <GL/glew.h>
#include <SDL.h>

#include <memory>

#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/memory/memory.h"

namespace quic_trace {
namespace render {

// Scoped global initialization for SDL.
class ScopedSDL {
 public:
  ScopedSDL() { CHECK_EQ(0, SDL_Init(SDL_INIT_VIDEO)); }
  ScopedSDL(const ScopedSDL&) = delete;
  ScopedSDL& operator=(const ScopedSDL&) = delete;

  ~ScopedSDL() { SDL_Quit(); }
};

// A helper class to automatically delete SDL objects once they go out of scope.
template <typename T, void (*D)(T*)>
class ScopedSdlType {
 public:
  ScopedSdlType() : value_(nullptr, D) {}
  ScopedSdlType(T* value) : value_(value, D) {}

  T* get() const { return value_.get(); }
  T* operator*() const { return value_.get(); }
  T* operator->() const { return value_.get(); }

 private:
  std::unique_ptr<T, void (*)(T*)> value_;
};

using ScopedSdlWindow = ScopedSdlType<SDL_Window, SDL_DestroyWindow>;
using ScopedSdlSurface = ScopedSdlType<SDL_Surface, SDL_FreeSurface>;

class OpenGlContext {
 public:
  OpenGlContext(SDL_Window* window);
  ~OpenGlContext() { SDL_GL_DeleteContext(context_); }

  SDL_GLContext context() const { return context_; }

 private:
  SDL_GLContext context_;
};

// Utility wrapper around an OpenGL shader.
class GlShader {
 public:
  GlShader(GLenum type) { shader_ = glCreateShader(type); }

  ~GlShader() { glDeleteShader(shader_); }

  GlShader(const GlShader&) = delete;
  GlShader& operator=(const GlShader&) = delete;

  bool Compile(const char* source);
  std::string GetCompileInfoLog();
  void CompileOrDie(const char* source);

  GLuint operator*() const { return shader_; }

 private:
  GLuint shader_;
};

class GlProgram {
 public:
  GlProgram() { program_ = glCreateProgram(); }

  ~GlProgram() { glDeleteProgram(program_); }

  GlProgram(const GlProgram&) = delete;
  GlProgram& operator=(const GlProgram&) = delete;

  void Attach(const GlShader& shader) { glAttachShader(program_, *shader); }

  bool Link() {
    glLinkProgram(program_);

    GLint link_status = GL_FALSE;
    glGetProgramiv(program_, GL_LINK_STATUS, &link_status);
    return link_status;
  }

  void SetUniform(const char* name, float value) const {
    GLint var = glGetUniformLocation(program_, name);
    DCHECK_NE(var, -1) << "Failed to locate a uniform " << name;
    glUniform1f(var, value);
  }

  void SetUniform(const char* name, float x, float y) const {
    GLint var = glGetUniformLocation(program_, name);
    DCHECK_NE(var, -1) << "Failed to locate a uniform " << name;
    glUniform2f(var, x, y);
  }

  void SetUniform(const char* name, int value) const {
    GLint var = glGetUniformLocation(program_, name);
    DCHECK_NE(var, -1) << "Failed to locate a uniform " << name;
    glUniform1i(var, value);
  }

  GLuint operator*() const { return program_; }

 private:
  GLuint program_;
};

template <GLenum BufferType>
class GlBuffer {
 public:
  GlBuffer() {
    glGenBuffers(1, &buffer_);
    glBindBuffer(BufferType, buffer_);
  }
  ~GlBuffer() { glDeleteBuffers(1, &buffer_); }

  GlBuffer(const GlBuffer&) = delete;
  GlBuffer& operator=(const GlBuffer&) = delete;

  GLuint operator*() const { return buffer_; }

 private:
  GLuint buffer_;
};

using GlVertexBuffer = GlBuffer<GL_ARRAY_BUFFER>;
using GlUniformBuffer = GlBuffer<GL_UNIFORM_BUFFER>;
using GlElementBuffer = GlBuffer<GL_ELEMENT_ARRAY_BUFFER>;

class GlVertexArray {
 public:
  GlVertexArray() {
    glGenVertexArrays(1, &array_);
    glBindVertexArray(array_);
  }
  ~GlVertexArray() { glDeleteVertexArrays(1, &array_); }

  GlVertexArray(const GlVertexArray&) = delete;
  GlVertexArray& operator=(const GlVertexArray&) = delete;

  GLuint operator*() const { return array_; }

 private:
  GLuint array_;
};

class GlVertexArrayAttrib {
 public:
  GlVertexArrayAttrib(const GlProgram& program, const char* name) {
    attribute_ = glGetAttribLocation(*program, name);
    DCHECK_NE(attribute_, -1) << "Failed to locate attribute " << name;
    glEnableVertexAttribArray(attribute_);
  }

  ~GlVertexArrayAttrib() { glDisableVertexAttribArray(attribute_); }

  GlVertexArrayAttrib(const GlVertexArrayAttrib&) = delete;
  GlVertexArrayAttrib& operator=(const GlVertexArrayAttrib&) = delete;

  GLint operator*() const { return attribute_; }

 private:
  GLint attribute_;
};

class GlTexture {
 public:
  GlTexture() {
    glGenTextures(1, &texture_);
    glBindTexture(GL_TEXTURE_2D, texture_);
  }
  ~GlTexture() { glDeleteTextures(1, &texture_); }

  GlTexture(const GlTexture&) = delete;
  GlTexture& operator=(const GlTexture&) = delete;

  GLuint operator*() const { return texture_; }

 private:
  GLuint texture_;
};

}  // namespace render
}  // namespace quic_trace

#endif  // THIRD_PARTY_QUIC_TRACE_TOOLS_SDL_UTIL_H_
