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

/** \file
 * \ingroup eevee
 *
 * Structures to identify unique data blocks. The keys are unique so we are able to
 * match ids across frame updates.
 */

#pragma once

#include "BKE_duplilist.hh"
#include "BLI_map.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_object_types.h"
#include "DRW_render.hh"
#include "GPU_material.hh"

#include "eevee_shader_shared.hh"

namespace blender::eevee {

class Instance;

/* -------------------------------------------------------------------- */
/** \name ObjectKey
 *
 * Unique key to identify each object in the hash-map.
 * Note that we get a unique key for each object component.
 * \{ */

class ObjectKey {
  /** Hash value of the key. */
  uint64_t hash_value_ = 0;
  /** Original Object or source object for duplis. */
  Object *ob_ = nullptr;
  /** Original Parent object for duplis. */
  Object *parent_ = nullptr;
  /** Dupli objects recursive unique identifier */
  int id_[MAX_DUPLI_RECUR];
  /** Used for particle system hair. */
  int sub_key_ = 0;

 public:
  ObjectKey() = default;

  ObjectKey(const ObjectRef &ob_ref, int sub_key = 0)
  {
    ob_ = DEG_get_original(ob_ref.object);
    hash_value_ = get_default_hash(ob_);

    if (DupliObject *dupli = ob_ref.dupli_object) {
      parent_ = ob_ref.dupli_parent;
      hash_value_ = get_default_hash(hash_value_, get_default_hash(parent_));
      for (int i : IndexRange(MAX_DUPLI_RECUR)) {
        id_[i] = dupli->persistent_id[i];
        if (id_[i] == INT_MAX) {
          break;
        }
        hash_value_ = get_default_hash(hash_value_, get_default_hash(id_[i]));
      }
    }

    if (sub_key != 0) {
      sub_key_ = sub_key;
      hash_value_ = get_default_hash(hash_value_, get_default_hash(sub_key_));
    }
  }

  uint64_t hash() const
  {
    return hash_value_;
  }

  bool operator==(const ObjectKey &k) const
  {
    if (hash_value_ != k.hash_value_) {
      return false;
    }
    if (ob_ != k.ob_) {
      return false;
    }
    if (parent_ != k.parent_) {
      return false;
    }
    if (sub_key_ != k.sub_key_) {
      return false;
    }
    if (parent_) {
      for (int i : IndexRange(MAX_DUPLI_RECUR)) {
        if (id_[i] != k.id_[i]) {
          return false;
        }
        if (id_[i] == INT_MAX) {
          break;
        }
      }
    }
    return true;
  }
};

/** \} */

/* -------------------------------------------------------------------- */
/** \name Sync Module
 *
 * \{ */

struct BaseHandle {
  unsigned int recalc;
};

struct ObjectHandle : BaseHandle {
  ObjectKey object_key;
};

struct WorldHandle : public BaseHandle {};

struct SceneHandle : public BaseHandle {};

class SyncModule {
 private:
  Instance &inst_;

  Map<ObjectKey, ObjectHandle> ob_handles = {};

 public:
  SyncModule(Instance &inst) : inst_(inst){};
  ~SyncModule(){};

  ObjectHandle &sync_object(const ObjectRef &ob_ref);
  WorldHandle sync_world(const ::World &world);

  void sync_mesh(Object *ob, ObjectHandle &ob_handle, const ObjectRef &ob_ref);
  bool sync_sculpt(Object *ob, ObjectHandle &ob_handle, const ObjectRef &ob_ref);
  void sync_pointcloud(Object *ob, ObjectHandle &ob_handle, const ObjectRef &ob_ref);
  void sync_volume(Object *ob, ObjectHandle &ob_handle, const ObjectRef &ob_ref);
  void sync_curves(Object *ob,
                   ObjectHandle &ob_handle,
                   const ObjectRef &ob_ref,
                   ResourceHandle res_handle = 0,
                   ModifierData *modifier_data = nullptr,
                   ParticleSystem *particle_sys = nullptr);
};

using HairHandleCallback = FunctionRef<void(ObjectHandle, ModifierData &, ParticleSystem &)>;
void foreach_hair_particle_handle(ObjectRef &ob_ref,
                                  ObjectHandle ob_handle,
                                  HairHandleCallback callback);

/** \} */

}  // namespace blender::eevee
