// 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.

#pragma once

#include <functional>
#include <string>
#include <vector>

#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/trace_event.h"
#include "impeller/renderer/backend/gles/capabilities_gles.h"
#include "impeller/renderer/backend/gles/description_gles.h"
#include "impeller/renderer/backend/gles/gles.h"

namespace impeller {

const char* GLErrorToString(GLenum value);

struct AutoErrorCheck {
  const PFNGLGETERRORPROC error_fn;
  const char* name;

  AutoErrorCheck(PFNGLGETERRORPROC error, const char* name)
      : error_fn(error), name(name) {}

  ~AutoErrorCheck() {
    if (error_fn) {
      auto error = error_fn();
      FML_CHECK(error == GL_NO_ERROR)
          << "GL Error " << GLErrorToString(error) << "(" << error << ")"
          << " encountered on call to " << name;
    }
  }
};

template <class T>
struct GLProc {
  using GLFunctionType = T;

  //----------------------------------------------------------------------------
  /// The name of the GL function.
  ///
  const char* name = nullptr;

  //----------------------------------------------------------------------------
  /// The pointer to the GL function.
  ///
  GLFunctionType* function = nullptr;

  //----------------------------------------------------------------------------
  /// An optional error function. If present, all calls will be followed by an
  /// error check.
  ///
  PFNGLGETERRORPROC error_fn = nullptr;

  //----------------------------------------------------------------------------
  /// @brief      Call the GL function with the appropriate parameters. Lookup
  ///             the documentation for the GL function being called to
  ///             understand the arguments and return types. The arguments
  ///             types must match and will be type checked.
  ///
  template <class... Args>
  auto operator()(Args&&... args) const {
#ifdef IMPELLER_ERROR_CHECK_ALL_GL_CALLS
    AutoErrorCheck error(error_fn, name);
#endif  // IMPELLER_ERROR_CHECK_ALL_GL_CALLS
#ifdef IMPELLER_TRACE_ALL_GL_CALLS
    TRACE_EVENT0("impeller", name);
#endif  // IMPELLER_TRACE_ALL_GL_CALLS
    return function(std::forward<Args>(args)...);
  }

  constexpr bool IsAvailable() const { return function != nullptr; }

  void Reset() {
    name = nullptr;
    function = nullptr;
    error_fn = nullptr;
  }
};

#define FOR_EACH_IMPELLER_PROC(PROC)         \
  PROC(ActiveTexture);                       \
  PROC(AttachShader);                        \
  PROC(BindAttribLocation);                  \
  PROC(BindBuffer);                          \
  PROC(BindFramebuffer);                     \
  PROC(BindRenderbuffer);                    \
  PROC(BindTexture);                         \
  PROC(BlendEquationSeparate);               \
  PROC(BlendFuncSeparate);                   \
  PROC(BufferData);                          \
  PROC(CheckFramebufferStatus);              \
  PROC(Clear);                               \
  PROC(ClearColor);                          \
  PROC(ClearDepthf);                         \
  PROC(ClearStencil);                        \
  PROC(ColorMask);                           \
  PROC(CompileShader);                       \
  PROC(CreateProgram);                       \
  PROC(CreateShader);                        \
  PROC(CullFace);                            \
  PROC(DeleteBuffers);                       \
  PROC(DeleteFramebuffers);                  \
  PROC(DeleteProgram);                       \
  PROC(DeleteRenderbuffers);                 \
  PROC(DeleteShader);                        \
  PROC(DeleteTextures);                      \
  PROC(DepthFunc);                           \
  PROC(DepthMask);                           \
  PROC(DepthRangef);                         \
  PROC(DetachShader);                        \
  PROC(Disable);                             \
  PROC(DisableVertexAttribArray);            \
  PROC(DrawArrays);                          \
  PROC(DrawElements);                        \
  PROC(Enable);                              \
  PROC(EnableVertexAttribArray);             \
  PROC(Flush);                               \
  PROC(FramebufferRenderbuffer);             \
  PROC(FramebufferTexture2D);                \
  PROC(FrontFace);                           \
  PROC(GenBuffers);                          \
  PROC(GenerateMipmap);                      \
  PROC(GenFramebuffers);                     \
  PROC(GenRenderbuffers);                    \
  PROC(GenTextures);                         \
  PROC(GetActiveUniform);                    \
  PROC(GetBooleanv);                         \
  PROC(GetFloatv);                           \
  PROC(GetFramebufferAttachmentParameteriv); \
  PROC(GetIntegerv);                         \
  PROC(GetProgramInfoLog);                   \
  PROC(GetProgramiv);                        \
  PROC(GetShaderInfoLog);                    \
  PROC(GetShaderiv);                         \
  PROC(GetString);                           \
  PROC(GetStringi);                          \
  PROC(GetUniformLocation);                  \
  PROC(IsBuffer);                            \
  PROC(IsFramebuffer);                       \
  PROC(IsProgram);                           \
  PROC(IsRenderbuffer);                      \
  PROC(IsShader);                            \
  PROC(IsTexture);                           \
  PROC(LinkProgram);                         \
  PROC(RenderbufferStorage);                 \
  PROC(Scissor);                             \
  PROC(ShaderBinary);                        \
  PROC(ShaderSource);                        \
  PROC(StencilFuncSeparate);                 \
  PROC(StencilMaskSeparate);                 \
  PROC(StencilOpSeparate);                   \
  PROC(TexImage2D);                          \
  PROC(TexParameteri);                       \
  PROC(Uniform1fv);                          \
  PROC(Uniform1i);                           \
  PROC(Uniform2fv);                          \
  PROC(Uniform3fv);                          \
  PROC(Uniform4fv);                          \
  PROC(UniformMatrix4fv);                    \
  PROC(UseProgram);                          \
  PROC(VertexAttribPointer);                 \
  PROC(Viewport);                            \
  PROC(ReadPixels);

#define FOR_EACH_IMPELLER_GLES3_PROC(PROC) PROC(BlitFramebuffer);

#define FOR_EACH_IMPELLER_EXT_PROC(PROC) \
  PROC(DiscardFramebufferEXT);           \
  PROC(PushDebugGroupKHR);               \
  PROC(PopDebugGroupKHR);                \
  PROC(ObjectLabelKHR);

enum class DebugResourceType {
  kTexture,
  kBuffer,
  kProgram,
  kShader,
  kRenderBuffer,
  kFrameBuffer,
};

class ProcTableGLES {
 public:
  using Resolver = std::function<void*(const char* function_name)>;
  ProcTableGLES(Resolver resolver);

  ~ProcTableGLES();

#define IMPELLER_PROC(name) \
  GLProc<decltype(gl##name)> name = {"gl" #name, nullptr};

  FOR_EACH_IMPELLER_PROC(IMPELLER_PROC);
  FOR_EACH_IMPELLER_GLES3_PROC(IMPELLER_PROC);
  FOR_EACH_IMPELLER_EXT_PROC(IMPELLER_PROC);

#undef IMPELLER_PROC

  bool IsValid() const;

  void ShaderSourceMapping(GLuint shader, const fml::Mapping& mapping) const;

  const DescriptionGLES* GetDescription() const;

  const CapabilitiesGLES* GetCapabilities() const;

  std::string DescribeCurrentFramebuffer() const;

  std::string GetProgramInfoLogString(GLuint program) const;

  bool IsCurrentFramebufferComplete() const;

  bool SetDebugLabel(DebugResourceType type,
                     GLint name,
                     const std::string& label) const;

  void PushDebugGroup(const std::string& string) const;

  void PopDebugGroup() const;

 private:
  bool is_valid_ = false;
  std::unique_ptr<DescriptionGLES> description_;
  std::unique_ptr<CapabilitiesGLES> capabilities_;
  GLint debug_label_max_length_ = 0;

  FML_DISALLOW_COPY_AND_ASSIGN(ProcTableGLES);
};

}  // namespace impeller
