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

/** \file
 * \ingroup intern_memutil
 */

#include <cstddef>
#include <list>

#include "MEM_CacheLimiter.h"
#include "MEM_CacheLimiterC-Api.h"

static bool is_disabled = false;

static size_t &get_max()
{
  static size_t m = 32 * 1024 * 1024;
  return m;
}

void MEM_CacheLimiter_set_maximum(size_t m)
{
  get_max() = m;
}

size_t MEM_CacheLimiter_get_maximum()
{
  return get_max();
}

void MEM_CacheLimiter_set_disabled(bool disabled)
{
  is_disabled = disabled;
}

bool MEM_CacheLimiter_is_disabled(void)
{
  return is_disabled;
}

class MEM_CacheLimiterHandleCClass;
class MEM_CacheLimiterCClass;

using handle_t = MEM_CacheLimiterHandle<MEM_CacheLimiterHandleCClass>;
using cache_t = MEM_CacheLimiter<MEM_CacheLimiterHandleCClass>;
using list_t =
    std::list<MEM_CacheLimiterHandleCClass *, MEM_Allocator<MEM_CacheLimiterHandleCClass *>>;

class MEM_CacheLimiterCClass {
 public:
  MEM_CacheLimiterCClass(MEM_CacheLimiter_Destruct_Func data_destructor_,
                         MEM_CacheLimiter_DataSize_Func data_size)
      : data_destructor(data_destructor_), cache(data_size)
  {
  }
  ~MEM_CacheLimiterCClass();

  handle_t *insert(void *data);

  void destruct(void *data, list_t::iterator it);

  cache_t *get_cache()
  {
    return &cache;
  }

 private:
  MEM_CacheLimiter_Destruct_Func data_destructor;

  MEM_CacheLimiter<MEM_CacheLimiterHandleCClass> cache;

  list_t cclass_list;
};

class MEM_CacheLimiterHandleCClass {
 public:
  MEM_CacheLimiterHandleCClass(void *data_, MEM_CacheLimiterCClass *parent_)
      : data(data_), parent(parent_)
  {
  }

  ~MEM_CacheLimiterHandleCClass();

  void set_iter(list_t::iterator it_)
  {
    it = it_;
  }

  void set_data(void *data_)
  {
    data = data_;
  }

  void *get_data() const
  {
    return data;
  }

 private:
  void *data;
  MEM_CacheLimiterCClass *parent;
  list_t::iterator it;
};

handle_t *MEM_CacheLimiterCClass::insert(void *data)
{
  cclass_list.push_back(new MEM_CacheLimiterHandleCClass(data, this));
  list_t::iterator it = cclass_list.end();
  --it;
  cclass_list.back()->set_iter(it);

  return cache.insert(cclass_list.back());
}

void MEM_CacheLimiterCClass::destruct(void *data, list_t::iterator it)
{
  data_destructor(data);
  cclass_list.erase(it);
}

MEM_CacheLimiterHandleCClass::~MEM_CacheLimiterHandleCClass()
{
  if (data) {
    parent->destruct(data, it);
  }
}

MEM_CacheLimiterCClass::~MEM_CacheLimiterCClass()
{
  // should not happen, but don't leak memory in this case...
  for (list_t::iterator it = cclass_list.begin(); it != cclass_list.end(); it++) {
    (*it)->set_data(nullptr);

    delete *it;
  }
}

// ----------------------------------------------------------------------

static inline MEM_CacheLimiterCClass *cast(MEM_CacheLimiterC *l)
{
  return (MEM_CacheLimiterCClass *)l;
}

static inline handle_t *cast(MEM_CacheLimiterHandleC *l)
{
  return (handle_t *)l;
}

MEM_CacheLimiterC *new_MEM_CacheLimiter(MEM_CacheLimiter_Destruct_Func data_destructor,
                                        MEM_CacheLimiter_DataSize_Func data_size)
{
  return (MEM_CacheLimiterC *)new MEM_CacheLimiterCClass(data_destructor, data_size);
}

void delete_MEM_CacheLimiter(MEM_CacheLimiterC *This)
{
  delete cast(This);
}

MEM_CacheLimiterHandleC *MEM_CacheLimiter_insert(MEM_CacheLimiterC *This, void *data)
{
  return (MEM_CacheLimiterHandleC *)cast(This)->insert(data);
}

void MEM_CacheLimiter_enforce_limits(MEM_CacheLimiterC *This)
{
  cast(This)->get_cache()->enforce_limits();
}

void MEM_CacheLimiter_unmanage(MEM_CacheLimiterHandleC *handle)
{
  cast(handle)->unmanage();
}

void MEM_CacheLimiter_touch(MEM_CacheLimiterHandleC *handle)
{
  cast(handle)->touch();
}

void MEM_CacheLimiter_ref(MEM_CacheLimiterHandleC *handle)
{
  cast(handle)->ref();
}

void MEM_CacheLimiter_unref(MEM_CacheLimiterHandleC *handle)
{
  cast(handle)->unref();
}

int MEM_CacheLimiter_get_refcount(MEM_CacheLimiterHandleC *handle)
{
  return cast(handle)->get_refcount();
}

void *MEM_CacheLimiter_get(MEM_CacheLimiterHandleC *handle)
{
  return cast(handle)->get()->get_data();
}

void MEM_CacheLimiter_ItemPriority_Func_set(MEM_CacheLimiterC *This,
                                            MEM_CacheLimiter_ItemPriority_Func item_priority_func)
{
  cast(This)->get_cache()->set_item_priority_func(item_priority_func);
}

void MEM_CacheLimiter_ItemDestroyable_Func_set(
    MEM_CacheLimiterC *This, MEM_CacheLimiter_ItemDestroyable_Func item_destroyable_func)
{
  cast(This)->get_cache()->set_item_destroyable_func(item_destroyable_func);
}

size_t MEM_CacheLimiter_get_memory_in_use(MEM_CacheLimiterC *This)
{
  return cast(This)->get_cache()->get_memory_in_use();
}
