namespace ThreadView
{

static const ImVec4 CurrentColor = HEXTOIMV4(0x000000, 1.0f);
static const ImVec4 CurrentBgColor = HEXTOIMV4(0x00E676, 1.0f);

struct ThreadInfo
{
   coreinit::OSThread *thread;
   uint32_t id;
   std::string name;
   coreinit::OSThreadState state;
   int32_t coreId;
   uint64_t coreTimeNs;
   int32_t priority;
   int32_t basePriority;
   uint32_t affinity;
};

bool
gIsVisible = true;

static std::vector<ThreadInfo>
sThreadsCache;

void
draw()
{
   if (!gIsVisible) {
      return;
   }

   ImGui::SetNextWindowSize(ImVec2(600, 300), ImGuiSetCond_FirstUseEver);

   if (!ImGui::Begin("Threads", &gIsVisible)) {
      ImGui::End();
      return;
   }

   sThreadsCache.clear();

   coreinit::internal::lockScheduler();
   auto core0Thread = coreinit::internal::getCoreRunningThread(0);
   auto core1Thread = coreinit::internal::getCoreRunningThread(1);
   auto core2Thread = coreinit::internal::getCoreRunningThread(2);
   auto firstThread = coreinit::internal::getFirstActiveThread();

   for (auto thread = firstThread; thread; thread = thread->activeLink.next) {
      ThreadInfo tinfo;
      tinfo.thread = thread;
      tinfo.id = thread->id;
      tinfo.name = thread->name ? thread->name.get() : "";
      tinfo.state = thread->state;
      tinfo.priority = thread->priority;
      tinfo.basePriority = thread->basePriority;
      tinfo.affinity = thread->attr & coreinit::OSThreadAttributes::AffinityAny;

      if (thread == core0Thread) {
         tinfo.coreId = 0;
      } else if (thread == core1Thread) {
         tinfo.coreId = 1;
      } else if (thread == core2Thread) {
         tinfo.coreId = 2;
      } else {
         tinfo.coreId = -1;
      }

      tinfo.coreTimeNs = thread->coreTimeConsumedNs;

      if (tinfo.coreId != -1) {
         tinfo.coreTimeNs += coreinit::internal::getCoreThreadRunningTime(0);
      }

      sThreadsCache.push_back(tinfo);
   }

   coreinit::internal::unlockScheduler();

   ImGui::Columns(8, "threadList", false);
   ImGui::SetColumnOffset(0, ImGui::GetWindowWidth() * 0.00f);
   ImGui::SetColumnOffset(1, ImGui::GetWindowWidth() * 0.05f);
   ImGui::SetColumnOffset(2, ImGui::GetWindowWidth() * 0.35f);
   ImGui::SetColumnOffset(3, ImGui::GetWindowWidth() * 0.50f);
   ImGui::SetColumnOffset(4, ImGui::GetWindowWidth() * 0.60f);
   ImGui::SetColumnOffset(5, ImGui::GetWindowWidth() * 0.70f);
   ImGui::SetColumnOffset(6, ImGui::GetWindowWidth() * 0.75f);
   ImGui::SetColumnOffset(7, ImGui::GetWindowWidth() * 0.84f);

   ImGui::Text("ID"); ImGui::NextColumn();
   ImGui::Text("Name"); ImGui::NextColumn();
   ImGui::Text("NIA"); ImGui::NextColumn();
   ImGui::Text("State"); ImGui::NextColumn();
   ImGui::Text("Prio"); ImGui::NextColumn();
   ImGui::Text("Aff"); ImGui::NextColumn();
   ImGui::Text("Core"); ImGui::NextColumn();
   ImGui::Text("Core Time"); ImGui::NextColumn();
   ImGui::Separator();

   for (auto &thread : sThreadsCache) {
      // ID
      if (thread.thread == getActiveThread()) {
         // Highlight the currently active thread
         // TODO: Clean this up
         auto idStr = fmt::format("{}", thread.id);
         auto drawList = ImGui::GetWindowDrawList();
         auto lineHeight = ImGui::GetTextLineHeight();
         float glyphWidth = ImGui::CalcTextSize("FF").x - ImGui::CalcTextSize("F").x;
         float idWidth = glyphWidth * idStr.length();
         auto rootPos = ImGui::GetCursorScreenPos();
         auto idMin = ImVec2(rootPos.x - 1, rootPos.y);
         auto idMax = ImVec2(rootPos.x + idWidth + 2, rootPos.y + lineHeight + 1);
         drawList->AddRectFilled(idMin, idMax, ImColor(CurrentBgColor), 2.0f);
         ImGui::TextColored(CurrentColor, "%s", idStr.c_str());
      } else {
         ImGui::Text("%d", thread.id);
      }
      ImGui::NextColumn();

      // Name
      if (isPaused()) {
         auto threadName = thread.name;

         if (thread.name.size() == 0) {
            threadName = fmt::format("(Unnamed Thread {})", thread.id);
         }

         if (ImGui::Selectable(threadName.c_str())) {
            setActiveThread(thread.thread);
         }
      } else {
         ImGui::Text("%s", thread.name.c_str());
      }

      ImGui::NextColumn();

      // NIA
      if (isPaused()) {
         ImGui::Text("%08x", getThreadNia(thread.thread));
      } else {
         ImGui::Text("        ");
      }

      ImGui::NextColumn();

      // Thread State
      ImGui::Text("%s", coreinit::enumAsString(thread.state).c_str());
      ImGui::NextColumn();

      // Priority
      ImGui::Text("%d (%d)", thread.priority, thread.basePriority);
      ImGui::NextColumn();

      // Affinity
      std::string coreAff;

      if (thread.affinity & coreinit::OSThreadAttributes::AffinityCPU0) {
         coreAff += "0";
      }

      if (thread.affinity & coreinit::OSThreadAttributes::AffinityCPU1) {
         if (coreAff.size() != 0) {
            coreAff += "|1";
         } else {
            coreAff += "1";
         }
      }

      if (thread.affinity & coreinit::OSThreadAttributes::AffinityCPU2) {
         if (coreAff.size() != 0) {
            coreAff += "|2";
         } else {
            coreAff += "2";
         }
      }

      ImGui::Text("%s", coreAff.c_str());
      ImGui::NextColumn();

      // Core Id
      if (thread.coreId != -1) {
         ImGui::Text("%d", thread.coreId);
      }

      ImGui::NextColumn();

      // Core Time
      ImGui::Text("%" PRIu64, thread.coreTimeNs / 1000);
      ImGui::NextColumn();
   }

   ImGui::Columns(1);
   ImGui::End();
}

} // namespace ThreadView
namespace debugui
{

static const ImVec4 CurrentColor = HEXTOIMV4(0x000000, 1.0f);
static const ImVec4 CurrentBgColor = HEXTOIMV4(0x00E676, 1.0f);

VoicesWindow::VoicesWindow(const std::string &name) :
   Window(name)
{
}

void
VoicesWindow::draw()
{
   ImGui::SetNextWindowSize(ImVec2 { 600, 300 }, ImGuiSetCond_FirstUseEver);

   if (!ImGui::Begin(mName.c_str(), &mVisible)) {
      ImGui::End();
      return;
   }

   ImGui::Columns(9, "voicesList", false);

   ImGui::Text("ID"); ImGui::NextColumn();
   ImGui::Text("State"); ImGui::NextColumn();
   ImGui::Text("Type"); ImGui::NextColumn();
   ImGui::Text("Strm"); ImGui::NextColumn();
   ImGui::Text("Base Addr"); ImGui::NextColumn();
   ImGui::Text("Current Off"); ImGui::NextColumn();
   ImGui::Text("End Off"); ImGui::NextColumn();
   ImGui::Text("Loop Off"); ImGui::NextColumn();
   ImGui::Text("Loop Mode"); ImGui::NextColumn();
   ImGui::Separator();

   auto voices = std::vector<decaf::debug::CafeVoice> {};
   if (decaf::debug::sampleCafeVoices(voices)) {
      for (auto &voice : voices) {
         ImGui::Text("%d", voice.index);
         ImGui::NextColumn();

         if (voice.state == decaf::debug::CafeVoice::Playing) {
            ImGui::Text("Playing");
         } else {
            ImGui::Text("Stopped");
         }
         ImGui::NextColumn();

         if (voice.format == decaf::debug::CafeVoice::ADPCM) {
            ImGui::Text("ADPCM");
         } else if (voice.format == decaf::debug::CafeVoice::LPCM16) {
            ImGui::Text("LPCM16");
         } else if (voice.format == decaf::debug::CafeVoice::LPCM8) {
            ImGui::Text("LPCM8");
         } else {
            ImGui::Text("Unknown");
         }
         ImGui::NextColumn();

         if (voice.type == decaf::debug::CafeVoice::Default) {
            ImGui::Text("Default");
         } else if (voice.type == decaf::debug::CafeVoice::Streaming) {
            ImGui::Text("Stream");
         } else {
            ImGui::Text("Unknown");
         }
         ImGui::NextColumn();

         ImGui::Text("%08x", voice.data);
         ImGui::NextColumn();

         ImGui::Text("%x", voice.currentOffset);
         ImGui::NextColumn();

         ImGui::Text("%x", voice.endOffset);
         ImGui::NextColumn();

         ImGui::Text("%x", voice.loopOffset);
         ImGui::NextColumn();

         ImGui::Text("%d", voice.loopingEnabled);
         ImGui::NextColumn();
      }
   }

   ImGui::Columns(1);
   ImGui::End();
}

} // namespace debugui