/* SPDX-FileCopyrightText: 2024 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup edinterface
 */

#include <fmt/format.h>

#include "AS_asset_library.hh"
#include "AS_remote_library.hh"

#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_main.hh"

#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "BLI_time.h"

#include "BLI_timecode.h"
#include "BLT_translation.hh"

#include "ED_screen.hh"

#include "UI_resources.hh"
#include "WM_api.hh"

#include "UI_interface_c.hh"
#include "UI_interface_layout.hh"
#include "interface_intern.hh"

namespace blender::ui {

struct ProgressTooltip_Store {
  wmWindowManager *wm;
  void *owner;
};

static std::string progress_tooltip_func(bContext * /*C*/, void *argN, const StringRef /*tip*/)
{
  ProgressTooltip_Store *arg = static_cast<ProgressTooltip_Store *>(argN);
  wmWindowManager *wm = arg->wm;
  void *owner = arg->owner;

  const float progress = WM_jobs_progress(wm, owner);

  /* create tooltip text and associate it with the job */
  char elapsed_str[32];
  char remaining_str[32];
  const double elapsed = BLI_time_now_seconds() - WM_jobs_starttime(wm, owner);
  BLI_timecode_string_from_time_simple(elapsed_str, sizeof(elapsed_str), elapsed);

  if (progress) {
    const double remaining = (elapsed / double(progress)) - elapsed;
    BLI_timecode_string_from_time_simple(remaining_str, sizeof(remaining_str), remaining);
  }

  return fmt::format(fmt::runtime(TIP_("Time Remaining: {}\n"
                                       "Time Elapsed: {}")),
                     progress ? remaining_str : TIP_("Unknown"),
                     elapsed_str);
}

static void cancel_all_scene_jobs(bContext &C)
{
  WM_jobs_stop_all_from_owner(CTX_wm_manager(&C), CTX_data_scene(&C));
}

static void set_global_break(bContext &C)
{
  WM_jobs_stop_all_from_owner(CTX_wm_manager(&C), CTX_data_scene(&C));
}

void template_running_jobs(Layout *layout, bContext *C)
{
  Main *bmain = CTX_data_main(C);
  wmWindowManager *wm = CTX_wm_manager(C);
  ScrArea *area = CTX_wm_area(C);

  void *owner = nullptr;
  int icon = 0;
  std::function<void(bContext &)> cancel_fn = nullptr;
  const char *op_name = nullptr;
  const char *op_description = nullptr;

  Block *block = layout->block();
  block_layout_set_current(block, layout);

  /* another scene can be rendering too, for example via compositor */
  for (Scene &scene : bmain->scenes) {
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_ANY)) {
      cancel_fn = set_global_break;
      icon = ICON_NONE;
      owner = &scene;
    }
    else {
      continue;
    }

    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_SEQ_BUILD_PROXY)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_SEQUENCE;
      owner = &scene;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_SEQ_BUILD_PREVIEW)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_SEQUENCE;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_SEQUENCE;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_CLIP_BUILD_PROXY)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_TRACKER;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_CLIP_PREFETCH)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_TRACKER;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_CLIP_TRACK_MARKERS)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_TRACKER;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_CLIP_SOLVE_CAMERA)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_TRACKER;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_RENDER)) {
      cancel_fn = set_global_break;
      icon = ICON_SCENE;
      if (U.render_display_type != USER_RENDER_DISPLAY_NONE) {
        op_name = "RENDER_OT_view_show";
        op_description = "Show the render window";
      }
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_COMPOSITE)) {
      cancel_fn = cancel_all_scene_jobs;
      icon = ICON_RENDERLAYERS;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_OBJECT_BAKE_TEXTURE) ||
        WM_jobs_test(wm, &scene, WM_JOB_TYPE_OBJECT_BAKE))
    {
      /* Skip bake jobs in compositor to avoid compo header displaying progress bar
       * which is not being updated (bake jobs only need to update NC_IMAGE context). */
      if (area->spacetype != SPACE_NODE) {
        cancel_fn = set_global_break;
        icon = ICON_IMAGE;
        break;
      }
      continue;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_DPAINT_BAKE)) {
      cancel_fn = set_global_break;
      icon = ICON_MOD_DYNAMICPAINT;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_POINTCACHE)) {
      cancel_fn = set_global_break;
      icon = ICON_PHYSICS;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_OBJECT_SIM_FLUID)) {
      cancel_fn = set_global_break;
      icon = ICON_MOD_FLUIDSIM;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_OBJECT_SIM_OCEAN)) {
      cancel_fn = set_global_break;
      icon = ICON_MOD_OCEAN;
      break;
    }
    if (WM_jobs_test(wm, &scene, WM_JOB_TYPE_SOUND_MIXDOWN)) {
      cancel_fn = set_global_break;
      icon = ICON_FILE_SOUND;
      break;
    }
  }
  if (!owner) {
    for (wmWindow &win : wm->windows) {
      const bScreen *screen = WM_window_get_active_screen(&win);
      for (ScrArea &area : screen->areabase) {
        if (area.spacetype != SPACE_FILE) {
          continue;
        }
        const SpaceFile *sfile = static_cast<SpaceFile *>(area.spacedata.first);

        if (WM_jobs_test(wm, sfile->files, WM_JOB_TYPE_FILESEL_READDIR)) {
          icon = ICON_FILEBROWSER;
          owner = sfile->files;
          cancel_fn = [sfile](bContext &C) {
            WM_jobs_stop_all_from_owner(CTX_wm_manager(&C), sfile->files);
          };
          break;
        }

        if (WM_jobs_test(wm, sfile->files, WM_JOB_TYPE_ASSET_LIBRARY_LOAD)) {
          const bool needs_cancelling_online_assets =
              sfile->asset_params && asset_system::is_or_contains_remote_libraries(
                                         sfile->asset_params->asset_library_ref);
          icon = ICON_ASSET_MANAGER;
          owner = sfile->files;
          cancel_fn = [sfile, needs_cancelling_online_assets](bContext &C) {
            WM_jobs_stop_all_from_owner(CTX_wm_manager(&C), sfile->files);
            if (needs_cancelling_online_assets) {
              asset_system::remote_library_cancel_all_listing_downloads(C);
            }
          };
          break;
        }
      }
      if (owner) {
        break;
      }
    }
  }

  /* Blend file wide jobs. */
  if (owner == nullptr) {
    if (WM_jobs_test(wm, bmain, WM_JOB_TYPE_GENERATE_TEXTURE_CACHE)) {
      owner = bmain;
      cancel_fn = set_global_break;
      icon = ICON_TEXTURE;
    }
  }

  if (owner) {
    const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
    const bool active = !(G.is_break || WM_jobs_is_stopped(wm, owner));

    Layout *row = &layout->row(false);
    block = row->block();

    /* get percentage done and set it as the UI text */
    const float progress = WM_jobs_progress(wm, owner);
    char text[8];
    SNPRINTF_UTF8(text, "%d%%", int(progress * 100));

    const char *name = active ? RPT_(WM_jobs_name(wm, owner)) : RPT_("Canceling...");

    /* job icon as a button */
    if (op_name) {
      uiDefIconButO(block,
                    ButtonType::But,
                    op_name,
                    wm::OpCallContext::InvokeDefault,
                    icon,
                    0,
                    0,
                    UI_UNIT_X,
                    UI_UNIT_Y,
                    TIP_(op_description));
    }

    /* job name and icon if not previously set */
    const int textwidth = fontstyle_string_width(fstyle, name);
    uiDefIconTextBut(block,
                     ButtonType::Label,
                     op_name ? 0 : icon,
                     name,
                     0,
                     0,
                     textwidth + UI_UNIT_X * 1.5f,
                     UI_UNIT_Y,
                     nullptr,
                     "");

    /* stick progress bar and cancel button together */
    row = &layout->row(true);
    row->active_set(active);
    block = row->block();

    {
      ProgressTooltip_Store *tip_arg = MEM_new_uninitialized<ProgressTooltip_Store>(__func__);
      tip_arg->wm = wm;
      tip_arg->owner = owner;
      ButtonProgress *but_progress = static_cast<ButtonProgress *>(
          uiDefIconTextBut(block,
                           ButtonType::Progress,
                           ICON_NONE,
                           text,
                           UI_UNIT_X,
                           0,
                           UI_UNIT_X * 6.0f,
                           UI_UNIT_Y,
                           nullptr,
                           nullptr));

      but_progress->progress_factor = progress;
      button_func_tooltip_set(but_progress, progress_tooltip_func, tip_arg, MEM_delete_void);
    }

    if (cancel_fn && !wm->runtime->is_interface_locked) {
      Button *but = uiDefIconTextBut(block,
                                     ButtonType::But,
                                     ICON_PANEL_CLOSE,
                                     "",
                                     0,
                                     0,
                                     UI_UNIT_X,
                                     UI_UNIT_Y,
                                     nullptr,
                                     TIP_("Stop this job"));
      button_func_set(but, std::move(cancel_fn));
    }
  }

  if (ED_screen_animation_no_scrub(wm)) {
    Button *but = uiDefIconTextBut(block,
                                   ButtonType::But,
                                   ICON_CANCEL,
                                   IFACE_("Anim Player"),
                                   0,
                                   0,
                                   UI_UNIT_X * 5.0f,
                                   UI_UNIT_Y,
                                   nullptr,
                                   TIP_("Stop animation playback"));
    button_func_set(but, [](bContext &C) {
      WM_operator_name_call(
          &C, "SCREEN_OT_animation_play", wm::OpCallContext::InvokeScreen, nullptr, nullptr);
    });
  }

  /* Not using the jobs system, but should be shown everywhere where jobs are shown too. */
  if (asset_system::remote_library_has_unfinished_asset_downloads()) {
    uiDefIconTextBut(block,
                     ButtonType::Label,
                     ICON_ASSET_MANAGER,
                     IFACE_("Downloading Assets"),
                     0,
                     0,
                     UI_UNIT_X,
                     UI_UNIT_Y,
                     nullptr,
                     "");

    Layout *row = &layout->row(false);
    block = row->block();
    row = &layout->row(true);

    const float progress = asset_system::remote_library_total_asset_downloads_progress();
    char text[8];
    SNPRINTF_UTF8(text, "%d%%", int(progress * 100));

    ButtonProgress *but_progress = static_cast<ButtonProgress *>(
        uiDefIconTextBut(block,
                         ButtonType::Progress,
                         ICON_NONE,
                         text,
                         UI_UNIT_X,
                         0,
                         UI_UNIT_X * 6.0f,
                         UI_UNIT_Y,
                         nullptr,
                         nullptr));
    but_progress->progress_factor = progress;

    Button *but = uiDefIconTextBut(block,
                                   ButtonType::But,
                                   ICON_PANEL_CLOSE,
                                   "",
                                   0,
                                   0,
                                   UI_UNIT_X,
                                   UI_UNIT_Y,
                                   nullptr,
                                   TIP_("Cancel all asset downloads"));
    button_func_set(
        but, [](bContext &C) { asset_system::remote_library_cancel_all_asset_downloads(C); });
  }
}

}  // namespace blender::ui
