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

/** \file
 * \ingroup bke
 */

#pragma once

#include <algorithm>
#include <optional>
#include <string>

#include "BKE_cryptomatte.h"

#include "BLI_hash_mm3.hh"
#include "BLI_map.hh"
#include "BLI_string_ref.hh"

namespace blender {

struct ID;

namespace bke::cryptomatte {

/**
 * Format to a cryptomatte meta data key.
 *
 * Cryptomatte stores meta data. The keys are formatted containing a hash that
 * is generated from its layer name.
 *
 * The output of this function is:
 * 'cryptomatte/{hash of layer_name}/{key_name}'.
 */
std::string BKE_cryptomatte_meta_data_key(StringRef layer_name, StringRefNull key_name);

/**
 * Extract the cryptomatte layer name from the given `render_pass_name`.
 *
 * Cryptomatte passes are formatted with a trailing number for storing multiple samples that belong
 * to the same cryptomatte layer. This function would remove the trailing numbers to determine the
 * cryptomatte layer name.
 *
 * # Example
 *
 * A render_pass_name could be 'View Layer.CryptoMaterial02'. The cryptomatte layer would be 'View
 * Layer.CryptoMaterial'.
 *
 * \note The return type is a sub-string of `render_pass_name` and therefore cannot outlive the
 * `render_pass_name` internal data.
 */
StringRef BKE_cryptomatte_extract_layer_name(StringRef render_pass_name);

struct CryptomatteHash {
  uint32_t hash;

  CryptomatteHash(uint32_t hash);
  CryptomatteHash(const char *name, int name_len)
  {
    hash = BLI_hash_mm3(reinterpret_cast<const unsigned char *>(name), name_len, 0);
  }

  static CryptomatteHash from_hex_encoded(StringRef hex_encoded);
  std::string hex_encoded() const;

  /**
   * Convert a cryptomatte hash to a float.
   *
   * Cryptomatte hashes are stored in float textures and images. The conversion is taken from the
   * cryptomatte specification. See Floating point conversion section in
   * https://github.com/Psyop/Cryptomatte/blob/master/specification/cryptomatte_specification.pdf.
   *
   * The conversion uses as many 32 bit floating point values as possible to minimize hash
   * collisions. Unfortunately not all 32 bits can be used as NaN and Inf can be problematic.
   *
   * Note that this conversion assumes to be running on a L-endian system.
   */
  float float_encoded() const
  {
    uint32_t mantissa = hash & ((1 << 23) - 1);
    uint32_t exponent = (hash >> 23) & ((1 << 8) - 1);
    exponent = std::max(exponent, uint32_t(1));
    exponent = std::min(exponent, uint32_t(254));
    exponent = exponent << 23;
    uint32_t sign = (hash >> 31);
    sign = sign << 31;
    uint32_t float_bits = sign | exponent | mantissa;
    float f;
    memcpy(&f, &float_bits, sizeof(uint32_t));
    return f;
  }
};

struct CryptomatteLayer {
  Map<std::string, CryptomatteHash> hashes;

  MEM_CXX_CLASS_ALLOC_FUNCS("cryptomatte:CryptomatteLayer")

  static std::unique_ptr<CryptomatteLayer> read_from_manifest(StringRefNull manifest);
  uint32_t add_ID(const ID &id);
  void add_hash(StringRef name, CryptomatteHash cryptomatte_hash);
  std::string manifest() const;

  std::optional<std::string> operator[](float encoded_hash) const;
};

struct CryptomatteStampDataCallbackData {
  CryptomatteSession *session;
  Map<std::string, std::string> hash_to_layer_name;

  /**
   * Extract the hash from a stamp data key.
   *
   * Cryptomatte keys are formatted as "cryptomatte/{layer_hash}/{attribute}".
   */
  static StringRef extract_layer_hash(StringRefNull key);

  /* C type callback function (StampCallback). */
  static void extract_layer_names(void *_data,
                                  const char *propname,
                                  char *propvalue,
                                  int propvalue_maxncpy);
  /* C type callback function (StampCallback). */
  static void extract_layer_manifest(void *_data,
                                     const char *propname,
                                     char *propvalue,
                                     int propvalue_maxncpy);
};

const Vector<std::string> &BKE_cryptomatte_layer_names_get(const CryptomatteSession &session);
CryptomatteLayer *BKE_cryptomatte_layer_get(CryptomatteSession &session, StringRef layer_name);

struct CryptomatteSessionDeleter {
  void operator()(CryptomatteSession *session)
  {
    BKE_cryptomatte_free(session);
  }
};

using CryptomatteSessionPtr = std::unique_ptr<CryptomatteSession, CryptomatteSessionDeleter>;

}  // namespace bke::cryptomatte
}  // namespace blender
