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

/** \file
 * \ingroup sequencer
 */

#include "BLI_map.hh"

#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"

#include "IMB_imbuf.hh"

#include "SEQ_sequencer.hh"

#include "intra_frame_cache.hh"

namespace blender::seq {

struct StripImageMap {
  Map<const Strip *, SeqResult> map_;
  SeqResult get(const Strip *strip) const;
  void put(const Strip *strip, const SeqResult &result);
  void invalidate(const Strip *strip);
  void clear();
};

struct IntraFrameCache {
  StripImageMap preprocessed;
  StripImageMap composite;
  float timeline_frame = -1.0f;
  int view_id = -1;
  int width = -1;
  int height = -1;
  bool is_render = false;

  ~IntraFrameCache()
  {
    preprocessed.clear();
    composite.clear();
  }
};

static IntraFrameCache *query_intra_frame_cache(Scene *scene)
{
  if (scene == nullptr || scene->ed == nullptr) {
    return nullptr;
  }
  return scene->ed->runtime->intra_frame_cache;
}

void intra_frame_cache_invalidate(Scene *scene)
{
  IntraFrameCache *cache = query_intra_frame_cache(scene);
  if (cache != nullptr) {
    cache->preprocessed.clear();
    cache->composite.clear();
    cache->timeline_frame = -1.0f;
    cache->view_id = -1;
    cache->width = -1;
    cache->height = -1;
    cache->is_render = false;
  }
}

void intra_frame_cache_invalidate(Scene *scene, const Strip *strip)
{
  if (strip == nullptr) {
    return;
  }
  IntraFrameCache *cache = query_intra_frame_cache(scene);
  if (cache != nullptr) {
    cache->preprocessed.invalidate(strip);
    cache->composite.invalidate(strip);
  }
}

void StripImageMap::invalidate(const Strip *strip)
{
  /* Invalidate this strip, and all strips that are above it. */
  for (auto it = this->map_.items().begin(); it != this->map_.items().end(); it++) {
    const Strip *key = (*it).key;
    if (key == strip || key->channel >= strip->channel) {
      IMB_freeImBuf((*it).value.image);
      this->map_.remove(it);
    }
  }
}

SeqResult StripImageMap::get(const Strip *strip) const
{
  SeqResult result = this->map_.lookup_default(strip, {});
  if (result.is_valid()) {
    IMB_refImBuf(result.image);
  }
  return result;
}

void StripImageMap::put(const Strip *strip, const SeqResult &result)
{
  BLI_assert(strip != nullptr);
  if (!result.is_valid()) {
    return;
  }
  SeqResult existing = this->map_.lookup_default(strip, {});
  if (existing.is_valid()) {
    IMB_freeImBuf(existing.image);
  }
  this->map_.add_overwrite(strip, result);
  IMB_refImBuf(result.image);
}

void StripImageMap::clear()
{
  for (const auto &item : this->map_.items()) {
    IMB_freeImBuf(item.value.image);
  }
  this->map_.clear();
}

SeqResult intra_frame_cache_get_preprocessed(Scene *scene, const Strip *strip)
{
  IntraFrameCache *cache = query_intra_frame_cache(scene);
  if (strip == nullptr || cache == nullptr) {
    return {};
  }
  return cache->preprocessed.get(strip);
}

SeqResult intra_frame_cache_get_composite(Scene *scene, const Strip *strip)
{
  IntraFrameCache *cache = query_intra_frame_cache(scene);
  if (strip == nullptr || cache == nullptr) {
    return {};
  }
  return cache->composite.get(strip);
}

void intra_frame_cache_put_preprocessed(Scene *scene, const Strip *strip, const SeqResult &result)
{
  if (scene == nullptr || scene->ed == nullptr || strip == nullptr || !result.is_valid()) {
    return;
  }
  IntraFrameCache *&cache = scene->ed->runtime->intra_frame_cache;
  if (cache == nullptr) {
    cache = MEM_new<IntraFrameCache>(__func__);
  }
  cache->preprocessed.put(strip, result);
}

void intra_frame_cache_put_composite(Scene *scene, const Strip *strip, const SeqResult &result)
{
  if (scene == nullptr || scene->ed == nullptr || strip == nullptr || !result.is_valid()) {
    return;
  }
  IntraFrameCache *&cache = scene->ed->runtime->intra_frame_cache;
  if (cache == nullptr) {
    cache = MEM_new<IntraFrameCache>(__func__);
  }
  cache->composite.put(strip, result);
}

void intra_frame_cache_destroy(Scene *scene)
{
  IntraFrameCache *cache = query_intra_frame_cache(scene);
  if (cache != nullptr) {
    MEM_SAFE_DELETE(scene->ed->runtime->intra_frame_cache);
  }
}

void intra_frame_cache_set_cur_frame(
    Scene *scene, float frame, int view_id, int width, int height, bool is_render)
{
  IntraFrameCache *cache = query_intra_frame_cache(scene);
  if (cache != nullptr) {
    if (cache->timeline_frame != frame || cache->view_id != view_id || cache->width != width ||
        cache->height != height || cache->is_render != is_render)
    {
      cache->timeline_frame = frame;
      cache->view_id = view_id;
      cache->width = width;
      cache->height = height;
      cache->is_render = is_render;
      cache->preprocessed.clear();
      cache->composite.clear();
    }
  }
}

}  // namespace blender::seq
