#ifndef ENGINE_RENDERER_OPENGL_RENDERER_OPENGL_H
#define ENGINE_RENDERER_OPENGL_RENDERER_OPENGL_H

#include <array>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "engine/renderer/opengl/opengl.h"
#include "engine/renderer/renderer.h"

#if defined(__ANDROID__)
struct ANativeWindow;
#endif

namespace eng {

class RendererOpenGL final : public Renderer {
 public:
  RendererOpenGL(base::Closure context_lost_cb);
  ~RendererOpenGL() final;

  bool Initialize(Platform* platform) final;
  void Shutdown() final;

  bool IsInitialzed() const final { return is_initialized_; }

  void OnWindowResized(int width, int height) final;

  int GetScreenWidth() const final;
  int GetScreenHeight() const final;

  void SetViewport(int x, int y, int width, int height) final;
  void ResetViewport() final;

  void SetScissor(int x, int y, int width, int height) final;
  void ResetScissor() final;

  uint64_t CreateGeometry(std::unique_ptr<Mesh> mesh) final;
  void DestroyGeometry(uint64_t resource_id) final;
  void Draw(uint64_t resource_id,
            uint64_t num_indices = 0,
            uint64_t start_offset = 0) final;

  uint64_t CreateTexture() final;
  void UpdateTexture(uint64_t resource_id, std::unique_ptr<Image> image) final;
  void DestroyTexture(uint64_t resource_id) final;
  void ActivateTexture(uint64_t resource_id, uint64_t texture_unit) final;

  uint64_t CreateShader(std::unique_ptr<ShaderSource> source,
                        const VertexDescription& vertex_description,
                        Primitive primitive,
                        bool enable_depth_test) final;
  void DestroyShader(uint64_t resource_id) final;
  void ActivateShader(uint64_t resource_id) final;

  void SetUniform(uint64_t resource_id,
                  const std::string& name,
                  const base::Vector2f& val) final;
  void SetUniform(uint64_t resource_id,
                  const std::string& name,
                  const base::Vector3f& val) final;
  void SetUniform(uint64_t resource_id,
                  const std::string& name,
                  const base::Vector4f& val) final;
  void SetUniform(uint64_t resource_id,
                  const std::string& name,
                  const base::Matrix4f& val) final;
  void SetUniform(uint64_t resource_id,
                  const std::string& name,
                  float val) final;
  void SetUniform(uint64_t resource_id, const std::string& name, int val) final;
  void UploadUniforms(uint64_t resource_id) final {}

  void PrepareForDrawing() final;
  void Present() final;

  size_t GetAndResetFPS() final;

  const char* GetDebugName() final { return "OpenGL"; }

  RendererType GetRendererType() final { return RendererType::kOpenGL; }

 private:
  struct GeometryOpenGL {
    struct Element {
      GLsizei num_elements;
      GLenum type;
      size_t vertex_offset;
    };

    GLsizei num_vertices = 0;
    GLsizei num_indices = 0;
    GLenum primitive = 0;
    GLenum index_type = 0;
    std::vector<Element> vertex_layout;
    GLuint vertex_size = 0;
    GLuint vertex_array_id = 0;
    GLuint vertex_buffer_id = 0;
    GLuint index_buffer_id = 0;
  };

  struct ShaderOpenGL {
    GLuint id = 0;
    std::vector<std::pair<size_t,  // Uniform name hash
                          GLuint   // Uniform index
                          >>
        uniforms;
    bool enable_depth_test = false;
  };

  std::unordered_map<uint64_t, GeometryOpenGL> geometries_;
  std::unordered_map<uint64_t, ShaderOpenGL> shaders_;
  std::unordered_map<uint64_t, GLuint> textures_;
  uint64_t last_resource_id_ = 0;

  GLuint active_shader_id_ = 0;
  std::array<GLuint, kMaxTextureUnits> active_texture_id_ = {};

  bool vertex_array_objects_ = false;
  bool npot_ = false;

  bool is_initialized_ = false;

  // Stats.
  size_t fps_ = 0;

  int screen_width_ = 0;
  int screen_height_ = 0;

#if defined(__ANDROID__)
  ANativeWindow* window_ = nullptr;
#elif defined(__linux__)
  Display* display_ = NULL;
  Window window_ = 0;
  GLXContext glx_context_ = NULL;
#elif defined(_WIN32)
  HWND wnd_ = nullptr;
  HDC dc_ = nullptr;
  HGLRC glrc_ = nullptr;
#endif

  bool InitCommon();
  void ShutdownInternal();
  void OnDestroy();
  void ContextLost();
  void DestroyAllResources();

  bool SetupVertexLayout(const VertexDescription& vd,
                         GLuint vertex_size,
                         bool use_vao,
                         std::vector<GeometryOpenGL::Element>& vertex_layout);
  GLuint CreateShader(const char* source, GLenum type);
  bool BindAttributeLocation(GLuint id, const VertexDescription& vd);
  GLint GetUniformLocation(GLuint id,
                           const std::string& name,
                           std::vector<std::pair<size_t, GLuint>>& uniforms);
};

}  // namespace eng

#endif  // ENGINE_RENDERER_OPENGL_RENDERER_OPENGL_H