text: // Copyright (c) FIRST and other WPILib contributors.
  // Open Source Software; you can modify and/or share it under the terms of
  // the WPILib BSD license file in the root directory of this project.
  
  #include "glass/Context.h"
  
  #include <algorithm>
  #include <cinttypes>
  #include <cstdio>
  
  #include <imgui.h>
  #include <imgui_internal.h>
  #include <imgui_stdlib.h>
  #include <wpi/StringExtras.h>
  #include <wpi/timestamp.h>
  #include <wpigui.h>
  
  #include "glass/ContextInternal.h"
  
  using namespace glass;
  
  Context* glass::gContext;
  
  static bool ConvertInt(Storage::Value* value) {
    value->type = Storage::Value::kInt;
    if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
      value->intVal = val.value();
      return true;
    }
    return false;
  }
  
  static bool ConvertInt64(Storage::Value* value) {
    value->type = Storage::Value::kInt64;
    if (auto val = wpi::parse_integer<int64_t>(value->stringVal, 10)) {
      value->int64Val = val.value();
      return true;
    }
    return false;
  }
  
  static bool ConvertBool(Storage::Value* value) {
    value->type = Storage::Value::kBool;
    if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
      value->intVal = (val.value() != 0);
      return true;
    }
    return false;
  }
  
  static bool ConvertFloat(Storage::Value* value) {
    value->type = Storage::Value::kFloat;
    if (auto val = wpi::parse_float<float>(value->stringVal)) {
      value->floatVal = val.value();
      return true;
    }
    return false;
  }
  
  static bool ConvertDouble(Storage::Value* value) {
    value->type = Storage::Value::kDouble;
    if (auto val = wpi::parse_float<double>(value->stringVal)) {
      value->doubleVal = val.value();
      return true;
    }
    return false;
  }
  
  static void* GlassStorageReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler,
                                    const char* name) {
    auto ctx = static_cast<Context*>(handler->UserData);
    auto& storage = ctx->storage[name];
    if (!storage) {
      storage = std::make_unique<Storage>();
    }
    return storage.get();
  }
  
  static void GlassStorageReadLine(ImGuiContext*, ImGuiSettingsHandler*,
                                   void* entry, const char* line) {
    auto storage = static_cast<Storage*>(entry);
    auto [key, val] = wpi::split(line, '=');
    auto& keys = storage->GetKeys();
    auto& values = storage->GetValues();
    auto it = std::find(keys.begin(), keys.end(), key);
    if (it == keys.end()) {
      keys.emplace_back(key);
      values.emplace_back(std::make_unique<Storage::Value>(val));
    } else {
      auto& value = *values[it - keys.begin()];
      value.stringVal = val;
      switch (value.type) {
        case Storage::Value::kInt:
          ConvertInt(&value);
          break;
        case Storage::Value::kInt64:
          ConvertInt64(&value);
          break;
        case Storage::Value::kBool:
          ConvertBool(&value);
          break;
        case Storage::Value::kFloat:
          ConvertFloat(&value);
          break;
        case Storage::Value::kDouble:
          ConvertDouble(&value);
          break;
        default:
          break;
      }
    }
  }
  
  static void GlassStorageWriteAll(ImGuiContext*, ImGuiSettingsHandler* handler,
                                   ImGuiTextBuffer* out_buf) {
    auto ctx = static_cast<Context*>(handler->UserData);
  
    // sort for output
    std::vector<wpi::StringMapConstIterator<std::unique_ptr<Storage>>> sorted;
    for (auto it = ctx->storage.begin(); it != ctx->storage.end(); ++it) {
      sorted.emplace_back(it);
    }
    std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
      return a->getKey() < b->getKey();
    });
  
    for (auto&& entryIt : sorted) {
      auto& entry = *entryIt;
      out_buf->append("[GlassStorage][");
      out_buf->append(entry.first().data(),
                      entry.first().data() + entry.first().size());
      out_buf->append("]\n");
      auto& keys = entry.second->GetKeys();
      auto& values = entry.second->GetValues();
      for (size_t i = 0; i < keys.size(); ++i) {
        out_buf->append(keys[i].data(), keys[i].data() + keys[i].size());
        out_buf->append("=");
        auto& value = *values[i];
        switch (value.type) {
          case Storage::Value::kInt:
            out_buf->appendf("%d\n", value.intVal);
            break;
          case Storage::Value::kInt64:
            out_buf->appendf("%" PRId64 "\n", value.int64Val);
            break;
          case Storage::Value::kBool:
            out_buf->appendf("%d\n", value.boolVal ? 1 : 0);
            break;
          case Storage::Value::kFloat:
            out_buf->appendf("%f\n", value.floatVal);
            break;
          case Storage::Value::kDouble:
            out_buf->appendf("%f\n", value.doubleVal);
            break;
          case Storage::Value::kNone:
          case Storage::Value::kString:
            out_buf->append(value.stringVal.data(),
                            value.stringVal.data() + value.stringVal.size());
            out_buf->append("\n");
            break;
        }
      }
      out_buf->append("\n");
    }
  }
  
  static void Initialize(Context* ctx) {
    wpi::gui::AddInit([=] {
      ImGuiSettingsHandler ini_handler;
      ini_handler.TypeName = "GlassStorage";
      ini_handler.TypeHash = ImHashStr("GlassStorage");
      ini_handler.ReadOpenFn = GlassStorageReadOpen;
      ini_handler.ReadLineFn = GlassStorageReadLine;
      ini_handler.WriteAllFn = GlassStorageWriteAll;
      ini_handler.UserData = ctx;
      ImGui::GetCurrentContext()->SettingsHandlers.push_back(ini_handler);
  
      ctx->sources.Initialize();
    });
  }
  
  static void Shutdown(Context* ctx) {}
  
  Context* glass::CreateContext() {
    Context* ctx = new Context;
    if (!gContext) {
      SetCurrentContext(ctx);
    }
    Initialize(ctx);
    return ctx;
  }
  
  void glass::DestroyContext(Context* ctx) {
    if (!ctx) {
      ctx = gContext;
    }
    Shutdown(ctx);
    if (gContext == ctx) {
      SetCurrentContext(nullptr);
    }
    delete ctx;
  }
  
  Context* glass::GetCurrentContext() {
    return gContext;
  }
  
  void glass::SetCurrentContext(Context* ctx) {
    gContext = ctx;
  }
  
  void glass::ResetTime() {
    gContext->zeroTime = wpi::Now();
  }
  
  uint64_t glass::GetZeroTime() {
    return gContext->zeroTime;
  }
  
  Storage::Value& Storage::GetValue(std::string_view key) {
    auto it = std::find(m_keys.begin(), m_keys.end(), key);
    if (it == m_keys.end()) {
      m_keys.emplace_back(key);
      m_values.emplace_back(std::make_unique<Value>());
      return *m_values.back();
    } else {
      return *m_values[it - m_keys.begin()];
    }
  }
  
  #define DEFUN(CapsName, LowerName, CType)                                      \
    CType Storage::Get##CapsName(std::string_view key, CType defaultVal) const { \
      auto it = std::find(m_keys.begin(), m_keys.end(), key);                    \
      if (it == m_keys.end())                                                    \
        return defaultVal;                                                       \
      Value& value = *m_values[it - m_keys.begin()];                             \
      if (value.type != Value::k##CapsName) {                                    \
        if (!Convert##CapsName(&value))                                          \
          value.LowerName##Val = defaultVal;                                     \
      }                                                                          \
      return value.LowerName##Val;                                               \
    }                                                                            \
                                                                                 \
    void Storage::Set##CapsName(std::string_view key, CType val) {               \
      auto it = std::find(m_keys.begin(), m_keys.end(), key);                    \
      if (it == m_keys.end()) {                                                  \
        m_keys.emplace_back(key);                                                \
        m_values.emplace_back(std::make_unique<Value>());                        \
        m_values.back()->type = Value::k##CapsName;                              \
        m_values.back()->LowerName##Val = val;                                   \
      } else {                                                                   \
        Value& value = *m_values[it - m_keys.begin()];                           \
        value.type = Value::k##CapsName;                                         \
        value.LowerName##Val = val;                                              \
      }                                                                          \
    }                                                                            \
                                                                                 \
    CType* Storage::Get##CapsName##Ref(std::string_view key, CType defaultVal) { \
      auto it = std::find(m_keys.begin(), m_keys.end(), key);                    \
      if (it == m_keys.end()) {                                                  \
        m_keys.emplace_back(key);                                                \
        m_values.emplace_back(std::make_unique<Value>());                        \
        m_values.back()->type = Value::k##CapsName;                              \
        m_values.back()->LowerName##Val = defaultVal;                            \
        return &m_values.back()->LowerName##Val;                                 \
      } else {                                                                   \
        Value& value = *m_values[it - m_keys.begin()];                           \
        if (value.type != Value::k##CapsName) {                                  \
          if (!Convert##CapsName(&value))                                        \
            value.LowerName##Val = defaultVal;                                   \
        }                                                                        \
        return &value.LowerName##Val;                                            \
      }                                                                          \
    }
  
  DEFUN(Int, int, int)
  DEFUN(Int64, int64, int64_t)
  DEFUN(Bool, bool, bool)
  DEFUN(Float, float, float)
  DEFUN(Double, double, double)
  
  std::string Storage::GetString(std::string_view key,
                                 std::string_view defaultVal) const {
    auto it = std::find(m_keys.begin(), m_keys.end(), key);
    if (it == m_keys.end()) {
      return std::string{defaultVal};
    }
    Value& value = *m_values[it - m_keys.begin()];
    value.type = Value::kString;
    return value.stringVal;
  }
  
  void Storage::SetString(std::string_view key, std::string_view val) {
    auto it = std::find(m_keys.begin(), m_keys.end(), key);
    if (it == m_keys.end()) {
      m_keys.emplace_back(key);
      m_values.emplace_back(std::make_unique<Value>(val));
      m_values.back()->type = Value::kString;
    } else {
      Value& value = *m_values[it - m_keys.begin()];
      value.type = Value::kString;
      value.stringVal = val;
    }
  }
  
  std::string* Storage::GetStringRef(std::string_view key,
                                     std::string_view defaultVal) {
    auto it = std::find(m_keys.begin(), m_keys.end(), key);
    if (it == m_keys.end()) {
      m_keys.emplace_back(key);
      m_values.emplace_back(std::make_unique<Value>(defaultVal));
      m_values.back()->type = Value::kString;
      return &m_values.back()->stringVal;
    } else {
      Value& value = *m_values[it - m_keys.begin()];
      value.type = Value::kString;
      return &value.stringVal;
    }
  }
  
  Storage& glass::GetStorage() {
    auto& storage = gContext->storage[gContext->curId];
    if (!storage) {
      storage = std::make_unique<Storage>();
    }
    return *storage;
  }
  
  Storage& glass::GetStorage(std::string_view id) {
    auto& storage = gContext->storage[id];
    if (!storage) {
      storage = std::make_unique<Storage>();
    }
    return *storage;
  }
  
  static void PushIDStack(std::string_view label_id) {
    gContext->idStack.emplace_back(gContext->curId.size());
  
    auto [label, id] = wpi::split(label_id, "###");
    // if no ###id, use label as id
    if (id.empty()) {
      id = label;
    }
    if (!gContext->curId.empty()) {
      gContext->curId += "###";
    }
    gContext->curId += id;
  }
  
  static void PopIDStack() {
    gContext->curId.resize(gContext->idStack.back());
    gContext->idStack.pop_back();
  }
  
  bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
    PushIDStack(name);
    return ImGui::Begin(name, p_open, flags);
  }
  
  void glass::End() {
    ImGui::End();
    PopIDStack();
  }
  
  bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
                         ImGuiWindowFlags flags) {
    PushIDStack(str_id);
    return ImGui::BeginChild(str_id, size, border, flags);
  }
  
  void glass::EndChild() {
    ImGui::EndChild();
    PopIDStack();
  }
  
  bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
    wpi::SmallString<64> openKey;
    auto [name, id] = wpi::split(label, "###");
    // if no ###id, use name as id
    if (id.empty()) {
      id = name;
    }
    openKey = id;
    openKey += "###open";
  
    bool* open = GetStorage().GetBoolRef(openKey.str());
    *open = ImGui::CollapsingHeader(
        label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
    return *open;
  }
  
  bool glass::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) {
    PushIDStack(label);
    bool* open = GetStorage().GetBoolRef("open");
    *open = ImGui::TreeNodeEx(
        label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
    if (!*open) {
      PopIDStack();
    }
    return *open;
  }
  
  void glass::TreePop() {
    ImGui::TreePop();
    PopIDStack();
  }
  
  void glass::PushID(const char* str_id) {
    PushIDStack(str_id);
    ImGui::PushID(str_id);
  }
  
  void glass::PushID(const char* str_id_begin, const char* str_id_end) {
    PushIDStack(std::string_view(str_id_begin, str_id_end - str_id_begin));
    ImGui::PushID(str_id_begin, str_id_end);
  }
  
  void glass::PushID(int int_id) {
    char buf[16];
    std::snprintf(buf, sizeof(buf), "%d", int_id);
    PushIDStack(buf);
    ImGui::PushID(int_id);
  }
  
  void glass::PopID() {
    ImGui::PopID();
    PopIDStack();
  }
  
  bool glass::PopupEditName(const char* label, std::string* name) {
    bool rv = false;
    if (ImGui::BeginPopupContextItem(label)) {
      ImGui::Text("Edit name:");
      if (ImGui::InputText("##editname", name)) {
        rv = true;
      }
      if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
          ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
        ImGui::CloseCurrentPopup();
      }
      ImGui::EndPopup();
    }
    return rv;
  }
  