// Dear ImGui: standalone example application for SDL2 + OpenGL
// (SDL is a cross-platform general purpose library for handling windows,
// inputs, OpenGL/Vulkan/Metal graphics context creation, etc.)

// Learn about Dear ImGui:
// - FAQ                  https://dearimgui.com/faq
// - Getting Started      https://dearimgui.com/getting-started
// - Documentation        https://dearimgui.com/docs (same as your local docs/
// folder).
// - Introduction, links and more at the top of imgui.cpp
//

#include <cstdint>
#ifdef __EMSCRIPTEN__
#else
#include "GL/glew.h"
#endif

#include <cstddef>
#define IMGUI_DEFINE_MATH_OPERATORS

#include "lib/backends/imgui_impl_opengl3.h"
#include "lib/backends/imgui_impl_sdl2.h"
#include "lib/imgui.h"
#include "lib/nodes/load_image.h"
#include <stdio.h>

#define STB_IMAGE_IMPLEMENTATION
#include "lib/stb_image.h"

#if defined(__APPLE__)
#include <SDL2/SDL.h>
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <SDL2/SDL_opengles.h>
#else
#include <SDL2/SDL_opengl.h>
#endif
#else
#include <SDL.h>
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <SDL_opengles2.h>
#else
#include <SDL_opengl.h>
#endif
#endif

// This example can also compile and run with Emscripten! See
// 'Makefile.emscripten' for details.
#ifdef __EMSCRIPTEN__
#include "lib/emscripten/emscripten_mainloop_stub.h"
#endif

#include "lib/FrameBuffer.h"
#include "lib/ImNodeFlow.h"

static bool init = true;

GLuint initShader(const char *vShader, const char *fShader,
                  const char *outputAttributeName) {
  struct Shader {
    GLenum type;
    const char *source;
  } shaders[2] = {{GL_VERTEX_SHADER, vShader}, {GL_FRAGMENT_SHADER, fShader}};

  GLuint program = glCreateProgram();

  for (int i = 0; i < 2; ++i) {
    Shader &s = shaders[i];
    GLuint shader = glCreateShader(s.type);
    glShaderSource(shader, 1, (const GLchar **)&s.source, NULL);
    glCompileShader(shader);

    GLint compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
      std::cerr << " failed to compile:" << std::endl;
      GLint logSize;
      glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logSize);
      char *logMsg = new char[logSize];
      glGetShaderInfoLog(shader, logSize, NULL, logMsg);
      std::cerr << logMsg << std::endl;
      delete[] logMsg;

      exit(EXIT_FAILURE);
    }

    glAttachShader(program, shader);
  }

  /* Link output */
#ifndef EMSCRIPTEN
  glBindFragDataLocation(program, 0, outputAttributeName);
#endif
  /* link  and error check */
  glLinkProgram(program);

  GLint linked;
  glGetProgramiv(program, GL_LINK_STATUS, &linked);
  if (!linked) {
    std::cerr << "Shader program failed to link" << std::endl;
    GLint logSize;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logSize);
    char *logMsg = new char[logSize];
    glGetProgramInfoLog(program, logSize, NULL, logMsg);
    std::cerr << logMsg << std::endl;
    delete[] logMsg;

    exit(EXIT_FAILURE);
  }

  /* use program object */
  glUseProgram(program);

  return program;
}

uint8_t loadShader() {
#ifdef EMSCRIPTEN
  const char *vert = R"(#version 100
    uniform vec2 offset;
    attribute vec4 position;
    attribute vec2 uv;
    varying vec2 vUV;
    void main (void)
    {
        vUV = uv;
        gl_Position = position + vec4(offset,0.0,0.0);
    })";
#else
  const char *vert = R"(#version 150
    uniform vec2 offset;
    in vec4 position;
    in vec2 uv;
    
    out vec2 vUV;
    
    void main (void)
    {
        vUV = uv;
        gl_Position = position + vec4(offset,0.0,0.0);
    })";
#endif
#ifdef EMSCRIPTEN
  const char *frag = R"(#version 100
    precision mediump float;
    
    varying vec2 vUV;
    uniform sampler2D tex;
    
    void main(void)
    {
        gl_FragColor = texture2D(tex,vUV);
    })";
#else
  const char *frag = R"(#version 150
    in vec2 vUV;
    out vec4 fragColor;
    uniform sampler2D tex;
    void main(void)
    {
        fragColor = texture(tex,vUV);
    })";
#endif
  std::cout << "Starting to init" << std::endl;
  uint8_t shaderProgram = initShader(vert, frag, "fragColor");
  std::cout << "done to init" << std::endl;

  uint8_t uvAttribute = glGetAttribLocation(shaderProgram, "uv");
  if (uvAttribute < 0) {
    std::cerr << "Shader did not contain the 'color' attribute." << std::endl;
  }
  uint8_t positionAttribute = glGetAttribLocation(shaderProgram, "position");
  if (positionAttribute < 0) {
    std::cerr << "Shader did not contain the 'position' attribute."
              << std::endl;
  }

  return shaderProgram;
}

uint8_t setup() { return loadShader(); }

// Main code
int main(int, char **) {
  // Setup SDL
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) !=
      0) {
    printf("Error: %s\n", SDL_GetError());
    return -1;
  }

  // Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
  // GL ES 2.0 + GLSL 100
  const char *glsl_version = "#version 100";
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(__APPLE__)
  // GL 3.2 Core + GLSL 150
  const char *glsl_version = "#version 150";
  SDL_GL_SetAttribute(
      SDL_GL_CONTEXT_FLAGS,
      SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
  // GL 3.0 + GLSL 130
  const char *glsl_version = "#version 130";
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif

  // From 2.0.18: Enable native IME.
#ifdef SDL_HINT_IME_SHOW_UI
  SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif

  // Create window with graphics context
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
  SDL_WindowFlags window_flags =
      (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
                        SDL_WINDOW_ALLOW_HIGHDPI);
  SDL_Window *window =
      SDL_CreateWindow("PPD Node", SDL_WINDOWPOS_CENTERED,
                       SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);
  if (window == nullptr) {
    printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
    return -1;
  }

  SDL_GLContext gl_context = SDL_GL_CreateContext(window);
  SDL_GL_MakeCurrent(window, gl_context);
  SDL_GL_SetSwapInterval(1); // Enable vsync

  // Setup Dear ImGui context
  IMGUI_CHECKVERSION();

  ImGui::CreateContext();
  ImGuiIO &io = ImGui::GetIO();
  (void)io;
  io.ConfigFlags |=
      ImGuiConfigFlags_NavEnableKeyboard;           // Enable Keyboard Controls
  io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
  io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport /
                                                      // Platform Windows
  // io.ConfigViewportsNoAutoMerge = true;
  // io.ConfigViewportsNoTaskBarIcon = true;

  // Setup Dear ImGui style
  ImGui::StyleColorsDark();
  // ImGui::StyleColorsLight();

  // When viewports are enabled we tweak WindowRounding/WindowBg so platform
  // windows can look identical to regular ones.
  ImGuiStyle &style = ImGui::GetStyle();
  if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
    style.WindowRounding = 0.0f;
    style.Colors[ImGuiCol_WindowBg].w = 1.0f;
  }

  // Setup Platform/Renderer backends
  ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
  ImGui_ImplOpenGL3_Init(glsl_version);

  ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
  ImFlow::ImNodeFlow editor = ImFlow::ImNodeFlow("Nodes");

  uint8_t shader = 0;
  // Main loop

  bool done = false;
#ifdef __EMSCRIPTEN__
  // For an Emscripten build we are disabling file-system access, so let's not
  // attempt to do a fopen() of the imgui.ini file. You may manually call
  // LoadIniSettingsFromMemory() to load settings from your own storage.
  io.IniFilename = nullptr;
  EMSCRIPTEN_MAINLOOP_BEGIN
#else
  while (!done)
#endif
  {
    // Poll and handle events (inputs, window resize, etc.)
    // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to
    // tell if dear imgui wants to use your inputs.
    // - When io.WantCaptureMouse is true, do not dispatch mouse input data to
    // your main application, or clear/overwrite your copy of the mouse data.
    // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input
    // data to your main application, or clear/overwrite your copy of the
    // keyboard data. Generally you may always pass all inputs to dear imgui,
    // and hide them from your application based on those two flags.
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
      ImGui_ImplSDL2_ProcessEvent(&event);
      if (event.type == SDL_QUIT)
        done = true;
      if (event.type == SDL_WINDOWEVENT &&
          event.window.event == SDL_WINDOWEVENT_CLOSE &&
          event.window.windowID == SDL_GetWindowID(window))
        done = true;
    }

    // Start the Dear ImGui frame
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplSDL2_NewFrame();
    ImGui::NewFrame();

    {
#ifdef IMGUI_HAS_VIEWPORT
      ImGuiViewport *viewport = ImGui::GetMainViewport();
      ImGui::SetNextWindowPos(viewport->WorkPos);
      ImGui::SetNextWindowSize(viewport->WorkSize);
      ImGui::SetNextWindowViewport(viewport->ID);
#else
      ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
      ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
#endif
      ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
      ImGui::Begin("Main", NULL,
                   ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize);
      ImGuiID dockspace_id = ImGui::GetID("MyDockspace");
      ImGuiDockNodeFlags dockspace_flags =
          ImGuiDockNodeFlags_PassthruCentralNode;
      ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f));
      if (init) {
        init = false;
        ImGui::DockBuilderRemoveNode(dockspace_id);
        ImGui::DockBuilderAddNode(
            dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace);
        ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);
        ImGui::DockBuilderDockWindow("Editor", dockspace_id);
        ImGui::DockBuilderFinish(dockspace_id);

        // setup_shaders();
      }
      if (ImGui::BeginMainMenuBar()) {
        if (ImGui::BeginMenu("Add")) {
          if (ImGui::MenuItem("Load image")) {
            editor.addNode<LoadImage>(ImVec2(10, 10));
          }
          if (ImGui::MenuItem("Preview")) {
            editor.addNode<PreviewImage>(ImVec2(10, 10));
          }
          if (ImGui::MenuItem("Simple Brightness")) {
            editor.addNode<SimpleBrightness>(ImVec2(10, 10));
          }
          if (ImGui::MenuItem("Contrast")) {
            editor.addNode<Contrast>(ImVec2(10, 10));
          }
          if (ImGui::MenuItem("Exposure")) {
            editor.addNode<Exposure>(ImVec2(10, 10));
          }
          ImGui::EndMenu();
        }
        ImGui::EndMainMenuBar();
      }

      ImGui::Begin("Editor", NULL,
                   ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize);
      editor.update();
      ImGui::End();
      ImGui::End();
      ImGui::PopStyleVar();

      if (shader != 0) {
        ImGui::Begin("Shader test");
        ImGui::Text("Shader ID: %d", shader);
        ImGui::End();
      }
    }

    // Rendering
    ImGui::Render();
    glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
    glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w,
                 clear_color.z * clear_color.w, clear_color.w);
    glClear(GL_COLOR_BUFFER_BIT);
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    // Update and Render additional Platform Windows
    // (Platform functions may change the current OpenGL context, so we
    // save/restore it to make it easier to paste this code elsewhere.
    //  For this specific demo app we could also call SDL_GL_MakeCurrent(window,
    //  gl_context) directly)
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
      SDL_Window *backup_current_window = SDL_GL_GetCurrentWindow();
      SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext();
      ImGui::UpdatePlatformWindows();
      ImGui::RenderPlatformWindowsDefault();
      SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
    }

    SDL_GL_SwapWindow(window);
  }
#ifdef __EMSCRIPTEN__
  EMSCRIPTEN_MAINLOOP_END;
#endif

  // Cleanup
  ImGui_ImplOpenGL3_Shutdown();
  ImGui_ImplSDL2_Shutdown();
  ImGui::DestroyContext();

  SDL_GL_DeleteContext(gl_context);
  SDL_DestroyWindow(window);
  SDL_Quit();

  return 0;
}
