
#include <algorithm>
#include <atomic>  // C++11
#include <cassert>
#include <chrono>  // C++11
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <mutex>   // C++11
#include <thread>  // C++11

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#endif

// To avoid some C define symbol conflict with SDL(e.g. 'Bool')
// tinyusdz headers muet be included before SDL
#include "tinyusdz.hh"
#include "tydra/render-data.hh"


// ../common/SDL2
#include <SDL.h>

#if defined(SDL_VIDEO_DRIVER_X11)
#include <SDL_syswm.h>
#endif

#if defined(USDVIEW_USE_BULLET3)
#include <btBulletDynamicsCommon.h>
#endif

// common
#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_sdlrenderer.h"
#include "imnodes.h"
#include "roboto_mono_embed.inc.h"
#include "virtualGizmo3D/vGizmo.h"
#include "trackball.h"

//
#include "gui.hh"
#include "simple-render.hh"
#include "trackball.h"
// 
#include "tinyusdz.hh"
#include "tydra/render-data.hh" // To convert USD Stage to OpenGL/Vulkan friendly data structure.

#if defined(USDVIEW_USE_NATIVEFILEDIALOG)
#include "nfd.h"
#endif

//#define EMULATE_EMSCRIPTEN

#if defined(EMULATE_EMSCRIPTEN)
#define EM_BOOL int
#define EM_TRUE 1
#define EM_FALSE 0
#endif

struct GUIContext {
  enum AOVMode {
    AOV_COLOR = 0,
    AOV_SHADING_NORMAL,
    AOV_GEOMETRIC_NORMAL,
    AOV_POSITION,
    AOV_DEPTH,
    AOV_TEXCOORD,
    AOV_VARYCOORD,
    AOV_VERTEXCOLOR
  };
  int aov_mode{AOV_COLOR};

  example::AOV aov;  // framebuffer

  int width = 1024;
  int height = 768;

  int mouse_x = -1;
  int mouse_y = -1;

  bool mouse_left_down = false;
  bool shift_pressed = false;
  bool ctrl_pressed = false;
  bool tab_pressed = false;

  float yaw = 90.0f;  // for Z up scene
  float pitch = 0.0f;
  float roll = 0.0f;

  // float curr_quat[4] = {0.0f, 0.0f, 0.0f, 1.0f};
  // std::array<float, 4> prev_quat[4] = {0.0f, 0.0f, 0.0f, 1.0f};

  // std::array<float, 3> eye = {0.0f, 0.0f, 5.0f};
  // std::array<float, 3> lookat = {0.0f, 0.0f, 0.0f};
  // std::array<float, 3> up = {0.0f, 1.0f, 0.0f};

  example::RTRenderScene rt_render_scene;

  example::Camera camera;

  std::atomic<bool> update_texture{false};
  std::atomic<bool> redraw{true};  // require redraw
  std::atomic<bool> quit{false};

  SDL_Renderer* renderer;
  SDL_Texture* texture;  // Texture for rendered image
  int render_width = 512;
  int render_height = 512;

  //tinyusdz::Stage stage;
  std::string usd_filename;

  // RenderScene: Scene graph object which is suited for GL/Vulkan renderer.
  // Constructed from `tinyusdz::Stage`
  tinyusdz::tydra::RenderScene render_scene;

  // scene reload
  std::atomic<bool> request_reload{false};

  tinyusdz::AssetResolutionResolver arr;

  // USDZ specific
  // Byte range info for assets(textures, audio, etc)
  tinyusdz::USDZAsset usdz_asset;

#if __EMSCRIPTEN__ || defined(EMULATE_EMSCRIPTEN)
  bool render_finished{false};
  int current_render_line = 0;
  int render_line_size =
      32;  // render images with this lines per animation loop.
  // for emscripten environment
#endif
};

GUIContext g_gui_ctx;

namespace {

inline double radians(double degree) { return 3.141592653589 * degree / 180.0; }

// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
std::array<double, 4> ToQuaternion(double yaw, double pitch,
                                   double roll)  // yaw (Z), pitch (Y), roll (X)
{
  // Abbreviations for the various angular functions
  double cy = std::cos(yaw * 0.5);
  double sy = std::sin(yaw * 0.5);
  double cp = std::cos(pitch * 0.5);
  double sp = std::sin(pitch * 0.5);
  double cr = std::cos(roll * 0.5);
  double sr = std::sin(roll * 0.5);

  std::array<double, 4> q;
  q[0] = cr * cp * cy + sr * sp * sy;
  q[1] = sr * cp * cy - cr * sp * sy;
  q[2] = cr * sp * cy + sr * cp * sy;
  q[3] = cr * cp * sy - sr * sp * cy;

  return q;
}

#if 0
static void DrawGeomMesh(tinyusdz::GeomMesh& mesh) {}

static void DrawNode(const tinyusdz::Scene& scene, const tinyusdz::Node& node) {
  if (node.type == tinyusdz::NODE_TYPE_XFORM) {
    const tinyusdz::Xform& xform = scene.xforms.at(node.index);
  }

  for (const auto& child : node.children) {
    // DrawNode(scene, scene.nodes.at(child));
  }

  if (node.type == tinyusdz::NODE_TYPE_XFORM) {
    // glPopMatrix();
  }
}
#endif

static void Proc(const tinyusdz::Stage& stage) {
  (void)stage;

  //std::cout << "num geom_meshes = " << scene.geom_meshes.size() << "\n";
  //for (auto& mesh : scene.geom_meshes) {
  //}
}

static std::string GetFileExtension(const std::string& filename) {
  if (filename.find_last_of(".") != std::string::npos)
    return filename.substr(filename.find_last_of(".") + 1);
  return "";
}

static std::string str_tolower(std::string s) {
  std::transform(s.begin(), s.end(), s.begin(),
                 // static_cast<int(*)(int)>(std::tolower)         // wrong
                 // [](int c){ return std::tolower(c); }           // wrong
                 // [](char c){ return std::tolower(c); }          // wrong
                 [](unsigned char c) { return std::tolower(c); }  // correct
  );
  return s;
}

// TODO: Use pow table for faster conversion.
inline float linearToSrgb(float x) {
  if (x <= 0.0f)
    return 0.0f;
  else if (x >= 1.0f)
    return 1.0f;
  else if (x < 0.0031308f)
    return x * 12.92f;
  else
    return std::pow(x, 1.0f / 2.4f) * 1.055f - 0.055f;
}

inline uint8_t ftouc(float f) {
  int val = int(f * 255.0f);
  val = (std::max)(0, (std::min)(255, val));

  return static_cast<uint8_t>(val);
}

void UpdateTexutre(SDL_Texture* tex, const GUIContext& ctx,
                   const example::AOV& aov) {
  int w, h;
  SDL_QueryTexture(tex, nullptr, nullptr, &w, &h);

  if ((aov.width != w) || (aov.height != h)) {
    std::cerr << "texture size and AOV sized mismatch\n";
    return;
  }

  std::vector<uint8_t> buf;
  buf.resize(aov.width * aov.height * 4);

  if (ctx.aov_mode == GUIContext::AOV_COLOR) {
    for (size_t i = 0; i < aov.width * aov.height; i++) {
      uint8_t r = ftouc(linearToSrgb(aov.rgb[3 * i + 0]));
      uint8_t g = ftouc(linearToSrgb(aov.rgb[3 * i + 1]));
      uint8_t b = ftouc(linearToSrgb(aov.rgb[3 * i + 2]));

      buf[4 * i + 0] = r;
      buf[4 * i + 1] = g;
      buf[4 * i + 2] = b;
      buf[4 * i + 3] = 255;
    }
  } else if (ctx.aov_mode == GUIContext::AOV_SHADING_NORMAL) {
    for (size_t i = 0; i < aov.width * aov.height; i++) {
      uint8_t r = ftouc(aov.shading_normal[3 * i + 0]);
      uint8_t g = ftouc(aov.shading_normal[3 * i + 1]);
      uint8_t b = ftouc(aov.shading_normal[3 * i + 2]);

      buf[4 * i + 0] = r;
      buf[4 * i + 1] = g;
      buf[4 * i + 2] = b;
      buf[4 * i + 3] = 255;
    }
  } else if (ctx.aov_mode == GUIContext::AOV_GEOMETRIC_NORMAL) {
    for (size_t i = 0; i < aov.width * aov.height; i++) {
      uint8_t r = ftouc(aov.geometric_normal[3 * i + 0]);
      uint8_t g = ftouc(aov.geometric_normal[3 * i + 1]);
      uint8_t b = ftouc(aov.geometric_normal[3 * i + 2]);

      buf[4 * i + 0] = r;
      buf[4 * i + 1] = g;
      buf[4 * i + 2] = b;
      buf[4 * i + 3] = 255;
    }
  } else if (ctx.aov_mode == GUIContext::AOV_TEXCOORD) {
    for (size_t i = 0; i < aov.width * aov.height; i++) {
      uint8_t r = ftouc(aov.texcoords[2 * i + 0]);
      uint8_t g = ftouc(aov.texcoords[2 * i + 1]);
      uint8_t b = 255;

      buf[4 * i + 0] = r;
      buf[4 * i + 1] = g;
      buf[4 * i + 2] = b;
      buf[4 * i + 3] = 255;
    }
  }

  SDL_UpdateTexture(tex, nullptr, reinterpret_cast<const void*>(buf.data()),
                    aov.width * 4);
}

// https://discourse.libsdl.org/t/sdl-and-xserver/12610/4
static void ScreenActivate(SDL_Window* window) {
#if defined(SDL_VIDEO_DRIVER_X11)
  SDL_SysWMinfo wm;

  // Get window info.
  SDL_VERSION(&wm.version);
  SDL_GetWindowWMInfo(window, &wm);

  // Lock to display access.
  // wm.info.x11.lock_func();

  // Show the window on top.
  XMapRaised(wm.info.x11.display, wm.info.x11.window);

  // Set the focus on it.
  XSetInputFocus(wm.info.x11.display, wm.info.x11.window, RevertToParent,
                 CurrentTime);
#else
  (void)window;
#endif
}

bool LoadModel(const std::string& filename, /* out */tinyusdz::Stage* stage) {

  std::string warn;
  std::string err;

  if (!tinyusdz::IsUSD(filename)) {
    std::cerr << "ERR: file not found or file is not USD format : " << filename << "\n";
    return false;
  }

  bool ret = tinyusdz::LoadUSDFromFile(filename, stage, &warn, &err);
  if (warn.size()) {
     std::cerr << "WARN : " << warn << "\n";
  }

  if (!ret) {
    if (!err.empty()) {
      std::cerr << "ERR : " << err << "\n";
    }
    return false;
  }

  return true;
}

void RenderThread(GUIContext* ctx) {
  bool done = false;

  while (!done) {
    if (ctx->quit) {
      return;
    }

    if (ctx->request_reload) {
      //ctx->stage = tinyusdz::Stage();  // reset

#if 0 // TODO
      if (LoadModel(ctx->filename, &ctx->stage)) {
        Proc(ctx->stage);
        if (ctx->scene.geom_meshes.empty()) {
          std::cerr << "The scene contains no GeomMesh\n";
        } else {
          ctx->render_scene.draw_meshes.clear();

          for (size_t i = 0; i < ctx->scene.geom_meshes.size(); i++) {
            example::DrawGeomMesh draw_mesh(&ctx->scene.geom_meshes[i]);
            ctx->render_scene.draw_meshes.push_back(draw_mesh);
          }

          // Setup render mesh
          if (!ctx->render_scene.Setup()) {
            std::cerr << "Failed to setup render mesh.\n";
            ctx->render_scene.draw_meshes.clear();
          }
          std::cout << "Setup render mesh\n";
        }
      }
#endif

      ctx->request_reload = false;

      ctx->redraw = true;
    }

    if (!ctx->redraw) {
      // Give CPU some cycles.
      std::this_thread::sleep_for(std::chrono::milliseconds(33));
      continue;
    }

    example::Render(ctx->rt_render_scene, ctx->camera, &ctx->aov);

    ctx->update_texture = true;

    ctx->redraw = false;
  }
};

#if defined(USDVIEW_USE_NATIVEFILEDIALOG)
// TODO: widechar(UTF-16) support for Windows
std::string OpenFileDialog() {
  std::string path;

  nfdchar_t* outPath;
  nfdfilteritem_t filterItem[1] = {{"USD file", "usd,usda,usdc,usdz"}};

  nfdresult_t result = NFD_OpenDialog(&outPath, filterItem, 1, NULL);
  if (result == NFD_OKAY) {
    puts("Success!");
    path = outPath;
    NFD_FreePath(outPath);
  } else if (result == NFD_CANCEL) {
    puts("User pressed cancel.");
  } else {
    printf("Error: %s\n", NFD_GetError());
  }

  return path;
}
#endif

// Helper to display a little (?) mark which shows a tooltip when hovered.
// In your own code you may want to display an actual icon if you are using a
// merged icon fonts (see docs/FONTS.md)
static void HelpMarker(const char* desc) {
  ImGui::TextDisabled("(?)");
  if (ImGui::IsItemHovered()) {
    ImGui::BeginTooltip();
    ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
    ImGui::TextUnformatted(desc);
    ImGui::PopTextWrapPos();
    ImGui::EndTooltip();
  }
}

#if defined(__EMSCRIPTEN__) || defined(EMULATE_EMSCRIPTEN)

EM_BOOL em_main_loop_frame(double tm, void* user) {
#if 1
  // Render image fragment
  if (g_gui_ctx.redraw) {
    g_gui_ctx.render_finished = false;
    g_gui_ctx.current_render_line = 0;

    g_gui_ctx.redraw = false;
  }

  if (!g_gui_ctx.render_finished) {
    std::cout << "RenderLines: " << g_gui_ctx.current_render_line << "\n";
    RenderLines(g_gui_ctx.current_render_line,
                g_gui_ctx.current_render_line + g_gui_ctx.render_line_size,
                g_gui_ctx.render_scene, g_gui_ctx.camera, &g_gui_ctx.aov);

    g_gui_ctx.current_render_line += g_gui_ctx.render_line_size;
    if (g_gui_ctx.current_render_line >= g_gui_ctx.render_height) {
      g_gui_ctx.current_render_line = 0;
      g_gui_ctx.render_finished = true;

      g_gui_ctx.update_texture = true;
    }
  }
#endif

  ImGuiIO& io = ImGui::GetIO();

#if 1
  int wheel = 0;

  SDL_Event e;
  if (SDL_PollEvent(&e)) {
    if (e.type == SDL_QUIT) {
      return EM_FALSE;
    } else if (e.type == SDL_DROPFILE) {
      char* filepath = e.drop.file;

      printf("File dropped: %s\n", filepath);

      SDL_free(filepath);

    } else if (e.type == SDL_WINDOWEVENT) {
      if (e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
        io.DisplaySize.x = static_cast<float>(e.window.data1);
        io.DisplaySize.y = static_cast<float>(e.window.data2);
      }
    } else if (e.type == SDL_KEYDOWN) {
      if (e.key.keysym.sym == SDLK_ESCAPE) {
        return EM_FALSE;
      }
    } else if (e.type == SDL_KEYUP) {
    } else if (e.type == SDL_MOUSEWHEEL) {
      wheel = e.wheel.y;
    }
  }

  int mouseX, mouseY;
  const int buttons = SDL_GetMouseState(&mouseX, &mouseY);

  // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or
  // write to those fields from your Windows message loop handlers,
  // etc.)

  io.DeltaTime = 1.0f / 60.0f;
  io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
  io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
  io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
  io.MouseWheel = static_cast<float>(wheel);
#endif

#if 1
  ImGui::NewFrame();

  bool update = false;
  bool update_display = false;
  ImGui::Begin("Scene");

  update |=
      ImGui::SliderFloat("eye.z", &g_gui_ctx.camera.eye[2], -1000.0, 1000.0f);
  update |= ImGui::SliderFloat("fov", &g_gui_ctx.camera.fov, 0.01f, 140.0f);

  // TODO: Validate coordinate definition.
  if (ImGui::SliderFloat("yaw", &g_gui_ctx.yaw, -360.0f, 360.0f)) {
    auto q = ToQuaternion(radians(g_gui_ctx.yaw), radians(g_gui_ctx.pitch),
                          radians(g_gui_ctx.roll));
    g_gui_ctx.camera.quat[0] = q[0];
    g_gui_ctx.camera.quat[1] = q[1];
    g_gui_ctx.camera.quat[2] = q[2];
    g_gui_ctx.camera.quat[3] = q[3];
    update = true;
  }
  if (ImGui::SliderFloat("pitch", &g_gui_ctx.pitch, -360.0f, 360.0f)) {
    auto q = ToQuaternion(radians(g_gui_ctx.yaw), radians(g_gui_ctx.pitch),
                          radians(g_gui_ctx.roll));
    g_gui_ctx.camera.quat[0] = q[0];
    g_gui_ctx.camera.quat[1] = q[1];
    g_gui_ctx.camera.quat[2] = q[2];
    g_gui_ctx.camera.quat[3] = q[3];
    update = true;
  }
  if (ImGui::SliderFloat("roll", &g_gui_ctx.roll, -360.0f, 360.0f)) {
    auto q = ToQuaternion(radians(g_gui_ctx.yaw), radians(g_gui_ctx.pitch),
                          radians(g_gui_ctx.roll));
    g_gui_ctx.camera.quat[0] = q[0];
    g_gui_ctx.camera.quat[1] = q[1];
    g_gui_ctx.camera.quat[2] = q[2];
    g_gui_ctx.camera.quat[3] = q[3];
    update = true;
  }

  ImGui::End();

  ImGui::Begin("Image");
  ImGui::Image(g_gui_ctx.texture,
               ImVec2(g_gui_ctx.render_width, g_gui_ctx.render_height));
  ImGui::End();

  if (update) {
    g_gui_ctx.redraw = true;
  }

  // Update texture
  if (g_gui_ctx.update_texture || update_display) {
    // Update texture for display
    UpdateTexutre(g_gui_ctx.texture, g_gui_ctx, g_gui_ctx.aov);

    g_gui_ctx.update_texture = false;
  }

  SDL_SetRenderDrawColor(g_gui_ctx.renderer, 114, 144, 154, 255);
  SDL_RenderClear(g_gui_ctx.renderer);

  // Imgui

  ImGui::Render();
  ImGuiSDL::Render(ImGui::GetDrawData());

  // static int texUpdateCount = 0;

  SDL_RenderPresent(g_gui_ctx.renderer);

#endif

  return EM_TRUE;
}

#endif

#if 0
void NodeTreeSubWindow(const tinyusdz::Node& node, uint32_t indent) {
  if (node.name.empty()) {
    // Do not traverse children of the node without name
    return;
  }

  if (ImGui::TreeNode(node.name.c_str())) {
    for (const auto& c : node.children) {
      NodeTreeSubWindow(c, indent + 1);
    }

    ImGui::TreePop();
  }
}

void NodeTreeWindow(const tinyusdz::HighLevelScene& scene) {
  ImGui::Begin("Node");

  if (scene.nodes.size()) {
    size_t root_node_id =
        (scene.default_root_node >= 0) ? size_t(scene.default_root_node) : 0;
    NodeTreeSubWindow(scene.nodes[root_node_id], /* indent */ 0);
  }

  ImGui::End();
}
#endif

#if 0
void ShaderParamWindow(const tinyusdz::HighLevelScene& scene) {
  ImGui::Begin("Shaders");

  for (const auto& item : scene.shaders) {
    if (item.name.empty()) {
      continue;
    }

    if (ImGui::TreeNode(item.name.c_str())) {
      if (item.value.is<tinyusdz::PreviewSurface>()) {
        ImGui::Text("type:id UsdPreviewSurface");

      } else if (item.value.is<tinyusdz::UVTexture>()) {
        ImGui::Text("type:id UVTexture");
      } else if (item.value.is<tinyusdz::PrimvarReader_float2>()) {
        ImGui::Text("type:id PrimvarReader_float2");
      } else if (item.value.is<tinyusdz::PrimvarReader_float3>()) {
        ImGui::Text("type:id PrimvarReader_float3");
      } else {
        ImGui::Text("Unsupported Shader type");
      }
      ImGui::TreePop();
    }
  }
  ImGui::End();
}

void MaterialsParamWindow(const tinyusdz::HighLevelScene& scene) {
  ImGui::Begin("Materials");

  for (const auto& item : scene.materials) {
    if (!item.name.empty()) {
      if (ImGui::TreeNode(item.name.c_str())) {
        int nrow = 1;

        ImGui::BeginTable("props", /* columns*/ 2);

        ImGui::TableNextRow();
        ImGui::TableSetColumnIndex(0);
        ImGui::Text("output.surface");
        ImGui::TableSetColumnIndex(1);

        ImGui::Text("%s", item.outputs_surface.c_str());

        ImGui::EndTable();
        ImGui::TreePop();
      }
    }
  }

  ImGui::End();
}

void UVTextureNode(int node_id, const tinyusdz::UVTexture& texture) {
  constexpr int kMaxSlots = 100;
  constexpr int kOutputOffset = kMaxSlots / 2;

  ImNodes::BeginNode(kMaxSlots * node_id);

  ImNodes::BeginNodeTitleBar();
  ImGui::TextUnformatted("UsdUVTexture");
  ImNodes::EndNodeTitleBar();

  ImNodes::BeginInputAttribute(kMaxSlots * node_id + 1);
  ImGui::Text("inputs:file");
  ImNodes::EndInputAttribute();

  ImNodes::BeginInputAttribute(kMaxSlots * node_id + 2);
  ImGui::Text("inputs:sourceColorSpace");
  ImNodes::EndInputAttribute();

  ImNodes::BeginInputAttribute(kMaxSlots * node_id + 3);
  ImGui::Text("inputs:st");
  ImNodes::EndInputAttribute();

  ImNodes::BeginOutputAttribute(kMaxSlots * node_id + kOutputOffset + 1);
  ImGui::Indent(40);
  ImGui::Text("outputs:rgb");
  ImNodes::EndOutputAttribute();

  ImNodes::EndNode();
}

void ShaderGraphWindow(const tinyusdz::HighLevelScene& scene) {
  ImGui::Begin("Shader graph");

  ImNodes::BeginNodeEditor();

  ImNodes::BeginNode(1);

  ImNodes::BeginNodeTitleBar();
  ImGui::TextUnformatted("File");
  ImNodes::EndNodeTitleBar();

  // ImNodes::BeginInputAttribute(2);
  // ImGui::Text("file");
  // ImNodes::EndInputAttribute();

  ImNodes::BeginOutputAttribute(3);
  ImGui::Indent(40);
  ImGui::Text("checkerboard.png");
  ImNodes::EndOutputAttribute();

  ImNodes::EndNode();

  tinyusdz::UVTexture texture;
  int texture_node_id = 1;
  UVTextureNode(texture_node_id, texture);

  ImNodes::MiniMap();

  ImNodes::EndNodeEditor();

  ImGui::End();
}
#endif

}  // namespace

int main(int argc, char** argv) {
#if defined(USDVIEW_USE_BULLET3)
  btDefaultCollisionConfiguration* collisionConfiguration =
      new btDefaultCollisionConfiguration();
#endif

  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
    std::cerr << "Failed to initialize SDL2\n";
    exit(-1);
  }

  std::cout << "SDL2 init OK\n";

#ifdef _WIN32
  std::string filename = "../../models/suzanne.usdc";
#elif __EMSCRIPTEN__
  // assume filename is embeded with --embed-file in emcc compile flag.
  std::string filename = "suzanne.usdc";
#else
  std::string filename = "../../../models/suzanne.usdc";
#endif

#if defined(USDVIEW_USE_NATIVEFILEDIALOG)
  NFD_Init();

#endif

  if (argc > 1) {
    filename = std::string(argv[1]);
  }

  g_gui_ctx.usd_filename = filename;

#if 0
  if (!init_with_empty) {
    std::cout << "Loaded USDC file\n";

    Proc(g_gui_ctx.stage);
    //if (g_gui_ctx.scene.geom_meshes.empty()) {
    //  std::cerr << "The scene contains no GeomMesh\n";
    //  exit(-1);
    //}
  }
#endif

  // Assume single monitor
  SDL_DisplayMode DM;
  SDL_GetCurrentDisplayMode(0, &DM);
  std::cout << "Current monitor: " << DM.w << " x " << DM.h << "\n";

  int default_win_x = 1600;
  int default_win_y = 800;

  if (DM.w > 2560) {
    default_win_x = 2560;
  }

  if (DM.h > 1600) {
    default_win_y = 1600;
  }

#if defined(__APPLE__)
  // For some reason, HIGHDPI does not work well on Retina Display for SDLRenderer backend.
  // Disable it for a while.
  SDL_WindowFlags window_flags = static_cast<SDL_WindowFlags>(SDL_WINDOW_RESIZABLE);
#else
  SDL_WindowFlags window_flags = static_cast<SDL_WindowFlags>(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
#endif

  std::cout << "default window size: " << default_win_x << " x " << default_win_y << "\n";
  SDL_Window* window = SDL_CreateWindow(
      "Simple USDZ viewer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
      default_win_x, default_win_y, window_flags);
  if (!window) {
    std::cerr << "Failed to create SDL2 window. If you are running on Linux, "
                 "probably X11 Display is not setup correctly. Check your "
                 "DISPLAY environment.\n";
    exit(-1);
  }

  std::cout << "SDL2 Window creation OK\n";

  SDL_Renderer* renderer = SDL_CreateRenderer(
      window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);

  if (!renderer) {
    std::cerr << "Failed to create SDL2 renderer. If you are running on "
                 "Linux, "
                 "probably X11 Display is not setup correctly. Check your "
                 "DISPLAY environment.\n";
    exit(-1);
  }

  std::cout << "SDL2 Renderer creation OK\n";


  GUIContext& gui_ctx = g_gui_ctx;
  gui_ctx.renderer = renderer;

  if (gui_ctx.usd_filename.size()) {
    std::string warn, err;

    // Setup render mesh
    bool ret = gui_ctx.rt_render_scene.SetupFromUSDFile(gui_ctx.usd_filename, warn, err);
    if (warn.size()) {
      std::cout << "WARN: " << warn << "\n";
    }

    if (!ret) {
      std::cerr << "Failed to load USD or setup render mesh.\n";
      std::cerr << err << "\n";
      exit(-1);
    }
    std::cout << "Setup render mesh\n";
  }

  bool done = false;

  ImGui::CreateContext();
  ImNodes::CreateContext();

  {

    float ddpi, hdpi, vdpi;
    if (SDL_GetDisplayDPI(0, &ddpi, &hdpi, &vdpi) != 0) {
        fprintf(stderr, "Failed to obtain DPI information for display 0: %s\n", SDL_GetError());
        // use default.
        ddpi = 72.0f;
        hdpi = 72.0f;
        vdpi = 72.0f;
    }
    std::cout << "ddpi " << ddpi << ", hdpi " << hdpi << ", vdpi " << vdpi << "\n";

    float dpi_scaling = ddpi / 72.0f;

    ImGuiIO& io = ImGui::GetIO();

    if (dpi_scaling >= 144.0f) {
      // https://github.com/ocornut/imgui/issues/1786
      // nx DisplayFrameBufferScale + nx font_size + FontGlobalScale 0.5 may give nicer visual on High DPI monitor
      io.FontGlobalScale = 0.5f;
    }

    io.DisplayFramebufferScale = {dpi_scaling , dpi_scaling}; // HACK

    ImFontConfig roboto_config;
    strcpy(roboto_config.Name, "Roboto");

    float font_size = 18.0 * dpi_scaling;

    //ImFontConfig font_config;
    //font_config.SizePixels = 18.0f * xscale;
    //io.Fonts->AddFontDefault(&font_config);
    io.Fonts->AddFontFromMemoryCompressedTTF(roboto_mono_compressed_data,
                                             roboto_mono_compressed_size,
                                             font_size, &roboto_config);
  }

  ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);
  ImGui_ImplSDLRenderer_Init(renderer);

  std::cout << "Imgui initialized\n";

  gui_ctx.texture = SDL_CreateTexture(
      renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET,
      gui_ctx.render_width, gui_ctx.render_height);
  {
    SDL_SetRenderTarget(renderer, gui_ctx.texture);
    SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
    SDL_RenderClear(renderer);
    SDL_SetRenderTarget(renderer, nullptr);

    // UpdateTexutre(texture, 0);
  }

  ScreenActivate(window);

  gui_ctx.aov.Resize(gui_ctx.render_width, gui_ctx.render_height);
  UpdateTexutre(gui_ctx.texture, gui_ctx, gui_ctx.aov);

  int display_w, display_h;
  ImVec4 clear_color = {0.1f, 0.18f, 0.3f, 1.0f};

  // init camera matrix
  {
    auto q = ToQuaternion(radians(gui_ctx.yaw), radians(gui_ctx.pitch),
                          radians(gui_ctx.roll));
    gui_ctx.camera.quat[0] = q[0];
    gui_ctx.camera.quat[1] = q[1];
    gui_ctx.camera.quat[2] = q[2];
    gui_ctx.camera.quat[3] = q[3];
  }

#if __EMSCRIPTEN__ || defined(EMULATE_EMSCRIPTEN)
  // no thread
#else
  std::thread render_thread(RenderThread, &gui_ctx);
#endif

  // Initial rendering requiest
  gui_ctx.redraw = true;

  std::map<std::string, int> aov_list = {
      {"color", GUIContext::AOV_COLOR},
      {"shading normal", GUIContext::AOV_SHADING_NORMAL},
      {"geometric normal", GUIContext::AOV_GEOMETRIC_NORMAL},
      {"texcoord", GUIContext::AOV_TEXCOORD}};

  std::string aov_name = "color";

#if __EMSCRIPTEN__ || defined(EMULATE_EMSCRIPTEN)

#if __EMSCRIPTEN__
  std::cout << "enter loop\n";
  emscripten_request_animation_frame_loop(em_main_loop_frame, /* fps */ 0);

  // render_thread.join();
  std::cout << "quit\n";
#else

  while (!done) {
    auto ret = em_main_loop_frame(0, nullptr);
    if (ret == EM_FALSE) {
      break;
    }
  }

#endif

#else
  // Enable drop file
  SDL_EventState(SDL_DROPFILE, SDL_ENABLE);

#if 0
  // Enable Docking;
  {
    ImGuiIO& io& io = ImGui::GetIO();

    // Enable docking(available in imgui `docking` branch at the moment)
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
  }
#endif

  while (!done) {
    ImGuiIO& io = ImGui::GetIO();

    int wheel = 0;

    SDL_Event e;
    while (SDL_PollEvent(&e)) {
      if (e.type == SDL_QUIT) {
        done = true;
      } else if (e.type == SDL_DROPFILE) {
        char* filepath = e.drop.file;

        printf("File dropped: %s\n", filepath);

        std::string fname = filepath;

        // Scene reloading is done in render thread.
        g_gui_ctx.usd_filename = fname;
        g_gui_ctx.request_reload = true;

        SDL_free(filepath);

      } else if (e.type == SDL_WINDOWEVENT) {
        if (e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
          io.DisplaySize.x = static_cast<float>(e.window.data1);
          io.DisplaySize.y = static_cast<float>(e.window.data2);
        }
      } else if (e.type == SDL_KEYDOWN) {
        if (e.key.keysym.sym == SDLK_ESCAPE) {
          done = true;
        }
      } else if (e.type == SDL_KEYUP) {
      } else if (e.type == SDL_MOUSEWHEEL) {
        wheel = e.wheel.y;
      }
    }

    int mouseX, mouseY;
    const int buttons = SDL_GetMouseState(&mouseX, &mouseY);

    // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or
    // write to those fields from your Windows message loop handlers,
    // etc.)

    io.DeltaTime = 1.0f / 60.0f;
    io.MousePos =
        ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
    io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
    io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
    io.MouseWheel = static_cast<float>(wheel);

    ImGui_ImplSDLRenderer_NewFrame();
    ImGui_ImplSDL2_NewFrame();
    ImGui::NewFrame();

    ImGui::Begin("Scene");
    bool update = false;

    bool update_display = false;

#if defined(USDVIEW_USE_NATIVEFILEDIALOG)
    if (ImGui::Button("Open file ...")) {
      std::string filename = OpenFileDialog();
      std::cout << "TODO: Open file"
                << "\n";
    }

    ImGui::SameLine();
    HelpMarker("You can also drop USDZ file to the window to open a file.");
#endif

    if (example::ImGuiComboUI("aov", aov_name, aov_list)) {
      gui_ctx.aov_mode = aov_list[aov_name];
      update_display = true;
    }

    update |= ImGui::InputFloat3("eye", gui_ctx.camera.eye);
    update |= ImGui::InputFloat3("look_at", gui_ctx.camera.look_at);
    update |= ImGui::InputFloat3("up", gui_ctx.camera.up);

#if 0
    update |=
        ImGui::SliderFloat("eye.z", &gui_ctx.camera.eye[2], -1000.0, 1000.0f);
#endif
    update |= ImGui::SliderFloat("fov", &gui_ctx.camera.fov, 0.01f, 140.0f);

    // TODO: Validate coordinate definition.
    if (ImGui::SliderFloat("yaw", &gui_ctx.yaw, -180.0f, 180.0f)) {
      auto q = ToQuaternion(radians(gui_ctx.yaw), radians(gui_ctx.pitch),
                            radians(gui_ctx.roll));
      gui_ctx.camera.quat[0] = q[0];
      gui_ctx.camera.quat[1] = q[1];
      gui_ctx.camera.quat[2] = q[2];
      gui_ctx.camera.quat[3] = q[3];
      update = true;
    }
    if (ImGui::SliderFloat("pitch", &gui_ctx.pitch, -89.9f, 89.9f)) {
      auto q = ToQuaternion(radians(gui_ctx.yaw), radians(gui_ctx.pitch),
                            radians(gui_ctx.roll));
      gui_ctx.camera.quat[0] = q[0];
      gui_ctx.camera.quat[1] = q[1];
      gui_ctx.camera.quat[2] = q[2];
      gui_ctx.camera.quat[3] = q[3];
      update = true;
    }
#if 0
    if (ImGui::SliderFloat("roll", &gui_ctx.roll, -360.0f, 360.0f)) {
      auto q = ToQuaternion(radians(gui_ctx.yaw), radians(gui_ctx.pitch),
                            radians(gui_ctx.roll));
      gui_ctx.camera.quat[0] = q[0];
      gui_ctx.camera.quat[1] = q[1];
      gui_ctx.camera.quat[2] = q[2];
      gui_ctx.camera.quat[3] = q[3];
      update = true;
    }
#endif
    ImGui::End();

    ImGui::Begin("Image");
    ImGui::Image(gui_ctx.texture,
                 ImVec2(gui_ctx.render_width, gui_ctx.render_height));
    ImGui::End();

    //NodeTreeWindow(gui_ctx.scene);
    //MaterialsParamWindow(gui_ctx.scene);
    //ShaderParamWindow(gui_ctx.scene);
    //ShaderGraphWindow(gui_ctx.scene);

    if (update) {
      gui_ctx.redraw = true;
    }

#if 0
    // Draw scene
    if ((scene.root_node >= 0) && (scene.root_node < scene.nodes.size())) {
      DrawNode(scene, scene.nodes[scene.root_node]);
    }
#endif

    // Update texture
    if (gui_ctx.update_texture || update_display) {
      // Update texture for display
      UpdateTexutre(gui_ctx.texture, gui_ctx, gui_ctx.aov);

      gui_ctx.update_texture = false;
    }

    SDL_SetRenderDrawColor(renderer, 114, 144, 154, 255);
    SDL_RenderClear(renderer);

    // Imgui

    ImGui::Render();

    // static int texUpdateCount = 0;
    static int frameCount = 0;
    static double currentTime =
        SDL_GetTicks() / 1000.0;  // GetTicks returns milliseconds
    static double previousTime = currentTime;
    static char title[256];

    frameCount++;
    currentTime = SDL_GetTicks() / 1000.0;
    const auto deltaTime = currentTime - previousTime;
    if (deltaTime >= 1.0) {
      sprintf(title, "Simple USDZ viewer [%dFPS]", frameCount);
      SDL_SetWindowTitle(window, title);
      frameCount = 0;
      previousTime = currentTime;
    }

    SDL_RenderClear(renderer);
    ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
    SDL_RenderPresent(renderer);
  };

  // Notify render thread to exit app
  gui_ctx.quit = true;

  render_thread.join();

  ImGui_ImplSDLRenderer_Shutdown();
  ImGui_ImplSDL2_Shutdown();

  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);

  SDL_Quit();

  ImNodes::DestroyContext();
  ImGui::DestroyContext();

#if defined(USDVIEW_USE_NATIVEFILEDIALOG)
  NFD_Quit();
#endif

#endif

  return EXIT_SUCCESS;
}
