#include "include/embedder_texture/embedder_texture_plugin.h"
#include "include/fl_my_texture_gl.h"

#include <flutter_linux/flutter_linux.h>
#include <flutter_linux/fl_view.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <sys/utsname.h>
#include <glib.h>
#include <GL/glew.h>

#include <thread>
#include <chrono>
#include <vector>

#include "embedder_texture_plugin_private.h"

#define EMBEDDER_TEXTURE_PLUGIN(obj)                                     \
  (G_TYPE_CHECK_INSTANCE_CAST((obj), embedder_texture_plugin_get_type(), \
                              LinuxOpenglPlugin))

struct _LinuxOpenglPlugin
{
  GObject parent_instance;

  unsigned int width = 0;
  unsigned int height = 0;
  unsigned int texture_id = 0;

  FlTexture *texture = nullptr;
  FlTextureRegistrar *texture_registrar = nullptr;

  GdkGLContext *context = nullptr;
  GdkWindow *window = nullptr;
  FlMyTextureGL *myTexture = nullptr;
  FlView *fl_view = nullptr;
};

G_DEFINE_TYPE(LinuxOpenglPlugin, embedder_texture_plugin, g_object_get_type())

// Called when a method call is received from Flutter.
static void embedder_texture_plugin_handle_method_call(
    LinuxOpenglPlugin *self,
    FlMethodCall *method_call)
{

  g_autoptr(FlMethodResponse) response = nullptr;

  const gchar *method = fl_method_call_get_name(method_call);

  if (strcmp(method, "create") == 0)
  {
    response = create(self, method_call);
  }
  else if (strcmp(method, "remove") == 0)
  {
    response = remove(self, method_call);
  }
  else
  {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }

  fl_method_call_respond(method_call, response, nullptr);
}

void set_color(LinuxOpenglPlugin *self)
{
    gdk_gl_context_make_current(self->context);

    static GLfloat pixels[] =
        {
            1, 0, 0,
            0, 1, 0,
            0, 0, 1,
            1, 1, 1
        };

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_FLOAT, pixels);

    fl_texture_registrar_mark_texture_frame_available(self->texture_registrar, self->texture);
}

FlMethodResponse *create(
    LinuxOpenglPlugin *self,
    FlMethodCall *method_call)
{
    GError *error = NULL;
    FlValue *args = fl_method_call_get_args(method_call);
    FlValue *w = fl_value_lookup_string(args, "width");
    FlValue *h = fl_value_lookup_string(args, "height");

    if (w != nullptr)
    {
        self->width = fl_value_get_float(w);
    }

    if (h != nullptr)
    {
        self->height = fl_value_get_float(h);
    }

    if (self->width != 0 && self->height != 0)
    {
        self->window = gtk_widget_get_parent_window(GTK_WIDGET(self->fl_view));
        self->context = gdk_window_create_gl_context(self->window, &error);

        gdk_gl_context_make_current(self->context);

        glGenTextures(1, &self->texture_id);
        glBindTexture(GL_TEXTURE_2D, self->texture_id);

        // drawing on the texture
        set_color(self);

        self->myTexture = fl_my_texture_gl_new(GL_TEXTURE_2D, self->texture_id, self->width, self->height);
        self->texture = FL_TEXTURE(self->myTexture);

        fl_texture_registrar_register_texture(self->texture_registrar, self->texture);
        gdk_gl_context_clear_current();

        g_autoptr(FlValue) result = fl_value_new_int(reinterpret_cast<int64_t>(self->texture));
        return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
    }

    return FL_METHOD_RESPONSE(fl_method_error_response_new(
      "500",
      "Method create() called without passing width or height parameters!",
      nullptr));
}

FlMethodResponse *remove(
    LinuxOpenglPlugin *self,
    FlMethodCall *method_call)
{
    FlValue *args = fl_method_call_get_args(method_call);
    FlValue *id = fl_value_lookup_string(args, "textureId");

    if (id != nullptr)
    {
        int textureId = fl_value_get_int(id);
        fl_texture_registrar_unregister_texture(self->texture_registrar, self->texture);
        g_autoptr(FlValue) result = fl_value_new_null();
        return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
    }

    return FL_METHOD_RESPONSE(fl_method_error_response_new(
      "500",
      "Method remove() called without passing textureId parameters!",
      nullptr));
}

static void embedder_texture_plugin_dispose(GObject *object)
{
  G_OBJECT_CLASS(embedder_texture_plugin_parent_class)->dispose(object);
}

static void embedder_texture_plugin_class_init(LinuxOpenglPluginClass *klass)
{
  G_OBJECT_CLASS(klass)->dispose = embedder_texture_plugin_dispose;
}

static void embedder_texture_plugin_init(LinuxOpenglPlugin *self) {}

static void method_call_cb(FlMethodChannel *channel, FlMethodCall *method_call,
                           gpointer user_data)
{
  LinuxOpenglPlugin *plugin = EMBEDDER_TEXTURE_PLUGIN(user_data);
  embedder_texture_plugin_handle_method_call(plugin, method_call);
}

void embedder_texture_plugin_register_with_registrar(FlPluginRegistrar *registrar)
{
  LinuxOpenglPlugin *plugin = EMBEDDER_TEXTURE_PLUGIN(
      g_object_new(embedder_texture_plugin_get_type(), nullptr));

  plugin->fl_view = fl_plugin_registrar_get_view(registrar);
  plugin->texture_registrar = fl_plugin_registrar_get_texture_registrar(registrar);

  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel =
      fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
                            "embedder_texture",
                            FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel, method_call_cb,
                                            g_object_ref(plugin),
                                            g_object_unref);

  GLenum err = glewInit();
  if (GLEW_OK != err)
  {
    printf("%s", "Error!");
    printf("%s", glewGetErrorString(err));
    return;
  }

  g_object_unref(plugin);
}