#include "flutter_angle_texture.h"

#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <flutter/texture_registrar.h>

#include <thread>

namespace thermion_flutter {

static void logEglError(const char *name) noexcept {
  const char *err;
  switch (eglGetError()) {
  case EGL_NOT_INITIALIZED:
    err = "EGL_NOT_INITIALIZED";
    break;
  case EGL_BAD_ACCESS:
    err = "EGL_BAD_ACCESS";
    break;
  case EGL_BAD_ALLOC:
    err = "EGL_BAD_ALLOC";
    break;
  case EGL_BAD_ATTRIBUTE:
    err = "EGL_BAD_ATTRIBUTE";
    break;
  case EGL_BAD_CONTEXT:
    err = "EGL_BAD_CONTEXT";
    break;
  case EGL_BAD_CONFIG:
    err = "EGL_BAD_CONFIG";
    break;
  case EGL_BAD_CURRENT_SURFACE:
    err = "EGL_BAD_CURRENT_SURFACE";
    break;
  case EGL_BAD_DISPLAY:
    err = "EGL_BAD_DISPLAY";
    break;
  case EGL_BAD_SURFACE:
    err = "EGL_BAD_SURFACE";
    break;
  case EGL_BAD_MATCH:
    err = "EGL_BAD_MATCH";
    break;
  case EGL_BAD_PARAMETER:
    err = "EGL_BAD_PARAMETER";
    break;
  case EGL_BAD_NATIVE_PIXMAP:
    err = "EGL_BAD_NATIVE_PIXMAP";
    break;
  case EGL_BAD_NATIVE_WINDOW:
    err = "EGL_BAD_NATIVE_WINDOW";
    break;
  case EGL_CONTEXT_LOST:
    err = "EGL_CONTEXT_LOST";
    break;
  default:
    err = "unknown";
    break;
  }
  std::cout << name << " failed with " << err << std::endl;
}

void FlutterAngleTexture::RenderCallback() {
  glFinish();
  _D3D11DeviceContext->CopyResource(_externalD3DTexture2D.Get(),
                                    _internalD3DTexture2D.Get());
  _D3D11DeviceContext->Flush();
}

FlutterAngleTexture::~FlutterAngleTexture() {
  if (_eglDisplay != EGL_NO_DISPLAY && _eglSurface != EGL_NO_SURFACE) {
    eglReleaseTexImage(_eglDisplay, _eglSurface, EGL_BACK_BUFFER);
  }
  auto success = eglDestroySurface(this->_eglDisplay, this->_eglSurface);
  if(success != EGL_TRUE) {
    std::cout << "Failed to destroy EGL Surface" << std::endl;
  }
  _internalD3DTexture2D->Release();
  _externalD3DTexture2D->Release();
  glDeleteTextures(1, &this->glTextureId);
}

FlutterAngleTexture::FlutterAngleTexture(
    flutter::PluginRegistrarWindows *pluginRegistrar,
    flutter::TextureRegistrar *textureRegistrar,
    std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result,
    uint32_t width, uint32_t height, ID3D11Device *D3D11Device,
    ID3D11DeviceContext *D3D11DeviceContext, EGLConfig eglConfig,
    EGLDisplay eglDisplay, EGLContext eglContext,
    std::function<void(size_t, size_t)> onResizeRequested
    )
    : _pluginRegistrar(pluginRegistrar), _textureRegistrar(textureRegistrar),
      _width(width), _height(height), _D3D11Device(D3D11Device),
      _D3D11DeviceContext(D3D11DeviceContext), _eglConfig(eglConfig),
      _eglDisplay(eglDisplay), _eglContext(eglContext), _onResizeRequested(onResizeRequested) {

  auto d3d11_texture2D_desc = D3D11_TEXTURE2D_DESC{0};
  d3d11_texture2D_desc.Width = width;
  d3d11_texture2D_desc.Height = height;
  d3d11_texture2D_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
  d3d11_texture2D_desc.MipLevels = 1;
  d3d11_texture2D_desc.ArraySize = 1;
  d3d11_texture2D_desc.SampleDesc.Count = 1;
  d3d11_texture2D_desc.SampleDesc.Quality = 0;
  d3d11_texture2D_desc.Usage = D3D11_USAGE_DEFAULT;
  d3d11_texture2D_desc.BindFlags =
      D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
  d3d11_texture2D_desc.CPUAccessFlags = 0;
  d3d11_texture2D_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;

  // create internal texture
  auto hr = _D3D11Device->CreateTexture2D(&d3d11_texture2D_desc, nullptr,
                                          &_internalD3DTexture2D);
  if FAILED (hr) {
    result->Error("ERROR", "Failed to create D3D texture", nullptr);
    return;
    ;
  }
  auto resource = Microsoft::WRL::ComPtr<IDXGIResource>{};
  hr = _internalD3DTexture2D.As(&resource);

  if FAILED (hr) {
    result->Error("ERROR", "Failed to create D3D texture", nullptr);
    return;
    ;
  }
  hr = resource->GetSharedHandle(&_internalD3DTextureHandle);
  if FAILED (hr) {
    result->Error("ERROR", "Failed to get shared handle to D3D texture",
                  nullptr);
    return;
    ;
  }
  _internalD3DTexture2D->AddRef();

  std::cout << "Created internal D3D texture" << std::endl;

  // external
  hr = _D3D11Device->CreateTexture2D(&d3d11_texture2D_desc, nullptr,
                                     &_externalD3DTexture2D);
  if FAILED (hr) {
    result->Error("ERROR", "Failed to create D3D texture", nullptr);
    return;
    ;
  }
  hr = _externalD3DTexture2D.As(&resource);

  if FAILED (hr) {
    result->Error("ERROR", "Failed to create D3D texture", nullptr);
    return;
    ;
  }
  hr = resource->GetSharedHandle(&_externalD3DTextureHandle);
  if FAILED (hr) {
    result->Error("ERROR",
                  "Failed to get shared handle to external D3D texture",
                  nullptr);
    return;
    ;
  }
  _externalD3DTexture2D->AddRef();

  std::cout << "Created external D3D texture" << std::endl;

  EGLint pbufferAttribs[] = {
      EGL_WIDTH,          width,          EGL_HEIGHT,         height,
      EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
      EGL_NONE,
  };

  _eglSurface = eglCreatePbufferFromClientBuffer(
      _eglDisplay, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE,
      _internalD3DTextureHandle, _eglConfig, pbufferAttribs);

  if (!eglMakeCurrent(_eglDisplay, _eglSurface, _eglSurface, _eglContext)) {
    // eglMakeCurrent failed
    logEglError("eglMakeCurrent");
    return;
  }

  glGenTextures(1, &glTextureId);

  if (glTextureId == 0) {
    std::cout
        << "Failed to generate OpenGL texture for ANGLE, OpenGL err was %d",
        glGetError();
    return;
  }

  glBindTexture(GL_TEXTURE_2D, glTextureId);
  eglBindTexImage(_eglDisplay, _eglSurface, EGL_BACK_BUFFER);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  //  clearGlError
  GLenum const error = glGetError();
  if (error != GL_NO_ERROR) {
    std::cout << "Ignoring pending GL error " << error << std::endl;
  }
  char const *version;

  version = (char const *)glGetString(GL_VERSION);
  std::cout << "Got version " << version << std::endl;

  EGLint major, minor;
  glGetIntegerv(GL_MAJOR_VERSION, &major);
  glGetIntegerv(GL_MINOR_VERSION, &minor);

  _textureDescriptor = std::make_unique<FlutterDesktopGpuSurfaceDescriptor>();
  _textureDescriptor->struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor);
  _textureDescriptor->handle = _externalD3DTextureHandle;
  _textureDescriptor->width = _textureDescriptor->visible_width = width;
  _textureDescriptor->height = _textureDescriptor->visible_height = height;
  _textureDescriptor->release_context = nullptr;
  _textureDescriptor->release_callback = [](void *release_context) {

  };
  _textureDescriptor->format = kFlutterDesktopPixelFormatBGRA8888;

  texture =
      std::make_unique<flutter::TextureVariant>(flutter::GpuSurfaceTexture(
          kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle,
          [&](size_t width, size_t height) { 
            if(width != this->_width || height != this->_height) {
              this->_onResizeRequested(width, height);
            }
            return _textureDescriptor.get(); 
          }));

  flutterTextureId = _textureRegistrar->RegisterTexture(texture.get());
  std::cout << "Registered Flutter texture ID " << flutterTextureId
            << std::endl;

  std::vector<flutter::EncodableValue> resultList;
  resultList.push_back(flutter::EncodableValue(flutterTextureId));
  resultList.push_back(flutter::EncodableValue((int64_t) nullptr));
  resultList.push_back(flutter::EncodableValue(glTextureId));
  resultList.push_back(flutter::EncodableValue((int64_t) eglContext));
  result->Success(resultList);
}

} // namespace thermion_flutter