/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 */

/** \file
 * \ingroup bke
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "BLI_endian_switch.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"

#include "BKE_global.h"
#include "BKE_idprop.h"
#include "BKE_lib_id.h"

#include "CLG_log.h"

#include "MEM_guardedalloc.h"

#include "BLO_read_write.h"

#include "BLI_strict_flags.h"

/* IDPropertyTemplate is a union in DNA_ID.h */

/**
 * if the new is 'IDP_ARRAY_REALLOC_LIMIT' items less,
 * than #IDProperty.totallen, reallocate anyway.
 */
#define IDP_ARRAY_REALLOC_LIMIT 200

static CLG_LogRef LOG = {"bke.idprop"};

/*local size table.*/
static size_t idp_size_table[] = {
    1, /*strings*/
    sizeof(int),
    sizeof(float),
    sizeof(float[3]),  /*Vector type, deprecated*/
    sizeof(float[16]), /*Matrix type, deprecated*/
    0,                 /*arrays don't have a fixed size*/
    sizeof(ListBase),  /*Group type*/
    sizeof(void *),
    sizeof(double),
};

/* -------------------------------------------------------------------- */
/** \name Array Functions (IDP Array API)
 * \{ */

#define GETPROP(prop, i) &(IDP_IDPArray(prop)[i])

/* --------- property array type -------------*/

/**
 * \note as a start to move away from the stupid IDP_New function, this type
 * has its own allocation function.
 */
IDProperty *IDP_NewIDPArray(const char *name)
{
  IDProperty *prop = MEM_callocN(sizeof(IDProperty), "IDProperty prop array");
  prop->type = IDP_IDPARRAY;
  prop->len = 0;
  BLI_strncpy(prop->name, name, MAX_IDPROP_NAME);

  return prop;
}

IDProperty *IDP_CopyIDPArray(const IDProperty *array, const int flag)
{
  /* don't use MEM_dupallocN because this may be part of an array */
  BLI_assert(array->type == IDP_IDPARRAY);

  IDProperty *narray = MEM_mallocN(sizeof(IDProperty), __func__);
  *narray = *array;

  narray->data.pointer = MEM_dupallocN(array->data.pointer);
  for (int i = 0; i < narray->len; i++) {
    /* ok, the copy functions always allocate a new structure,
     * which doesn't work here.  instead, simply copy the
     * contents of the new structure into the array cell,
     * then free it.  this makes for more maintainable
     * code than simply re-implementing the copy functions
     * in this loop.*/
    IDProperty *tmp = IDP_CopyProperty_ex(GETPROP(narray, i), flag);
    memcpy(GETPROP(narray, i), tmp, sizeof(IDProperty));
    MEM_freeN(tmp);
  }

  return narray;
}

static void IDP_FreeIDPArray(IDProperty *prop, const bool do_id_user)
{
  BLI_assert(prop->type == IDP_IDPARRAY);

  for (int i = 0; i < prop->len; i++) {
    IDP_FreePropertyContent_ex(GETPROP(prop, i), do_id_user);
  }

  if (prop->data.pointer) {
    MEM_freeN(prop->data.pointer);
  }
}

/* shallow copies item */
void IDP_SetIndexArray(IDProperty *prop, int index, IDProperty *item)
{
  BLI_assert(prop->type == IDP_IDPARRAY);

  if (index >= prop->len || index < 0) {
    return;
  }

  IDProperty *old = GETPROP(prop, index);
  if (item != old) {
    IDP_FreePropertyContent(old);

    memcpy(old, item, sizeof(IDProperty));
  }
}

IDProperty *IDP_GetIndexArray(IDProperty *prop, int index)
{
  BLI_assert(prop->type == IDP_IDPARRAY);

  return GETPROP(prop, index);
}

void IDP_AppendArray(IDProperty *prop, IDProperty *item)
{
  BLI_assert(prop->type == IDP_IDPARRAY);

  IDP_ResizeIDPArray(prop, prop->len + 1);
  IDP_SetIndexArray(prop, prop->len - 1, item);
}

void IDP_ResizeIDPArray(IDProperty *prop, int newlen)
{
  BLI_assert(prop->type == IDP_IDPARRAY);

  /* first check if the array buffer size has room */
  if (newlen <= prop->totallen) {
    if (newlen < prop->len && prop->totallen - newlen < IDP_ARRAY_REALLOC_LIMIT) {
      for (int i = newlen; i < prop->len; i++) {
        IDP_FreePropertyContent(GETPROP(prop, i));
      }

      prop->len = newlen;
      return;
    }
    if (newlen >= prop->len) {
      prop->len = newlen;
      return;
    }
  }

  /* free trailing items */
  if (newlen < prop->len) {
    /* newlen is smaller */
    for (int i = newlen; i < prop->len; i++) {
      IDP_FreePropertyContent(GETPROP(prop, i));
    }
  }

  /* - Note: This code comes from python, here's the corresponding comment. - */
  /* This over-allocates proportional to the list size, making room
   * for additional growth.  The over-allocation is mild, but is
   * enough to give linear-time amortized behavior over a long
   * sequence of appends() in the presence of a poorly-performing
   * system realloc().
   * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
   */
  int newsize = newlen;
  newsize = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize;
  prop->data.pointer = MEM_recallocN(prop->data.pointer, sizeof(IDProperty) * (size_t)newsize);
  prop->len = newlen;
  prop->totallen = newsize;
}

/* ----------- Numerical Array Type ----------- */
static void idp_resize_group_array(IDProperty *prop, int newlen, void *newarr)
{
  if (prop->subtype != IDP_GROUP) {
    return;
  }

  if (newlen >= prop->len) {
    /* bigger */
    IDProperty **array = newarr;
    IDPropertyTemplate val;

    for (int a = prop->len; a < newlen; a++) {
      val.i = 0; /* silence MSVC warning about uninitialized var when debugging */
      array[a] = IDP_New(IDP_GROUP, &val, "IDP_ResizeArray group");
    }
  }
  else {
    /* smaller */
    IDProperty **array = prop->data.pointer;

    for (int a = newlen; a < prop->len; a++) {
      IDP_FreeProperty(array[a]);
    }
  }
}

/*this function works for strings too!*/
void IDP_ResizeArray(IDProperty *prop, int newlen)
{
  const bool is_grow = newlen >= prop->len;

  /* first check if the array buffer size has room */
  if (newlen <= prop->totallen && prop->totallen - newlen < IDP_ARRAY_REALLOC_LIMIT) {
    idp_resize_group_array(prop, newlen, prop->data.pointer);
    prop->len = newlen;
    return;
  }

  /* - Note: This code comes from python, here's the corresponding comment. - */
  /* This over-allocates proportional to the list size, making room
   * for additional growth.  The over-allocation is mild, but is
   * enough to give linear-time amortized behavior over a long
   * sequence of appends() in the presence of a poorly-performing
   * system realloc().
   * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
   */
  int newsize = newlen;
  newsize = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize;

  if (is_grow == false) {
    idp_resize_group_array(prop, newlen, prop->data.pointer);
  }

  prop->data.pointer = MEM_recallocN(prop->data.pointer,
                                     idp_size_table[(int)prop->subtype] * (size_t)newsize);

  if (is_grow == true) {
    idp_resize_group_array(prop, newlen, prop->data.pointer);
  }

  prop->len = newlen;
  prop->totallen = newsize;
}

void IDP_FreeArray(IDProperty *prop)
{
  if (prop->data.pointer) {
    idp_resize_group_array(prop, 0, NULL);
    MEM_freeN(prop->data.pointer);
  }
}

static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(flag))
{
  IDProperty *newp = MEM_callocN(sizeof(IDProperty), __func__);

  BLI_strncpy(newp->name, prop->name, MAX_IDPROP_NAME);
  newp->type = prop->type;
  newp->flag = prop->flag;
  newp->data.val = prop->data.val;
  newp->data.val2 = prop->data.val2;

  return newp;
}

static IDProperty *IDP_CopyArray(const IDProperty *prop, const int flag)
{
  IDProperty *newp = idp_generic_copy(prop, flag);

  if (prop->data.pointer) {
    newp->data.pointer = MEM_dupallocN(prop->data.pointer);

    if (prop->type == IDP_GROUP) {
      IDProperty **array = newp->data.pointer;
      int a;

      for (a = 0; a < prop->len; a++) {
        array[a] = IDP_CopyProperty_ex(array[a], flag);
      }
    }
  }
  newp->len = prop->len;
  newp->subtype = prop->subtype;
  newp->totallen = prop->totallen;

  return newp;
}
/** \} */

/* -------------------------------------------------------------------- */
/** \name String Functions (IDProperty String API)
 * \{ */

/**
 *
 * \param st: The string to assign.
 * \param name: The property name.
 * \param maxlen: The size of the new string (including the \0 terminator).
 * \return The new string property.
 */
IDProperty *IDP_NewString(const char *st, const char *name, int maxlen)
{
  IDProperty *prop = MEM_callocN(sizeof(IDProperty), "IDProperty string");

  if (st == NULL) {
    prop->data.pointer = MEM_mallocN(DEFAULT_ALLOC_FOR_NULL_STRINGS, "id property string 1");
    *IDP_String(prop) = '\0';
    prop->totallen = DEFAULT_ALLOC_FOR_NULL_STRINGS;
    prop->len = 1; /* NULL string, has len of 1 to account for null byte. */
  }
  else {
    /* include null terminator '\0' */
    int stlen = (int)strlen(st) + 1;

    if (maxlen > 0 && maxlen < stlen) {
      stlen = maxlen;
    }

    prop->data.pointer = MEM_mallocN((size_t)stlen, "id property string 2");
    prop->len = prop->totallen = stlen;
    BLI_strncpy(prop->data.pointer, st, (size_t)stlen);
  }

  prop->type = IDP_STRING;
  BLI_strncpy(prop->name, name, MAX_IDPROP_NAME);

  return prop;
}

static IDProperty *IDP_CopyString(const IDProperty *prop, const int flag)
{
  BLI_assert(prop->type == IDP_STRING);
  IDProperty *newp = idp_generic_copy(prop, flag);

  if (prop->data.pointer) {
    newp->data.pointer = MEM_dupallocN(prop->data.pointer);
  }
  newp->len = prop->len;
  newp->subtype = prop->subtype;
  newp->totallen = prop->totallen;

  return newp;
}

void IDP_AssignString(IDProperty *prop, const char *st, int maxlen)
{
  BLI_assert(prop->type == IDP_STRING);
  int stlen = (int)strlen(st);
  if (maxlen > 0 && maxlen < stlen) {
    stlen = maxlen;
  }

  if (prop->subtype == IDP_STRING_SUB_BYTE) {
    IDP_ResizeArray(prop, stlen);
    memcpy(prop->data.pointer, st, (size_t)stlen);
  }
  else {
    stlen++;
    IDP_ResizeArray(prop, stlen);
    BLI_strncpy(prop->data.pointer, st, (size_t)stlen);
  }
}

void IDP_ConcatStringC(IDProperty *prop, const char *st)
{
  BLI_assert(prop->type == IDP_STRING);

  int newlen = prop->len + (int)strlen(st);
  /* we have to remember that prop->len includes the null byte for strings.
   * so there's no need to add +1 to the resize function.*/
  IDP_ResizeArray(prop, newlen);
  strcat(prop->data.pointer, st);
}

void IDP_ConcatString(IDProperty *str1, IDProperty *append)
{
  BLI_assert(append->type == IDP_STRING);

  /* since ->len for strings includes the NULL byte, we have to subtract one or
   * we'll get an extra null byte after each concatenation operation.*/
  int newlen = str1->len + append->len - 1;
  IDP_ResizeArray(str1, newlen);
  strcat(str1->data.pointer, append->data.pointer);
}

void IDP_FreeString(IDProperty *prop)
{
  BLI_assert(prop->type == IDP_STRING);

  if (prop->data.pointer) {
    MEM_freeN(prop->data.pointer);
  }
}
/** \} */

/* -------------------------------------------------------------------- */
/** \name ID Type (IDProperty ID API)
 * \{ */

static IDProperty *IDP_CopyID(const IDProperty *prop, const int flag)
{
  BLI_assert(prop->type == IDP_ID);
  IDProperty *newp = idp_generic_copy(prop, flag);

  newp->data.pointer = prop->data.pointer;
  if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
    id_us_plus(IDP_Id(newp));
  }

  return newp;
}

void IDP_AssignID(IDProperty *prop, ID *id, const int flag)
{
  BLI_assert(prop->type == IDP_ID);

  if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0 && IDP_Id(prop) != NULL) {
    id_us_min(IDP_Id(prop));
  }

  prop->data.pointer = id;

  if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
    id_us_plus(IDP_Id(prop));
  }
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Group Functions (IDProperty Group API)
 * \{ */

/**
 * Checks if a property with the same name as prop exists, and if so replaces it.
 */
static IDProperty *IDP_CopyGroup(const IDProperty *prop, const int flag)
{
  BLI_assert(prop->type == IDP_GROUP);
  IDProperty *newp = idp_generic_copy(prop, flag);
  newp->len = prop->len;
  newp->subtype = prop->subtype;

  LISTBASE_FOREACH (IDProperty *, link, &prop->data.group) {
    BLI_addtail(&newp->data.group, IDP_CopyProperty_ex(link, flag));
  }

  return newp;
}

/* use for syncing proxies.
 * When values name and types match, copy the values, else ignore */
void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src)
{
  BLI_assert(dest->type == IDP_GROUP);
  BLI_assert(src->type == IDP_GROUP);

  LISTBASE_FOREACH (IDProperty *, prop, &src->data.group) {
    IDProperty *other = BLI_findstring(&dest->data.group, prop->name, offsetof(IDProperty, name));
    if (other && prop->type == other->type) {
      switch (prop->type) {
        case IDP_INT:
        case IDP_FLOAT:
        case IDP_DOUBLE:
          other->data = prop->data;
          break;
        case IDP_GROUP:
          IDP_SyncGroupValues(other, prop);
          break;
        default: {
          BLI_insertlinkreplace(&dest->data.group, other, IDP_CopyProperty(prop));
          IDP_FreeProperty(other);
          break;
        }
      }
    }
  }
}

void IDP_SyncGroupTypes(IDProperty *dest, const IDProperty *src, const bool do_arraylen)
{
  LISTBASE_FOREACH_MUTABLE (IDProperty *, prop_dst, &dest->data.group) {
    const IDProperty *prop_src = IDP_GetPropertyFromGroup((IDProperty *)src, prop_dst->name);
    if (prop_src != NULL) {
      /* check of we should replace? */
      if ((prop_dst->type != prop_src->type || prop_dst->subtype != prop_src->subtype) ||
          (do_arraylen && ELEM(prop_dst->type, IDP_ARRAY, IDP_IDPARRAY) &&
           (prop_src->len != prop_dst->len))) {
        BLI_insertlinkreplace(&dest->data.group, prop_dst, IDP_CopyProperty(prop_src));
        IDP_FreeProperty(prop_dst);
      }
      else if (prop_dst->type == IDP_GROUP) {
        IDP_SyncGroupTypes(prop_dst, prop_src, do_arraylen);
      }
    }
    else {
      IDP_FreeFromGroup(dest, prop_dst);
    }
  }
}

/**
 * Replaces all properties with the same name in a destination group from a source group.
 */
void IDP_ReplaceGroupInGroup(IDProperty *dest, const IDProperty *src)
{
  BLI_assert(dest->type == IDP_GROUP);
  BLI_assert(src->type == IDP_GROUP);

  LISTBASE_FOREACH (IDProperty *, prop, &src->data.group) {
    IDProperty *loop;
    for (loop = dest->data.group.first; loop; loop = loop->next) {
      if (STREQ(loop->name, prop->name)) {
        BLI_insertlinkreplace(&dest->data.group, loop, IDP_CopyProperty(prop));
        IDP_FreeProperty(loop);
        break;
      }
    }

    /* only add at end if not added yet */
    if (loop == NULL) {
      IDProperty *copy = IDP_CopyProperty(prop);
      dest->len++;
      BLI_addtail(&dest->data.group, copy);
    }
  }
}

/**
 * Checks if a property with the same name as prop exists, and if so replaces it.
 * Use this to preserve order!
 */
void IDP_ReplaceInGroup_ex(IDProperty *group, IDProperty *prop, IDProperty *prop_exist)
{
  BLI_assert(group->type == IDP_GROUP);
  BLI_assert(prop_exist == IDP_GetPropertyFromGroup(group, prop->name));

  if (prop_exist != NULL) {
    BLI_insertlinkreplace(&group->data.group, prop_exist, prop);
    IDP_FreeProperty(prop_exist);
  }
  else {
    group->len++;
    BLI_addtail(&group->data.group, prop);
  }
}

void IDP_ReplaceInGroup(IDProperty *group, IDProperty *prop)
{
  IDProperty *prop_exist = IDP_GetPropertyFromGroup(group, prop->name);

  IDP_ReplaceInGroup_ex(group, prop, prop_exist);
}

/**
 * If a property is missing in \a dest, add it.
 * Do it recursively.
 */
void IDP_MergeGroup_ex(IDProperty *dest,
                       const IDProperty *src,
                       const bool do_overwrite,
                       const int flag)
{
  BLI_assert(dest->type == IDP_GROUP);
  BLI_assert(src->type == IDP_GROUP);

  if (do_overwrite) {
    LISTBASE_FOREACH (IDProperty *, prop, &src->data.group) {
      if (prop->type == IDP_GROUP) {
        IDProperty *prop_exist = IDP_GetPropertyFromGroup(dest, prop->name);

        if (prop_exist != NULL) {
          IDP_MergeGroup_ex(prop_exist, prop, do_overwrite, flag);
          continue;
        }
      }

      IDProperty *copy = IDP_CopyProperty_ex(prop, flag);
      IDP_ReplaceInGroup(dest, copy);
    }
  }
  else {
    LISTBASE_FOREACH (IDProperty *, prop, &src->data.group) {
      IDProperty *prop_exist = IDP_GetPropertyFromGroup(dest, prop->name);
      if (prop_exist != NULL) {
        if (prop->type == IDP_GROUP) {
          IDP_MergeGroup_ex(prop_exist, prop, do_overwrite, flag);
          continue;
        }
      }
      else {
        IDProperty *copy = IDP_CopyProperty_ex(prop, flag);
        dest->len++;
        BLI_addtail(&dest->data.group, copy);
      }
    }
  }
}

/**
 * If a property is missing in \a dest, add it.
 * Do it recursively.
 */
void IDP_MergeGroup(IDProperty *dest, const IDProperty *src, const bool do_overwrite)
{
  IDP_MergeGroup_ex(dest, src, do_overwrite, 0);
}

/**
 * This function has a sanity check to make sure ID properties with the same name don't
 * get added to the group.
 *
 * The sanity check just means the property is not added to the group if another property
 * exists with the same name; the client code using ID properties then needs to detect this
 * (the function that adds new properties to groups, #IDP_AddToGroup,
 * returns false if a property can't be added to the group, and true if it can)
 * and free the property.
 */
bool IDP_AddToGroup(IDProperty *group, IDProperty *prop)
{
  BLI_assert(group->type == IDP_GROUP);

  if (IDP_GetPropertyFromGroup(group, prop->name) == NULL) {
    group->len++;
    BLI_addtail(&group->data.group, prop);
    return true;
  }

  return false;
}

/**
 * This is the same as IDP_AddToGroup, only you pass an item
 * in the group list to be inserted after.
 */
bool IDP_InsertToGroup(IDProperty *group, IDProperty *previous, IDProperty *pnew)
{
  BLI_assert(group->type == IDP_GROUP);

  if (IDP_GetPropertyFromGroup(group, pnew->name) == NULL) {
    group->len++;
    BLI_insertlinkafter(&group->data.group, previous, pnew);
    return true;
  }

  return false;
}

/**
 * \note this does not free the property!!
 *
 * To free the property, you have to do:
 * IDP_FreeProperty(prop);
 */
void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop)
{
  BLI_assert(group->type == IDP_GROUP);

  group->len--;
  BLI_remlink(&group->data.group, prop);
}

/**
 * Removes the property from the group and frees it.
 */
void IDP_FreeFromGroup(IDProperty *group, IDProperty *prop)
{
  IDP_RemoveFromGroup(group, prop);
  IDP_FreeProperty(prop);
}

IDProperty *IDP_GetPropertyFromGroup(const IDProperty *prop, const char *name)
{
  BLI_assert(prop->type == IDP_GROUP);

  return (IDProperty *)BLI_findstring(&prop->data.group, name, offsetof(IDProperty, name));
}
/** same as above but ensure type match */
IDProperty *IDP_GetPropertyTypeFromGroup(const IDProperty *prop, const char *name, const char type)
{
  IDProperty *idprop = IDP_GetPropertyFromGroup(prop, name);
  return (idprop && idprop->type == type) ? idprop : NULL;
}

/* Ok, the way things work, Groups free the ID Property structs of their children.
 * This is because all ID Property freeing functions free only direct data (not the ID Property
 * struct itself), but for Groups the child properties *are* considered
 * direct data. */
static void IDP_FreeGroup(IDProperty *prop, const bool do_id_user)
{
  BLI_assert(prop->type == IDP_GROUP);

  LISTBASE_FOREACH (IDProperty *, loop, &prop->data.group) {
    IDP_FreePropertyContent_ex(loop, do_id_user);
  }
  BLI_freelistN(&prop->data.group);
}
/** \} */

/* -------------------------------------------------------------------- */
/** \name Main Functions  (IDProperty Main API)
 * \{ */

IDProperty *IDP_CopyProperty_ex(const IDProperty *prop, const int flag)
{
  switch (prop->type) {
    case IDP_GROUP:
      return IDP_CopyGroup(prop, flag);
    case IDP_STRING:
      return IDP_CopyString(prop, flag);
    case IDP_ID:
      return IDP_CopyID(prop, flag);
    case IDP_ARRAY:
      return IDP_CopyArray(prop, flag);
    case IDP_IDPARRAY:
      return IDP_CopyIDPArray(prop, flag);
    default:
      return idp_generic_copy(prop, flag);
  }
}

IDProperty *IDP_CopyProperty(const IDProperty *prop)
{
  return IDP_CopyProperty_ex(prop, 0);
}

/**
 * Copy content from source IDProperty into destination one, freeing destination property's content
 * first.
 */
void IDP_CopyPropertyContent(IDProperty *dst, IDProperty *src)
{
  IDProperty *idprop_tmp = IDP_CopyProperty(src);
  idprop_tmp->prev = dst->prev;
  idprop_tmp->next = dst->next;
  SWAP(IDProperty, *dst, *idprop_tmp);
  IDP_FreeProperty(idprop_tmp);
}

/**
 * Get the Group property that contains the id properties for ID id.  Set create_if_needed
 * to create the Group property and attach it to id if it doesn't exist; otherwise
 * the function will return NULL if there's no Group property attached to the ID.
 */
IDProperty *IDP_GetProperties(ID *id, const bool create_if_needed)
{
  if (id->properties) {
    return id->properties;
  }

  if (create_if_needed) {
    id->properties = MEM_callocN(sizeof(IDProperty), "IDProperty");
    id->properties->type = IDP_GROUP;
    /* don't overwrite the data's name and type
     * some functions might need this if they
     * don't have a real ID, should be named elsewhere - Campbell */
    /* strcpy(id->name, "top_level_group");*/
  }
  return id->properties;
}

/**
 * \param is_strict: When false treat missing items as a match */
bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is_strict)
{
  if (prop1 == NULL && prop2 == NULL) {
    return true;
  }
  if (prop1 == NULL || prop2 == NULL) {
    return is_strict ? false : true;
  }
  if (prop1->type != prop2->type) {
    return false;
  }

  switch (prop1->type) {
    case IDP_INT:
      return (IDP_Int(prop1) == IDP_Int(prop2));
    case IDP_FLOAT:
#if !defined(NDEBUG) && defined(WITH_PYTHON)
    {
      float p1 = IDP_Float(prop1);
      float p2 = IDP_Float(prop2);
      if ((p1 != p2) && ((fabsf(p1 - p2) / max_ff(p1, p2)) < 0.001f)) {
        printf(
            "WARNING: Comparing two float properties that have nearly the same value (%f vs. "
            "%f)\n",
            p1,
            p2);
        printf("    p1: ");
        IDP_print(prop1);
        printf("    p2: ");
        IDP_print(prop2);
      }
    }
#endif
      return (IDP_Float(prop1) == IDP_Float(prop2));
    case IDP_DOUBLE:
      return (IDP_Double(prop1) == IDP_Double(prop2));
    case IDP_STRING: {
      return (((prop1->len == prop2->len) &&
               STREQLEN(IDP_String(prop1), IDP_String(prop2), (size_t)prop1->len)));
    }
    case IDP_ARRAY:
      if (prop1->len == prop2->len && prop1->subtype == prop2->subtype) {
        return (memcmp(IDP_Array(prop1),
                       IDP_Array(prop2),
                       idp_size_table[(int)prop1->subtype] * (size_t)prop1->len) == 0);
      }
      return false;
    case IDP_GROUP: {
      if (is_strict && prop1->len != prop2->len) {
        return false;
      }

      LISTBASE_FOREACH (IDProperty *, link1, &prop1->data.group) {
        IDProperty *link2 = IDP_GetPropertyFromGroup(prop2, link1->name);

        if (!IDP_EqualsProperties_ex(link1, link2, is_strict)) {
          return false;
        }
      }

      return true;
    }
    case IDP_IDPARRAY: {
      IDProperty *array1 = IDP_IDPArray(prop1);
      IDProperty *array2 = IDP_IDPArray(prop2);

      if (prop1->len != prop2->len) {
        return false;
      }

      for (int i = 0; i < prop1->len; i++) {
        if (!IDP_EqualsProperties_ex(&array1[i], &array2[i], is_strict)) {
          return false;
        }
      }
      return true;
    }
    case IDP_ID:
      return (IDP_Id(prop1) == IDP_Id(prop2));
    default:
      BLI_assert(0);
      break;
  }

  return true;
}

bool IDP_EqualsProperties(IDProperty *prop1, IDProperty *prop2)
{
  return IDP_EqualsProperties_ex(prop1, prop2, true);
}

/**
 * Allocate a new ID.
 *
 * This function takes three arguments: the ID property type, a union which defines
 * its initial value, and a name.
 *
 * The union is simple to use; see the top of BKE_idprop.h for its definition.
 * An example of using this function:
 *
 * \code{.c}
 * IDPropertyTemplate val;
 * IDProperty *group, *idgroup, *color;
 * group = IDP_New(IDP_GROUP, val, "group1"); // groups don't need a template.
 *
 * val.array.len = 4
 * val.array.type = IDP_FLOAT;
 * color = IDP_New(IDP_ARRAY, val, "color1");
 *
 * idgroup = IDP_GetProperties(some_id, 1);
 * IDP_AddToGroup(idgroup, color);
 * IDP_AddToGroup(idgroup, group);
 * \endcode
 *
 * Note that you MUST either attach the id property to an id property group with
 * IDP_AddToGroup or MEM_freeN the property, doing anything else might result in
 * a memory leak.
 */
IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *name)
{
  IDProperty *prop = NULL;

  switch (type) {
    case IDP_INT:
      prop = MEM_callocN(sizeof(IDProperty), "IDProperty int");
      prop->data.val = val->i;
      break;
    case IDP_FLOAT:
      prop = MEM_callocN(sizeof(IDProperty), "IDProperty float");
      *(float *)&prop->data.val = val->f;
      break;
    case IDP_DOUBLE:
      prop = MEM_callocN(sizeof(IDProperty), "IDProperty double");
      *(double *)&prop->data.val = val->d;
      break;
    case IDP_ARRAY: {
      /* for now, we only support float and int and double arrays */
      if ((val->array.type == IDP_FLOAT) || (val->array.type == IDP_INT) ||
          (val->array.type == IDP_DOUBLE) || (val->array.type == IDP_GROUP)) {
        prop = MEM_callocN(sizeof(IDProperty), "IDProperty array");
        prop->subtype = val->array.type;
        if (val->array.len) {
          prop->data.pointer = MEM_callocN(
              idp_size_table[val->array.type] * (size_t)val->array.len, "id property array");
        }
        prop->len = prop->totallen = val->array.len;
        break;
      }
      CLOG_ERROR(&LOG, "bad array type.");
      return NULL;
    }
    case IDP_STRING: {
      const char *st = val->string.str;

      prop = MEM_callocN(sizeof(IDProperty), "IDProperty string");
      if (val->string.subtype == IDP_STRING_SUB_BYTE) {
        /* note, intentionally not null terminated */
        if (st == NULL) {
          prop->data.pointer = MEM_mallocN(DEFAULT_ALLOC_FOR_NULL_STRINGS, "id property string 1");
          *IDP_String(prop) = '\0';
          prop->totallen = DEFAULT_ALLOC_FOR_NULL_STRINGS;
          prop->len = 0;
        }
        else {
          prop->data.pointer = MEM_mallocN((size_t)val->string.len, "id property string 2");
          prop->len = prop->totallen = val->string.len;
          memcpy(prop->data.pointer, st, (size_t)val->string.len);
        }
        prop->subtype = IDP_STRING_SUB_BYTE;
      }
      else {
        if (st == NULL || val->string.len <= 1) {
          prop->data.pointer = MEM_mallocN(DEFAULT_ALLOC_FOR_NULL_STRINGS, "id property string 1");
          *IDP_String(prop) = '\0';
          prop->totallen = DEFAULT_ALLOC_FOR_NULL_STRINGS;
          /* NULL string, has len of 1 to account for null byte. */
          prop->len = 1;
        }
        else {
          BLI_assert((int)val->string.len <= (int)strlen(st) + 1);
          prop->data.pointer = MEM_mallocN((size_t)val->string.len, "id property string 3");
          memcpy(prop->data.pointer, st, (size_t)val->string.len - 1);
          IDP_String(prop)[val->string.len - 1] = '\0';
          prop->len = prop->totallen = val->string.len;
        }
        prop->subtype = IDP_STRING_SUB_UTF8;
      }
      break;
    }
    case IDP_GROUP: {
      /* Values are set properly by calloc. */
      prop = MEM_callocN(sizeof(IDProperty), "IDProperty group");
      break;
    }
    case IDP_ID: {
      prop = MEM_callocN(sizeof(IDProperty), "IDProperty datablock");
      prop->data.pointer = (void *)val->id;
      prop->type = IDP_ID;
      id_us_plus(IDP_Id(prop));
      break;
    }
    default: {
      prop = MEM_callocN(sizeof(IDProperty), "IDProperty array");
      break;
    }
  }

  prop->type = type;
  BLI_strncpy(prop->name, name, MAX_IDPROP_NAME);

  return prop;
}

/**
 * \note This will free allocated data, all child properties of arrays and groups, and unlink IDs!
 * But it does not free the actual IDProperty struct itself.
 */
void IDP_FreePropertyContent_ex(IDProperty *prop, const bool do_id_user)
{
  switch (prop->type) {
    case IDP_ARRAY:
      IDP_FreeArray(prop);
      break;
    case IDP_STRING:
      IDP_FreeString(prop);
      break;
    case IDP_GROUP:
      IDP_FreeGroup(prop, do_id_user);
      break;
    case IDP_IDPARRAY:
      IDP_FreeIDPArray(prop, do_id_user);
      break;
    case IDP_ID:
      if (do_id_user) {
        id_us_min(IDP_Id(prop));
      }
      break;
  }
}

void IDP_FreePropertyContent(IDProperty *prop)
{
  IDP_FreePropertyContent_ex(prop, true);
}

void IDP_FreeProperty_ex(IDProperty *prop, const bool do_id_user)
{
  IDP_FreePropertyContent_ex(prop, do_id_user);
  MEM_freeN(prop);
}

void IDP_FreeProperty(IDProperty *prop)
{
  IDP_FreePropertyContent(prop);
  MEM_freeN(prop);
}

void IDP_ClearProperty(IDProperty *prop)
{
  IDP_FreePropertyContent(prop);
  prop->data.pointer = NULL;
  prop->len = prop->totallen = 0;
}

void IDP_Reset(IDProperty *prop, const IDProperty *reference)
{
  if (prop == NULL) {
    return;
  }
  IDP_ClearProperty(prop);
  if (reference != NULL) {
    IDP_MergeGroup(prop, reference, true);
  }
}

/**
 * Loop through all ID properties in hierarchy of given \a id_property_root included.
 *
 * \note Container types (groups and arrays) are processed after applying the callback on them.
 *
 * \param type_filter: If not 0, only apply callback on properties of matching types, see
 * IDP_TYPE_FILTER_ enum in DNA_ID.h.
 */
void IDP_foreach_property(IDProperty *id_property_root,
                          const int type_filter,
                          IDPForeachPropertyCallback callback,
                          void *user_data)
{
  if (!id_property_root) {
    return;
  }

  if (type_filter == 0 || (1 << id_property_root->type) & type_filter) {
    callback(id_property_root, user_data);
  }

  /* Recursive call into container types of ID properties. */
  switch (id_property_root->type) {
    case IDP_GROUP: {
      LISTBASE_FOREACH (IDProperty *, loop, &id_property_root->data.group) {
        IDP_foreach_property(loop, type_filter, callback, user_data);
      }
      break;
    }
    case IDP_IDPARRAY: {
      IDProperty *loop = IDP_Array(id_property_root);
      for (int i = 0; i < id_property_root->len; i++) {
        IDP_foreach_property(&loop[i], type_filter, callback, user_data);
      }
      break;
    }
    default:
      break; /* Nothing to do here with other types of IDProperties... */
  }
}

void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer);

static void IDP_WriteArray(const IDProperty *prop, BlendWriter *writer)
{
  /*REMEMBER to set totalen to len in the linking code!!*/
  if (prop->data.pointer) {
    BLO_write_raw(writer, MEM_allocN_len(prop->data.pointer), prop->data.pointer);

    if (prop->subtype == IDP_GROUP) {
      IDProperty **array = prop->data.pointer;
      int a;

      for (a = 0; a < prop->len; a++) {
        IDP_BlendWrite(writer, array[a]);
      }
    }
  }
}

static void IDP_WriteIDPArray(const IDProperty *prop, BlendWriter *writer)
{
  /*REMEMBER to set totalen to len in the linking code!!*/
  if (prop->data.pointer) {
    const IDProperty *array = prop->data.pointer;

    BLO_write_struct_array(writer, IDProperty, prop->len, array);

    for (int a = 0; a < prop->len; a++) {
      IDP_WriteProperty_OnlyData(&array[a], writer);
    }
  }
}

static void IDP_WriteString(const IDProperty *prop, BlendWriter *writer)
{
  /*REMEMBER to set totalen to len in the linking code!!*/
  BLO_write_raw(writer, (size_t)prop->len, prop->data.pointer);
}

static void IDP_WriteGroup(const IDProperty *prop, BlendWriter *writer)
{
  LISTBASE_FOREACH (IDProperty *, loop, &prop->data.group) {
    IDP_BlendWrite(writer, loop);
  }
}

/* Functions to read/write ID Properties */
void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer)
{
  switch (prop->type) {
    case IDP_GROUP:
      IDP_WriteGroup(prop, writer);
      break;
    case IDP_STRING:
      IDP_WriteString(prop, writer);
      break;
    case IDP_ARRAY:
      IDP_WriteArray(prop, writer);
      break;
    case IDP_IDPARRAY:
      IDP_WriteIDPArray(prop, writer);
      break;
  }
}

void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop)
{
  BLO_write_struct(writer, IDProperty, prop);
  IDP_WriteProperty_OnlyData(prop, writer);
}

static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader);

static void IDP_DirectLinkIDPArray(IDProperty *prop, BlendDataReader *reader)
{
  /* since we didn't save the extra buffer, set totallen to len */
  prop->totallen = prop->len;
  BLO_read_data_address(reader, &prop->data.pointer);

  IDProperty *array = (IDProperty *)prop->data.pointer;

  /* note!, idp-arrays didn't exist in 2.4x, so the pointer will be cleared
   * there's not really anything we can do to correct this, at least don't crash */
  if (array == NULL) {
    prop->len = 0;
    prop->totallen = 0;
  }

  for (int i = 0; i < prop->len; i++) {
    IDP_DirectLinkProperty(&array[i], reader);
  }
}

static void IDP_DirectLinkArray(IDProperty *prop, BlendDataReader *reader)
{
  /* since we didn't save the extra buffer, set totallen to len */
  prop->totallen = prop->len;

  if (prop->subtype == IDP_GROUP) {
    BLO_read_pointer_array(reader, &prop->data.pointer);
    IDProperty **array = prop->data.pointer;

    for (int i = 0; i < prop->len; i++) {
      IDP_DirectLinkProperty(array[i], reader);
    }
  }
  else if (prop->subtype == IDP_DOUBLE) {
    BLO_read_double_array(reader, prop->len, (double **)&prop->data.pointer);
  }
  else {
    /* also used for floats */
    BLO_read_int32_array(reader, prop->len, (int **)&prop->data.pointer);
  }
}

static void IDP_DirectLinkString(IDProperty *prop, BlendDataReader *reader)
{
  /*since we didn't save the extra string buffer, set totallen to len.*/
  prop->totallen = prop->len;
  BLO_read_data_address(reader, &prop->data.pointer);
}

static void IDP_DirectLinkGroup(IDProperty *prop, BlendDataReader *reader)
{
  ListBase *lb = &prop->data.group;

  BLO_read_list(reader, lb);

  /*Link child id properties now*/
  LISTBASE_FOREACH (IDProperty *, loop, &prop->data.group) {
    IDP_DirectLinkProperty(loop, reader);
  }
}

static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader)
{
  switch (prop->type) {
    case IDP_GROUP:
      IDP_DirectLinkGroup(prop, reader);
      break;
    case IDP_STRING:
      IDP_DirectLinkString(prop, reader);
      break;
    case IDP_ARRAY:
      IDP_DirectLinkArray(prop, reader);
      break;
    case IDP_IDPARRAY:
      IDP_DirectLinkIDPArray(prop, reader);
      break;
    case IDP_DOUBLE:
      /* Workaround for doubles.
       * They are stored in the same field as `int val, val2` in the #IDPropertyData struct,
       * they have to deal with endianness specifically.
       *
       * In theory, val and val2 would've already been swapped
       * if switch_endian is true, so we have to first un-swap
       * them then re-swap them as a single 64-bit entity. */
      if (BLO_read_requires_endian_switch(reader)) {
        BLI_endian_switch_int32(&prop->data.val);
        BLI_endian_switch_int32(&prop->data.val2);
        BLI_endian_switch_int64((int64_t *)&prop->data.val);
      }
      break;
    case IDP_INT:
    case IDP_FLOAT:
    case IDP_ID:
      break; /* Nothing special to do here. */
    default:
      /* Unknown IDP type, nuke it (we cannot handle unknown types everywhere in code,
       * IDP are way too polymorphic to do it safely. */
      printf(
          "%s: found unknown IDProperty type %d, reset to Integer one !\n", __func__, prop->type);
      /* Note: we do not attempt to free unknown prop, we have no way to know how to do that! */
      prop->type = IDP_INT;
      prop->subtype = 0;
      IDP_Int(prop) = 0;
  }
}

void IDP_BlendReadData_impl(BlendDataReader *reader, IDProperty **prop, const char *caller_func_id)
{
  if (*prop) {
    if ((*prop)->type == IDP_GROUP) {
      IDP_DirectLinkGroup(*prop, reader);
    }
    else {
      /* corrupt file! */
      printf("%s: found non group data, freeing type %d!\n", caller_func_id, (*prop)->type);
      /* don't risk id, data's likely corrupt. */
      // IDP_FreePropertyContent(*prop);
      *prop = NULL;
    }
  }
}

void IDP_BlendReadLib(BlendLibReader *reader, IDProperty *prop)
{
  if (!prop) {
    return;
  }

  switch (prop->type) {
    case IDP_ID: /* PointerProperty */
    {
      void *newaddr = BLO_read_get_new_id_address(reader, NULL, IDP_Id(prop));
      if (IDP_Id(prop) && !newaddr && G.debug) {
        printf("Error while loading \"%s\". Data not found in file!\n", prop->name);
      }
      prop->data.pointer = newaddr;
      break;
    }
    case IDP_IDPARRAY: /* CollectionProperty */
    {
      IDProperty *idp_array = IDP_IDPArray(prop);
      for (int i = 0; i < prop->len; i++) {
        IDP_BlendReadLib(reader, &(idp_array[i]));
      }
      break;
    }
    case IDP_GROUP: /* PointerProperty */
    {
      LISTBASE_FOREACH (IDProperty *, loop, &prop->data.group) {
        IDP_BlendReadLib(reader, loop);
      }
      break;
    }
    default:
      break; /* Nothing to do for other IDProps. */
  }
}

void IDP_BlendReadExpand(struct BlendExpander *expander, IDProperty *prop)
{
  if (!prop) {
    return;
  }

  switch (prop->type) {
    case IDP_ID:
      BLO_expand(expander, IDP_Id(prop));
      break;
    case IDP_IDPARRAY: {
      IDProperty *idp_array = IDP_IDPArray(prop);
      for (int i = 0; i < prop->len; i++) {
        IDP_BlendReadExpand(expander, &idp_array[i]);
      }
      break;
    }
    case IDP_GROUP:
      LISTBASE_FOREACH (IDProperty *, loop, &prop->data.group) {
        IDP_BlendReadExpand(expander, loop);
      }
      break;
  }
}

/** \} */
