/* SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup bmesh
 *
 * BMesh operator access.
 */

#include "MEM_guardedalloc.h"

#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_mempool.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"

#include "BLT_translation.h"

#include "bmesh.h"
#include "intern/bmesh_private.h"

/* forward declarations */
static void bmo_flag_layer_alloc(BMesh *bm);
static void bmo_flag_layer_free(BMesh *bm);
static void bmo_flag_layer_clear(BMesh *bm);
static int bmo_name_to_slotcode(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier);
static int bmo_name_to_slotcode_check(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                      const char *identifier);

const int BMO_OPSLOT_TYPEINFO[BMO_OP_SLOT_TOTAL_TYPES] = {
    0,                /*  0: BMO_OP_SLOT_SENTINEL */
    sizeof(int),      /*  1: BMO_OP_SLOT_BOOL */
    sizeof(int),      /*  2: BMO_OP_SLOT_INT */
    sizeof(float),    /*  3: BMO_OP_SLOT_FLT */
    sizeof(void *),   /*  4: BMO_OP_SLOT_PNT */
    sizeof(void *),   /*  5: BMO_OP_SLOT_PNT */
    0,                /*  6: unused */
    0,                /*  7: unused */
    sizeof(float[3]), /*  8: BMO_OP_SLOT_VEC */
    sizeof(void *),   /*  9: BMO_OP_SLOT_ELEMENT_BUF */
    sizeof(void *),   /* 10: BMO_OP_SLOT_MAPPING */
};

/* Dummy slot so there is something to return when slot name lookup fails */
// static BMOpSlot BMOpEmptySlot = {0};

void BMO_op_flag_enable(BMesh *UNUSED(bm), BMOperator *op, const int op_flag)
{
  op->flag |= op_flag;
}

void BMO_op_flag_disable(BMesh *UNUSED(bm), BMOperator *op, const int op_flag)
{
  op->flag &= ~op_flag;
}

void BMO_push(BMesh *bm, BMOperator *UNUSED(op))
{
  bm->toolflag_index++;

  BLI_assert(bm->totflags > 0);

  /* add flag layer, if appropriate */
  if (bm->toolflag_index > 0) {
    bmo_flag_layer_alloc(bm);
  }
  else {
    bmo_flag_layer_clear(bm);
  }
}

void BMO_pop(BMesh *bm)
{
  if (bm->toolflag_index > 0) {
    bmo_flag_layer_free(bm);
  }

  bm->toolflag_index--;
}

/* use for both slot_types_in and slot_types_out */
static void bmo_op_slots_init(const BMOSlotType *slot_types, BMOpSlot *slot_args)
{
  BMOpSlot *slot;
  uint i;
  for (i = 0; slot_types[i].type; i++) {
    slot = &slot_args[i];
    slot->slot_name = slot_types[i].name;
    slot->slot_type = slot_types[i].type;
    slot->slot_subtype = slot_types[i].subtype;
    // slot->index = i;  // UNUSED

    switch (slot->slot_type) {
      case BMO_OP_SLOT_MAPPING:
        slot->data.ghash = BLI_ghash_ptr_new("bmesh slot map hash");
        break;
      case BMO_OP_SLOT_INT:
        if (ELEM(slot->slot_subtype.intg,
                 BMO_OP_SLOT_SUBTYPE_INT_ENUM,
                 BMO_OP_SLOT_SUBTYPE_INT_FLAG)) {
          slot->data.enum_data.flags = slot_types[i].enum_flags;
          /* Set the first value of the enum as the default value. */
          slot->data.i = slot->data.enum_data.flags[0].value;
        }
      default:
        break;
    }
  }
}

static void bmo_op_slots_free(const BMOSlotType *slot_types, BMOpSlot *slot_args)
{
  BMOpSlot *slot;
  uint i;
  for (i = 0; slot_types[i].type; i++) {
    slot = &slot_args[i];
    switch (slot->slot_type) {
      case BMO_OP_SLOT_MAPPING:
        BLI_ghash_free(slot->data.ghash, NULL, NULL);
        break;
      default:
        break;
    }
  }
}

void BMO_op_init(BMesh *bm, BMOperator *op, const int flag, const char *opname)
{
  int opcode = BMO_opcode_from_opname(opname);

#ifdef DEBUG
  BM_ELEM_INDEX_VALIDATE(bm, "pre bmo", opname);
#else
  (void)bm;
#endif

  if (opcode == -1) {
    opcode = 0; /* error!, already printed, have a better way to handle this? */
  }

  memset(op, 0, sizeof(BMOperator));
  op->type = opcode;
  op->type_flag = bmo_opdefines[opcode]->type_flag;
  op->flag = flag;

  /* initialize the operator slot types */
  bmo_op_slots_init(bmo_opdefines[opcode]->slot_types_in, op->slots_in);
  bmo_op_slots_init(bmo_opdefines[opcode]->slot_types_out, op->slots_out);

  /* callback */
  op->exec = bmo_opdefines[opcode]->exec;

  /* memarena, used for operator's slot buffers */
  op->arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
  BLI_memarena_use_calloc(op->arena);
}

void BMO_op_exec(BMesh *bm, BMOperator *op)
{
  /* allocate tool flags on demand */
  BM_mesh_elem_toolflags_ensure(bm);

  BMO_push(bm, op);

  if (bm->toolflag_index == 1) {
    bmesh_edit_begin(bm, op->type_flag);
  }
  op->exec(bm, op);

  if (bm->toolflag_index == 1) {
    bmesh_edit_end(bm, op->type_flag);
  }

  BMO_pop(bm);
}

void BMO_op_finish(BMesh *bm, BMOperator *op)
{
  bmo_op_slots_free(bmo_opdefines[op->type]->slot_types_in, op->slots_in);
  bmo_op_slots_free(bmo_opdefines[op->type]->slot_types_out, op->slots_out);

  BLI_memarena_free(op->arena);

#ifdef DEBUG
  BM_ELEM_INDEX_VALIDATE(bm, "post bmo", bmo_opdefines[op->type]->opname);

  /* avoid accidental re-use */
  memset(op, 0xff, sizeof(*op));
#else
  (void)bm;
#endif
}

bool BMO_slot_exists(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier)
{
  int slot_code = bmo_name_to_slotcode(slot_args, identifier);
  return (slot_code >= 0);
}

BMOpSlot *BMO_slot_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier)
{
  int slot_code = bmo_name_to_slotcode_check(slot_args, identifier);

  if (UNLIKELY(slot_code < 0)) {
    // return &BMOpEmptySlot;
    BLI_assert(0);
    return NULL; /* better crash */
  }

  return &slot_args[slot_code];
}

void _bmo_slot_copy(BMOpSlot slot_args_src[BMO_OP_MAX_SLOTS],
                    const char *slot_name_src,
                    BMOpSlot slot_args_dst[BMO_OP_MAX_SLOTS],
                    const char *slot_name_dst,
                    struct MemArena *arena_dst)
{
  BMOpSlot *slot_src = BMO_slot_get(slot_args_src, slot_name_src);
  BMOpSlot *slot_dst = BMO_slot_get(slot_args_dst, slot_name_dst);

  if (slot_src == slot_dst) {
    return;
  }

  BLI_assert(slot_src->slot_type == slot_dst->slot_type);
  if (slot_src->slot_type != slot_dst->slot_type) {
    return;
  }

  if (slot_dst->slot_type == BMO_OP_SLOT_ELEMENT_BUF) {
    /* do buffer copy */
    slot_dst->data.buf = NULL;
    slot_dst->len = slot_src->len;
    if (slot_dst->len) {
      /* check dest has all flags enabled that the source has */
      const eBMOpSlotSubType_Elem src_elem_flag = (slot_src->slot_subtype.elem & BM_ALL_NOLOOP);
      const eBMOpSlotSubType_Elem dst_elem_flag = (slot_dst->slot_subtype.elem & BM_ALL_NOLOOP);

      if ((src_elem_flag | dst_elem_flag) == dst_elem_flag) {
        /* pass */
      }
      else {
        /* check types */
        const uint tot = slot_src->len;
        uint i;
        uint out = 0;
        BMElem **ele_src = (BMElem **)slot_src->data.buf;
        for (i = 0; i < tot; i++, ele_src++) {
          if ((*ele_src)->head.htype & dst_elem_flag) {
            out++;
          }
        }
        if (out != tot) {
          slot_dst->len = out;
        }
      }

      if (slot_dst->len) {
        const int slot_alloc_size = BMO_OPSLOT_TYPEINFO[slot_dst->slot_type] * slot_dst->len;
        slot_dst->data.buf = BLI_memarena_alloc(arena_dst, slot_alloc_size);
        if (slot_src->len == slot_dst->len) {
          memcpy(slot_dst->data.buf, slot_src->data.buf, slot_alloc_size);
        }
        else {
          /* only copy compatible elements */
          const uint tot = slot_src->len;
          uint i;
          BMElem **ele_src = (BMElem **)slot_src->data.buf;
          BMElem **ele_dst = (BMElem **)slot_dst->data.buf;
          for (i = 0; i < tot; i++, ele_src++) {
            if ((*ele_src)->head.htype & dst_elem_flag) {
              *ele_dst = *ele_src;
              ele_dst++;
            }
          }
        }
      }
    }
  }
  else if (slot_dst->slot_type == BMO_OP_SLOT_MAPPING) {
    GHashIterator gh_iter;
    GHASH_ITER (gh_iter, slot_src->data.ghash) {
      void *key = BLI_ghashIterator_getKey(&gh_iter);
      void *val = BLI_ghashIterator_getValue(&gh_iter);
      BLI_ghash_insert(slot_dst->data.ghash, key, val);
    }
  }
  else {
    slot_dst->data = slot_src->data;
  }
}

/*
 * BMESH OPSTACK SET XXX
 *
 * Sets the value of a slot depending on its type
 */

void BMO_slot_float_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, const float f)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_FLT);
  if (!(slot->slot_type == BMO_OP_SLOT_FLT)) {
    return;
  }

  slot->data.f = f;
}

void BMO_slot_int_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, const int i)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_INT);
  if (!(slot->slot_type == BMO_OP_SLOT_INT)) {
    return;
  }

  slot->data.i = i;
}

void BMO_slot_bool_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, const bool i)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_BOOL);
  if (!(slot->slot_type == BMO_OP_SLOT_BOOL)) {
    return;
  }

  slot->data.i = i;
}

void BMO_slot_mat_set(BMOperator *op,
                      BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                      const char *slot_name,
                      const float *mat,
                      int size)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_MAT);
  if (!(slot->slot_type == BMO_OP_SLOT_MAT)) {
    return;
  }

  slot->len = 4;
  slot->data.p = BLI_memarena_alloc(op->arena, sizeof(float[4][4]));

  if (size == 4) {
    copy_m4_m4(slot->data.p, (const float(*)[4])mat);
  }
  else if (size == 3) {
    copy_m4_m3(slot->data.p, (const float(*)[3])mat);
  }
  else {
    fprintf(stderr, "%s: invalid size argument %d (bmesh internal error)\n", __func__, size);

    zero_m4(slot->data.p);
  }
}

void BMO_slot_mat4_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                       const char *slot_name,
                       float r_mat[4][4])
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_MAT);
  if (!(slot->slot_type == BMO_OP_SLOT_MAT)) {
    return;
  }

  if (slot->data.p) {
    copy_m4_m4(r_mat, BMO_SLOT_AS_MATRIX(slot));
  }
  else {
    unit_m4(r_mat);
  }
}

void BMO_slot_mat3_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                       const char *slot_name,
                       float r_mat[3][3])
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_MAT);
  if (!(slot->slot_type == BMO_OP_SLOT_MAT)) {
    return;
  }

  if (slot->data.p) {
    copy_m3_m4(r_mat, BMO_SLOT_AS_MATRIX(slot));
  }
  else {
    unit_m3(r_mat);
  }
}

void BMO_slot_ptr_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, void *p)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_PTR);
  if (!(slot->slot_type == BMO_OP_SLOT_PTR)) {
    return;
  }

  slot->data.p = p;
}

void BMO_slot_vec_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                      const char *slot_name,
                      const float vec[3])
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_VEC);
  if (!(slot->slot_type == BMO_OP_SLOT_VEC)) {
    return;
  }

  copy_v3_v3(slot->data.vec, vec);
}

float BMO_slot_float_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_FLT);
  if (!(slot->slot_type == BMO_OP_SLOT_FLT)) {
    return 0.0f;
  }

  return slot->data.f;
}

int BMO_slot_int_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_INT);
  if (!(slot->slot_type == BMO_OP_SLOT_INT)) {
    return 0;
  }

  return slot->data.i;
}

bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_BOOL);
  if (!(slot->slot_type == BMO_OP_SLOT_BOOL)) {
    return 0;
  }

  return slot->data.i;
}

void *BMO_slot_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, int *len)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  void **ret;

  /* could add support for mapping type */
  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);

  ret = MEM_mallocN(sizeof(void *) * slot->len, __func__);
  memcpy(ret, slot->data.buf, sizeof(void *) * slot->len);
  *len = slot->len;
  return ret;
}

void *BMO_slot_ptr_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_PTR);
  if (!(slot->slot_type == BMO_OP_SLOT_PTR)) {
    return NULL;
  }

  return slot->data.p;
}

void BMO_slot_vec_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, float r_vec[3])
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_VEC);
  if (!(slot->slot_type == BMO_OP_SLOT_VEC)) {
    return;
  }

  copy_v3_v3(r_vec, slot->data.vec);
}

/*
 * BMO_COUNTFLAG
 *
 * Counts the number of elements of a certain type that have a
 * specific flag enabled (or disabled if test_for_enabled is false).
 */

static int bmo_mesh_flag_count(BMesh *bm,
                               const char htype,
                               const short oflag,
                               const bool test_for_enabled)
{
  int count_vert = 0, count_edge = 0, count_face = 0;

  if (htype & BM_VERT) {
    BMIter iter;
    BMVert *ele;
    BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
      if (BMO_vert_flag_test_bool(bm, ele, oflag) == test_for_enabled) {
        count_vert++;
      }
    }
  }
  if (htype & BM_EDGE) {
    BMIter iter;
    BMEdge *ele;
    BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
      if (BMO_edge_flag_test_bool(bm, ele, oflag) == test_for_enabled) {
        count_edge++;
      }
    }
  }
  if (htype & BM_FACE) {
    BMIter iter;
    BMFace *ele;
    BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) {
      if (BMO_face_flag_test_bool(bm, ele, oflag) == test_for_enabled) {
        count_face++;
      }
    }
  }

  return (count_vert + count_edge + count_face);
}

int BMO_mesh_enabled_flag_count(BMesh *bm, const char htype, const short oflag)
{
  return bmo_mesh_flag_count(bm, htype, oflag, true);
}

int BMO_mesh_disabled_flag_count(BMesh *bm, const char htype, const short oflag)
{
  return bmo_mesh_flag_count(bm, htype, oflag, false);
}

void BMO_mesh_flag_disable_all(BMesh *bm,
                               BMOperator *UNUSED(op),
                               const char htype,
                               const short oflag)
{
  if (htype & BM_VERT) {
    BMIter iter;
    BMVert *ele;
    BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
      BMO_vert_flag_disable(bm, ele, oflag);
    }
  }
  if (htype & BM_EDGE) {
    BMIter iter;
    BMEdge *ele;
    BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
      BMO_edge_flag_disable(bm, ele, oflag);
    }
  }
  if (htype & BM_FACE) {
    BMIter iter;
    BMFace *ele;
    BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) {
      BMO_face_flag_disable(bm, ele, oflag);
    }
  }
}

void BMO_mesh_selected_remap(BMesh *bm,
                             BMOpSlot *slot_vert_map,
                             BMOpSlot *slot_edge_map,
                             BMOpSlot *slot_face_map,
                             const bool check_select)
{
  if (bm->selected.first) {
    BMEditSelection *ese, *ese_next;
    BMOpSlot *slot_elem_map;

    for (ese = bm->selected.first; ese; ese = ese_next) {
      ese_next = ese->next;

      switch (ese->htype) {
        case BM_VERT:
          slot_elem_map = slot_vert_map;
          break;
        case BM_EDGE:
          slot_elem_map = slot_edge_map;
          break;
        default:
          slot_elem_map = slot_face_map;
          break;
      }

      ese->ele = BMO_slot_map_elem_get(slot_elem_map, ese->ele);

      if (UNLIKELY((ese->ele == NULL) ||
                   (check_select && (BM_elem_flag_test(ese->ele, BM_ELEM_SELECT) == false)))) {
        BLI_remlink(&bm->selected, ese);
        MEM_freeN(ese);
      }
    }
  }

  if (bm->act_face) {
    BMFace *f = BMO_slot_map_elem_get(slot_face_map, bm->act_face);
    if (f) {
      bm->act_face = f;
    }
  }
}

int BMO_slot_buffer_len(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);

  /* check if its actually a buffer */
  if (slot->slot_type != BMO_OP_SLOT_ELEMENT_BUF) {
    return 0;
  }

  return slot->len;
}

int BMO_slot_map_len(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_MAPPING);
  return BLI_ghash_len(slot->data.ghash);
}

void BMO_slot_map_insert(BMOperator *op, BMOpSlot *slot, const void *element, const void *data)
{
  (void)op; /* Ignored in release builds. */

  BLI_assert(slot->slot_type == BMO_OP_SLOT_MAPPING);
  BMO_ASSERT_SLOT_IN_OP(slot, op);

  BLI_ghash_insert(slot->data.ghash, (void *)element, (void *)data);
}

#if 0
void *bmo_slot_buffer_grow(BMesh *bm, BMOperator *op, int slot_code, int totadd)
{
  BMOpSlot *slot = &op->slots[slot_code];
  void *tmp;
  ssize_t allocsize;

  BLI_assert(slot->slottype == BMO_OP_SLOT_ELEMENT_BUF);

  /* check if its actually a buffer */
  if (slot->slottype != BMO_OP_SLOT_ELEMENT_BUF) {
    return NULL;
  }

  if (slot->flag & BMOS_DYNAMIC_ARRAY) {
    if (slot->len >= slot->size) {
      slot->size = (slot->size + 1 + totadd) * 2;

      allocsize = BMO_OPSLOT_TYPEINFO[bmo_opdefines[op->type]->slot_types[slot_code].type] *
                  slot->size;
      slot->data.buf = MEM_recallocN_id(slot->data.buf, allocsize, "opslot dynamic array");
    }

    slot->len += totadd;
  }
  else {
    slot->flag |= BMOS_DYNAMIC_ARRAY;
    slot->len += totadd;
    slot->size = slot->len + 2;

    allocsize = BMO_OPSLOT_TYPEINFO[bmo_opdefines[op->type]->slot_types[slot_code].type] *
                slot->len;

    tmp = slot->data.buf;
    slot->data.buf = MEM_callocN(allocsize, "opslot dynamic array");
    memcpy(slot->data.buf, tmp, allocsize);
  }

  return slot->data.buf;
}
#endif

void BMO_slot_map_to_flag(BMesh *bm,
                          BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                          const char *slot_name,
                          const char htype,
                          const short oflag)
{
  GHashIterator gh_iter;
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BMElemF *ele_f;

  BLI_assert(slot->slot_type == BMO_OP_SLOT_MAPPING);

  GHASH_ITER (gh_iter, slot->data.ghash) {
    ele_f = BLI_ghashIterator_getKey(&gh_iter);
    if (ele_f->head.htype & htype) {
      BMO_elem_flag_enable(bm, ele_f, oflag);
    }
  }
}

void *BMO_slot_buffer_alloc(BMOperator *op,
                            BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                            const char *slot_name,
                            const int len)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);

  /* check if its actually a buffer */
  if (slot->slot_type != BMO_OP_SLOT_ELEMENT_BUF) {
    return NULL;
  }

  slot->len = len;
  if (len) {
    slot->data.buf = BLI_memarena_alloc(op->arena, BMO_OPSLOT_TYPEINFO[slot->slot_type] * len);
  }
  else {
    slot->data.buf = NULL;
  }

  return slot->data.buf;
}

void BMO_slot_buffer_from_all(BMesh *bm,
                              BMOperator *op,
                              BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                              const char *slot_name,
                              const char htype)
{
  BMOpSlot *output = BMO_slot_get(slot_args, slot_name);
  int totelement = 0, i = 0;

  BLI_assert(output->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(((output->slot_subtype.elem & BM_ALL_NOLOOP) & htype) == htype);

  if (htype & BM_VERT) {
    totelement += bm->totvert;
  }
  if (htype & BM_EDGE) {
    totelement += bm->totedge;
  }
  if (htype & BM_FACE) {
    totelement += bm->totface;
  }

  if (totelement) {
    BMIter iter;
    BMHeader *ele;

    BMO_slot_buffer_alloc(op, slot_args, slot_name, totelement);

    /* TODO: collapse these loops into one. */

    if (htype & BM_VERT) {
      BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
        output->data.buf[i] = ele;
        i++;
      }
    }

    if (htype & BM_EDGE) {
      BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
        output->data.buf[i] = ele;
        i++;
      }
    }

    if (htype & BM_FACE) {
      BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) {
        output->data.buf[i] = ele;
        i++;
      }
    }
  }
}

/**
 * \brief BMO_HEADERFLAG_TO_SLOT
 *
 * Copies elements of a certain type, which have a certain header flag
 * enabled/disabled into a slot for an operator.
 */
static void bmo_slot_buffer_from_hflag(BMesh *bm,
                                       BMOperator *op,
                                       BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                       const char *slot_name,
                                       const char htype,
                                       const char hflag,
                                       const bool test_for_enabled)
{
  BMOpSlot *output = BMO_slot_get(slot_args, slot_name);
  int totelement = 0, i = 0;
  const bool respecthide = ((op->flag & BMO_FLAG_RESPECT_HIDE) != 0) &&
                           ((hflag & BM_ELEM_HIDDEN) == 0);

  BLI_assert(output->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(((output->slot_subtype.elem & BM_ALL_NOLOOP) & htype) == htype);
  BLI_assert((output->slot_subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE) == 0);

  if (test_for_enabled) {
    totelement = BM_mesh_elem_hflag_count_enabled(bm, htype, hflag, respecthide);
  }
  else {
    totelement = BM_mesh_elem_hflag_count_disabled(bm, htype, hflag, respecthide);
  }

  if (totelement) {
    BMIter iter;
    BMElem *ele;

    BMO_slot_buffer_alloc(op, slot_args, slot_name, totelement);

    /* TODO: collapse these loops into one. */

    if (htype & BM_VERT) {
      BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
        if ((!respecthide || !BM_elem_flag_test(ele, BM_ELEM_HIDDEN)) &&
            BM_elem_flag_test_bool(ele, hflag) == test_for_enabled) {
          output->data.buf[i] = ele;
          i++;
        }
      }
    }

    if (htype & BM_EDGE) {
      BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
        if ((!respecthide || !BM_elem_flag_test(ele, BM_ELEM_HIDDEN)) &&
            BM_elem_flag_test_bool(ele, hflag) == test_for_enabled) {
          output->data.buf[i] = ele;
          i++;
        }
      }
    }

    if (htype & BM_FACE) {
      BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) {
        if ((!respecthide || !BM_elem_flag_test(ele, BM_ELEM_HIDDEN)) &&
            BM_elem_flag_test_bool(ele, hflag) == test_for_enabled) {
          output->data.buf[i] = ele;
          i++;
        }
      }
    }
  }
  else {
    output->len = 0;
  }
}

void BMO_slot_buffer_from_enabled_hflag(BMesh *bm,
                                        BMOperator *op,
                                        BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                        const char *slot_name,
                                        const char htype,
                                        const char hflag)
{
  bmo_slot_buffer_from_hflag(bm, op, slot_args, slot_name, htype, hflag, true);
}

void BMO_slot_buffer_from_disabled_hflag(BMesh *bm,
                                         BMOperator *op,
                                         BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                         const char *slot_name,
                                         const char htype,
                                         const char hflag)
{
  bmo_slot_buffer_from_hflag(bm, op, slot_args, slot_name, htype, hflag, false);
}

void BMO_slot_buffer_from_single(BMOperator *op, BMOpSlot *slot, BMHeader *ele)
{
  BMO_ASSERT_SLOT_IN_OP(slot, op);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(slot->slot_subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE);
  BLI_assert(ELEM(slot->len, 0, 1));

  BLI_assert(slot->slot_subtype.elem & ele->htype);

  slot->data.buf = BLI_memarena_alloc(op->arena, sizeof(void *) * 4); /* XXX, why 'x4' ? */
  slot->len = 1;
  *slot->data.buf = ele;
}

void BMO_slot_buffer_from_array(BMOperator *op,
                                BMOpSlot *slot,
                                BMHeader **ele_buffer,
                                int ele_buffer_len)
{
  BMO_ASSERT_SLOT_IN_OP(slot, op);
  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(ELEM(slot->len, 0, ele_buffer_len));

  if (slot->data.buf == NULL) {
    slot->data.buf = BLI_memarena_alloc(op->arena, sizeof(*slot->data.buf) * ele_buffer_len);
  }

  slot->len = ele_buffer_len;
  memcpy(slot->data.buf, ele_buffer, ele_buffer_len * sizeof(*slot->data.buf));
}

void *BMO_slot_buffer_get_single(BMOpSlot *slot)
{
  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(slot->slot_subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE);
  BLI_assert(ELEM(slot->len, 0, 1));

  return slot->len ? (BMHeader *)slot->data.buf[0] : NULL;
}

void _bmo_slot_buffer_append(BMOpSlot slot_args_dst[BMO_OP_MAX_SLOTS],
                             const char *slot_name_dst,
                             BMOpSlot slot_args_src[BMO_OP_MAX_SLOTS],
                             const char *slot_name_src,
                             struct MemArena *arena_dst)
{
  BMOpSlot *slot_dst = BMO_slot_get(slot_args_dst, slot_name_dst);
  BMOpSlot *slot_src = BMO_slot_get(slot_args_src, slot_name_src);

  BLI_assert(slot_dst->slot_type == BMO_OP_SLOT_ELEMENT_BUF &&
             slot_src->slot_type == BMO_OP_SLOT_ELEMENT_BUF);

  if (slot_dst->len == 0) {
    /* output slot is empty, copy rather than append */
    _bmo_slot_copy(slot_args_src, slot_name_src, slot_args_dst, slot_name_dst, arena_dst);
  }
  else if (slot_src->len != 0) {
    int elem_size = BMO_OPSLOT_TYPEINFO[slot_dst->slot_type];
    int alloc_size = elem_size * (slot_dst->len + slot_src->len);
    /* allocate new buffer */
    void *buf = BLI_memarena_alloc(arena_dst, alloc_size);

    /* copy slot data */
    memcpy(buf, slot_dst->data.buf, elem_size * slot_dst->len);
    memcpy(
        ((char *)buf) + elem_size * slot_dst->len, slot_src->data.buf, elem_size * slot_src->len);

    slot_dst->data.buf = buf;
    slot_dst->len += slot_src->len;
  }
}

/**
 * \brief BMO_FLAG_TO_SLOT
 *
 * Copies elements of a certain type, which have a certain flag set
 * into an output slot for an operator.
 */
static void bmo_slot_buffer_from_flag(BMesh *bm,
                                      BMOperator *op,
                                      BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                      const char *slot_name,
                                      const char htype,
                                      const short oflag,
                                      const bool test_for_enabled)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  int totelement, i = 0;

  BLI_assert(op->slots_in == slot_args || op->slots_out == slot_args);

  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(((slot->slot_subtype.elem & BM_ALL_NOLOOP) & htype) == htype);
  BLI_assert((slot->slot_subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE) == 0);

  if (test_for_enabled) {
    totelement = BMO_mesh_enabled_flag_count(bm, htype, oflag);
  }
  else {
    totelement = BMO_mesh_disabled_flag_count(bm, htype, oflag);
  }

  if (totelement) {
    BMIter iter;
    BMHeader *ele;
    BMHeader **ele_array;

    BMO_slot_buffer_alloc(op, slot_args, slot_name, totelement);

    ele_array = (BMHeader **)slot->data.buf;

    /* TODO: collapse these loops into one. */

    if (htype & BM_VERT) {
      BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
        if (BMO_vert_flag_test_bool(bm, (BMVert *)ele, oflag) == test_for_enabled) {
          ele_array[i] = ele;
          i++;
        }
      }
    }

    if (htype & BM_EDGE) {
      BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
        if (BMO_edge_flag_test_bool(bm, (BMEdge *)ele, oflag) == test_for_enabled) {
          ele_array[i] = ele;
          i++;
        }
      }
    }

    if (htype & BM_FACE) {
      BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) {
        if (BMO_face_flag_test_bool(bm, (BMFace *)ele, oflag) == test_for_enabled) {
          ele_array[i] = ele;
          i++;
        }
      }
    }
  }
  else {
    slot->len = 0;
  }
}

void BMO_slot_buffer_from_enabled_flag(BMesh *bm,
                                       BMOperator *op,
                                       BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                       const char *slot_name,
                                       const char htype,
                                       const short oflag)
{
  bmo_slot_buffer_from_flag(bm, op, slot_args, slot_name, htype, oflag, true);
}

void BMO_slot_buffer_from_disabled_flag(BMesh *bm,
                                        BMOperator *op,
                                        BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                        const char *slot_name,
                                        const char htype,
                                        const short oflag)
{
  bmo_slot_buffer_from_flag(bm, op, slot_args, slot_name, htype, oflag, false);
}

void BMO_slot_buffer_hflag_enable(BMesh *bm,
                                  BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                  const char *slot_name,
                                  const char htype,
                                  const char hflag,
                                  const bool do_flush)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BMElem **data = (BMElem **)slot->data.buf;
  int i;
  const bool do_flush_select = (do_flush && (hflag & BM_ELEM_SELECT));
  const bool do_flush_hide = (do_flush && (hflag & BM_ELEM_HIDDEN));

  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(((slot->slot_subtype.elem & BM_ALL_NOLOOP) & htype) == htype);
  BLI_assert((slot->slot_subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE) == 0);

  for (i = 0; i < slot->len; i++, data++) {
    if (!(htype & (*data)->head.htype)) {
      continue;
    }

    if (do_flush_select) {
      BM_elem_select_set(bm, *data, true);
    }

    if (do_flush_hide) {
      BM_elem_hide_set(bm, *data, false);
    }

    BM_elem_flag_enable(*data, hflag);
  }
}

void BMO_slot_buffer_hflag_disable(BMesh *bm,
                                   BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                   const char *slot_name,
                                   const char htype,
                                   const char hflag,
                                   const bool do_flush)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BMElem **data = (BMElem **)slot->data.buf;
  int i;
  const bool do_flush_select = (do_flush && (hflag & BM_ELEM_SELECT));
  const bool do_flush_hide = (do_flush && (hflag & BM_ELEM_HIDDEN));

  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(((slot->slot_subtype.elem & BM_ALL_NOLOOP) & htype) == htype);

  for (i = 0; i < slot->len; i++, data++) {
    if (!(htype & (*data)->head.htype)) {
      continue;
    }

    if (do_flush_select) {
      BM_elem_select_set(bm, *data, false);
    }

    if (do_flush_hide) {
      BM_elem_hide_set(bm, *data, false);
    }

    BM_elem_flag_disable(*data, hflag);
  }
}

void BMO_slot_buffer_flag_enable(BMesh *bm,
                                 BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                 const char *slot_name,
                                 const char htype,
                                 const short oflag)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BMHeader **data = slot->data.p;
  int i;

  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(((slot->slot_subtype.elem & BM_ALL_NOLOOP) & htype) == htype);

  for (i = 0; i < slot->len; i++) {
    if (!(htype & data[i]->htype)) {
      continue;
    }

    BMO_elem_flag_enable(bm, (BMElemF *)data[i], oflag);
  }
}

void BMO_slot_buffer_flag_disable(BMesh *bm,
                                  BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                                  const char *slot_name,
                                  const char htype,
                                  const short oflag)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);
  BMHeader **data = (BMHeader **)slot->data.buf;
  int i;

  BLI_assert(slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF);
  BLI_assert(((slot->slot_subtype.elem & BM_ALL_NOLOOP) & htype) == htype);

  for (i = 0; i < slot->len; i++) {
    if (!(htype & data[i]->htype)) {
      continue;
    }

    BMO_elem_flag_disable(bm, (BMElemF *)data[i], oflag);
  }
}

/**
 * \brief ALLOC/FREE FLAG LAYER
 *
 * Used by operator stack to free/allocate
 * private flag data. This is allocated
 * using a mempool so the allocation/frees
 * should be quite fast.
 *
 * BMESH_TODO:
 * Investigate not freeing flag layers until
 * all operators have been executed. This would
 * save a lot of realloc potentially.
 */
static void bmo_flag_layer_alloc(BMesh *bm)
{
  /* set the index values since we are looping over all data anyway,
   * may save time later on */

  BLI_mempool *voldpool = bm->vtoolflagpool; /* old flag pool */
  BLI_mempool *eoldpool = bm->etoolflagpool; /* old flag pool */
  BLI_mempool *foldpool = bm->ftoolflagpool; /* old flag pool */

  /* store memcpy size for reuse */
  const size_t old_totflags_size = (bm->totflags * sizeof(BMFlagLayer));

  bm->totflags++;

  bm->vtoolflagpool = BLI_mempool_create(
      sizeof(BMFlagLayer) * bm->totflags, bm->totvert, 512, BLI_MEMPOOL_NOP);
  bm->etoolflagpool = BLI_mempool_create(
      sizeof(BMFlagLayer) * bm->totflags, bm->totedge, 512, BLI_MEMPOOL_NOP);
  bm->ftoolflagpool = BLI_mempool_create(
      sizeof(BMFlagLayer) * bm->totflags, bm->totface, 512, BLI_MEMPOOL_NOP);

  /* now go through and memcpy all the flags. Loops don't get a flag layer at this time. */
  BMIter iter;
  int i;

  BMVert_OFlag *v_oflag;
  BLI_mempool *newpool = bm->vtoolflagpool;
  BM_ITER_MESH_INDEX (v_oflag, &iter, bm, BM_VERTS_OF_MESH, i) {
    void *oldflags = v_oflag->oflags;
    v_oflag->oflags = BLI_mempool_calloc(newpool);
    memcpy(v_oflag->oflags, oldflags, old_totflags_size);
    BM_elem_index_set(&v_oflag->base, i); /* set_inline */
    BM_ELEM_API_FLAG_CLEAR((BMElemF *)v_oflag);
  }

  BMEdge_OFlag *e_oflag;
  newpool = bm->etoolflagpool;
  BM_ITER_MESH_INDEX (e_oflag, &iter, bm, BM_EDGES_OF_MESH, i) {
    void *oldflags = e_oflag->oflags;
    e_oflag->oflags = BLI_mempool_calloc(newpool);
    memcpy(e_oflag->oflags, oldflags, old_totflags_size);
    BM_elem_index_set(&e_oflag->base, i); /* set_inline */
    BM_ELEM_API_FLAG_CLEAR((BMElemF *)e_oflag);
  }

  BMFace_OFlag *f_oflag;
  newpool = bm->ftoolflagpool;
  BM_ITER_MESH_INDEX (f_oflag, &iter, bm, BM_FACES_OF_MESH, i) {
    void *oldflags = f_oflag->oflags;
    f_oflag->oflags = BLI_mempool_calloc(newpool);
    memcpy(f_oflag->oflags, oldflags, old_totflags_size);
    BM_elem_index_set(&f_oflag->base, i); /* set_inline */
    BM_ELEM_API_FLAG_CLEAR((BMElemF *)f_oflag);
  }

  BLI_mempool_destroy(voldpool);
  BLI_mempool_destroy(eoldpool);
  BLI_mempool_destroy(foldpool);

  bm->elem_index_dirty &= ~(BM_VERT | BM_EDGE | BM_FACE);
}

static void bmo_flag_layer_free(BMesh *bm)
{
  /* set the index values since we are looping over all data anyway,
   * may save time later on */

  BLI_mempool *voldpool = bm->vtoolflagpool;
  BLI_mempool *eoldpool = bm->etoolflagpool;
  BLI_mempool *foldpool = bm->ftoolflagpool;

  /* store memcpy size for reuse */
  const size_t new_totflags_size = ((bm->totflags - 1) * sizeof(BMFlagLayer));

  /* de-increment the totflags first. */
  bm->totflags--;

  bm->vtoolflagpool = BLI_mempool_create(new_totflags_size, bm->totvert, 512, BLI_MEMPOOL_NOP);
  bm->etoolflagpool = BLI_mempool_create(new_totflags_size, bm->totedge, 512, BLI_MEMPOOL_NOP);
  bm->ftoolflagpool = BLI_mempool_create(new_totflags_size, bm->totface, 512, BLI_MEMPOOL_NOP);

  /* now go through and memcpy all the flag */
  BMIter iter;
  int i;

  BMVert_OFlag *v_oflag;
  BLI_mempool *newpool = bm->vtoolflagpool;
  BM_ITER_MESH_INDEX (v_oflag, &iter, bm, BM_VERTS_OF_MESH, i) {
    void *oldflags = v_oflag->oflags;
    v_oflag->oflags = BLI_mempool_alloc(newpool);
    memcpy(v_oflag->oflags, oldflags, new_totflags_size);
    BM_elem_index_set(&v_oflag->base, i); /* set_inline */
    BM_ELEM_API_FLAG_CLEAR((BMElemF *)v_oflag);
  }

  BMEdge_OFlag *e_oflag;
  newpool = bm->etoolflagpool;
  BM_ITER_MESH_INDEX (e_oflag, &iter, bm, BM_EDGES_OF_MESH, i) {
    void *oldflags = e_oflag->oflags;
    e_oflag->oflags = BLI_mempool_alloc(newpool);
    memcpy(e_oflag->oflags, oldflags, new_totflags_size);
    BM_elem_index_set(&e_oflag->base, i); /* set_inline */
    BM_ELEM_API_FLAG_CLEAR((BMElemF *)e_oflag);
  }

  BMFace_OFlag *f_oflag;
  newpool = bm->ftoolflagpool;
  BM_ITER_MESH_INDEX (f_oflag, &iter, bm, BM_FACES_OF_MESH, i) {
    void *oldflags = f_oflag->oflags;
    f_oflag->oflags = BLI_mempool_alloc(newpool);
    memcpy(f_oflag->oflags, oldflags, new_totflags_size);
    BM_elem_index_set(&f_oflag->base, i); /* set_inline */
    BM_ELEM_API_FLAG_CLEAR((BMElemF *)f_oflag);
  }

  BLI_mempool_destroy(voldpool);
  BLI_mempool_destroy(eoldpool);
  BLI_mempool_destroy(foldpool);

  bm->elem_index_dirty &= ~(BM_VERT | BM_EDGE | BM_FACE);
}

static void bmo_flag_layer_clear(BMesh *bm)
{
  /* set the index values since we are looping over all data anyway,
   * may save time later on */
  const BMFlagLayer zero_flag = {0};

  const int totflags_offset = bm->totflags - 1;

  /* now go through and memcpy all the flag */
  {
    BMIter iter;
    BMVert_OFlag *ele;
    int i;
    BM_ITER_MESH_INDEX (ele, &iter, bm, BM_VERTS_OF_MESH, i) {
      ele->oflags[totflags_offset] = zero_flag;
      BM_elem_index_set(&ele->base, i); /* set_inline */
    }
  }
  {
    BMIter iter;
    BMEdge_OFlag *ele;
    int i;
    BM_ITER_MESH_INDEX (ele, &iter, bm, BM_EDGES_OF_MESH, i) {
      ele->oflags[totflags_offset] = zero_flag;
      BM_elem_index_set(&ele->base, i); /* set_inline */
    }
  }
  {
    BMIter iter;
    BMFace_OFlag *ele;
    int i;
    BM_ITER_MESH_INDEX (ele, &iter, bm, BM_FACES_OF_MESH, i) {
      ele->oflags[totflags_offset] = zero_flag;
      BM_elem_index_set(&ele->base, i); /* set_inline */
    }
  }

  bm->elem_index_dirty &= ~(BM_VERT | BM_EDGE | BM_FACE);
}

void *BMO_slot_buffer_get_first(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);

  if (slot->slot_type != BMO_OP_SLOT_ELEMENT_BUF) {
    return NULL;
  }

  return slot->data.buf ? *slot->data.buf : NULL;
}

void *BMO_iter_new(BMOIter *iter,
                   BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
                   const char *slot_name,
                   const char restrictmask)
{
  BMOpSlot *slot = BMO_slot_get(slot_args, slot_name);

  memset(iter, 0, sizeof(BMOIter));

  iter->slot = slot;
  iter->cur = 0;
  iter->restrictmask = restrictmask;

  if (iter->slot->slot_type == BMO_OP_SLOT_MAPPING) {
    BLI_ghashIterator_init(&iter->giter, slot->data.ghash);
  }
  else if (iter->slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF) {
    BLI_assert(restrictmask & slot->slot_subtype.elem);
  }
  else {
    BLI_assert(0);
  }

  return BMO_iter_step(iter);
}

void *BMO_iter_step(BMOIter *iter)
{
  BMOpSlot *slot = iter->slot;
  if (slot->slot_type == BMO_OP_SLOT_ELEMENT_BUF) {
    BMHeader *ele;

    if (iter->cur >= slot->len) {
      return NULL;
    }

    ele = slot->data.buf[iter->cur++];
    while (!(iter->restrictmask & ele->htype)) {
      if (iter->cur >= slot->len) {
        return NULL;
      }

      ele = slot->data.buf[iter->cur++];
      BLI_assert((ele == NULL) || (slot->slot_subtype.elem & ele->htype));
    }

    BLI_assert((ele == NULL) || (slot->slot_subtype.elem & ele->htype));

    return ele;
  }
  if (slot->slot_type == BMO_OP_SLOT_MAPPING) {
    void *ret;

    if (BLI_ghashIterator_done(&iter->giter) == false) {
      ret = BLI_ghashIterator_getKey(&iter->giter);
      iter->val = BLI_ghashIterator_getValue_p(&iter->giter);

      BLI_ghashIterator_step(&iter->giter);
    }
    else {
      ret = NULL;
      iter->val = NULL;
    }

    return ret;
  }
  BLI_assert(0);

  return NULL;
}

/* used for iterating over mappings */

void **BMO_iter_map_value_p(BMOIter *iter)
{
  return iter->val;
}

void *BMO_iter_map_value_ptr(BMOIter *iter)
{
  BLI_assert(ELEM(iter->slot->slot_subtype.map,
                  BMO_OP_SLOT_SUBTYPE_MAP_ELEM,
                  BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL));
  return iter->val ? *iter->val : NULL;
}

float BMO_iter_map_value_float(BMOIter *iter)
{
  BLI_assert(iter->slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_FLT);
  return **((float **)iter->val);
}

int BMO_iter_map_value_int(BMOIter *iter)
{
  BLI_assert(iter->slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INT);
  return **((int **)iter->val);
}

bool BMO_iter_map_value_bool(BMOIter *iter)
{
  BLI_assert(iter->slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_BOOL);
  return **((bool **)iter->val);
}

/* error system */
typedef struct BMOpError {
  struct BMOpError *next, *prev;
  BMOperator *op;
  const char *msg;
  eBMOpErrorLevel level;
} BMOpError;

void BMO_error_clear(BMesh *bm)
{
  while (BMO_error_pop(bm, NULL, NULL, NULL)) {
    /* pass */
  }
}

void BMO_error_raise(BMesh *bm, BMOperator *owner, eBMOpErrorLevel level, const char *msg)
{
  BMOpError *err = MEM_callocN(sizeof(BMOpError), "bmop_error");

  err->msg = msg;
  err->op = owner;
  err->level = level;

  BLI_addhead(&bm->errorstack, err);
}

bool BMO_error_occurred_at_level(BMesh *bm, eBMOpErrorLevel level)
{
  for (const BMOpError *err = bm->errorstack.first; err; err = err->next) {
    if (err->level == level) {
      return true;
    }
  }
  return false;
}

bool BMO_error_get(BMesh *bm, const char **r_msg, BMOperator **r_op, eBMOpErrorLevel *r_level)
{
  BMOpError *err = bm->errorstack.first;
  if (err == NULL) {
    return false;
  }

  if (r_msg) {
    *r_msg = err->msg;
  }
  if (r_op) {
    *r_op = err->op;
  }
  if (r_level) {
    *r_level = err->level;
  }

  return true;
}

bool BMO_error_get_at_level(BMesh *bm,
                            eBMOpErrorLevel level,
                            const char **r_msg,
                            BMOperator **r_op)
{
  for (BMOpError *err = bm->errorstack.first; err; err = err->next) {
    if (err->level >= level) {
      if (r_msg) {
        *r_msg = err->msg;
      }
      if (r_op) {
        *r_op = err->op;
      }
      return true;
    }
  }

  return false;
}

bool BMO_error_pop(BMesh *bm, const char **r_msg, BMOperator **r_op, eBMOpErrorLevel *r_level)
{
  bool result = BMO_error_get(bm, r_msg, r_op, r_level);

  if (result) {
    BMOpError *err = bm->errorstack.first;

    BLI_remlink(&bm->errorstack, bm->errorstack.first);
    MEM_freeN(err);
  }

  return result;
}

#define NEXT_CHAR(fmt) ((fmt)[0] != 0 ? (fmt)[1] : 0)

static int bmo_name_to_slotcode(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier)
{
  int i = 0;

  while (slot_args->slot_name) {
    if (STREQLEN(identifier, slot_args->slot_name, MAX_SLOTNAME)) {
      return i;
    }
    slot_args++;
    i++;
  }

  return -1;
}

static int bmo_name_to_slotcode_check(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier)
{
  int i = bmo_name_to_slotcode(slot_args, identifier);
  if (i < 0) {
    fprintf(stderr,
            "%s: ! could not find bmesh slot for name %s! (bmesh internal error)\n",
            __func__,
            identifier);
  }

  return i;
}

int BMO_opcode_from_opname(const char *opname)
{

  const uint tot = bmo_opdefines_total;
  uint i;
  for (i = 0; i < tot; i++) {
    if (STREQ(bmo_opdefines[i]->opname, opname)) {
      return i;
    }
  }
  return -1;
}

static int BMO_opcode_from_opname_check(const char *opname)
{
  int i = BMO_opcode_from_opname(opname);
  if (i == -1) {
    fprintf(stderr,
            "%s: could not find bmesh slot for name %s! (bmesh internal error)\n",
            __func__,
            opname);
  }
  return i;
}

bool BMO_op_vinitf(BMesh *bm, BMOperator *op, const int flag, const char *_fmt, va_list vlist)
{
  //  BMOpDefine *def;
  char *opname, *ofmt, *fmt;
  char slot_name[64] = {0};
  int i, type;
  bool noslot, state;

  /* basic useful info to help find where bmop formatting strings fail */
  const char *err_reason = "Unknown";
  int lineno = -1;

#define GOTO_ERROR(reason) \
  { \
    err_reason = reason; \
    lineno = __LINE__; \
    goto error; \
  } \
  (void)0

  /* we muck around in here, so dup it */
  fmt = ofmt = BLI_strdup(_fmt);

  /* find operator name */
  i = strcspn(fmt, " ");

  opname = fmt;
  noslot = (opname[i] == '\0');
  opname[i] = '\0';

  fmt += i + (noslot ? 0 : 1);

  i = BMO_opcode_from_opname_check(opname);

  if (i == -1) {
    MEM_freeN(ofmt);
    BLI_assert(0);
    return false;
  }

  BMO_op_init(bm, op, flag, opname);
  //  def = bmo_opdefines[i];

  i = 0;
  state = true; /* false: not inside slot_code name, true: inside slot_code name */

  while (*fmt) {
    if (state) {
      /* Jump past leading white-space. */
      i = strspn(fmt, " ");
      fmt += i;

      /* Ignore trailing white-space. */
      if (!fmt[i]) {
        break;
      }

      /* find end of slot name, only "slot=%f", can be used */
      i = strcspn(fmt, "=");
      if (!fmt[i]) {
        GOTO_ERROR("could not match end of slot name");
      }

      fmt[i] = 0;

      if (bmo_name_to_slotcode_check(op->slots_in, fmt) < 0) {
        GOTO_ERROR("name to slot code check failed");
      }

      BLI_strncpy(slot_name, fmt, sizeof(slot_name));

      state = false;
      fmt += i;
    }
    else {
      switch (*fmt) {
        case ' ':
        case '=':
        case '%':
          break;
        case 'm': {
          int size;
          const char c = NEXT_CHAR(fmt);
          fmt++;

          if (c == '3') {
            size = 3;
          }
          else if (c == '4') {
            size = 4;
          }
          else {
            GOTO_ERROR("matrix size was not 3 or 4");
          }

          BMO_slot_mat_set(op, op->slots_in, slot_name, va_arg(vlist, void *), size);
          state = true;
          break;
        }
        case 'v': {
          BMO_slot_vec_set(op->slots_in, slot_name, va_arg(vlist, float *));
          state = true;
          break;
        }
        case 'e': {
          BMOpSlot *slot = BMO_slot_get(op->slots_in, slot_name);

          if (NEXT_CHAR(fmt) == 'b') {
            BMHeader **ele_buffer = va_arg(vlist, void *);
            int ele_buffer_len = va_arg(vlist, int);

            BMO_slot_buffer_from_array(op, slot, ele_buffer, ele_buffer_len);
            fmt++;
          }
          else {
            /* single vert/edge/face */
            BMHeader *ele = va_arg(vlist, void *);

            BMO_slot_buffer_from_single(op, slot, ele);
          }

          state = true;
          break;
        }
        case 's':
        case 'S': {
          BMOperator *op_other = va_arg(vlist, void *);
          const char *slot_name_other = va_arg(vlist, char *);

          if (*fmt == 's') {
            BLI_assert(bmo_name_to_slotcode_check(op_other->slots_in, slot_name_other) != -1);
            BMO_slot_copy(op_other, slots_in, slot_name_other, op, slots_in, slot_name);
          }
          else {
            BLI_assert(bmo_name_to_slotcode_check(op_other->slots_out, slot_name_other) != -1);
            BMO_slot_copy(op_other, slots_out, slot_name_other, op, slots_in, slot_name);
          }
          state = true;
          break;
        }
        case 'i':
          BMO_slot_int_set(op->slots_in, slot_name, va_arg(vlist, int));
          state = true;
          break;
        case 'b':
          BMO_slot_bool_set(op->slots_in, slot_name, va_arg(vlist, int));
          state = true;
          break;
        case 'p':
          BMO_slot_ptr_set(op->slots_in, slot_name, va_arg(vlist, void *));
          state = true;
          break;
        case 'f':
        case 'F':
        case 'h':
        case 'H':
        case 'a':
          type = *fmt;

          if (NEXT_CHAR(fmt) == ' ' || NEXT_CHAR(fmt) == '\0') {
            BMO_slot_float_set(op->slots_in, slot_name, va_arg(vlist, double));
          }
          else {
            char htype = 0;

            while (1) {
              char htype_set;
              const char c = NEXT_CHAR(fmt);
              if (c == 'f') {
                htype_set = BM_FACE;
              }
              else if (c == 'e') {
                htype_set = BM_EDGE;
              }
              else if (c == 'v') {
                htype_set = BM_VERT;
              }
              else {
                break;
              }

              if (UNLIKELY(htype & htype_set)) {
                GOTO_ERROR("htype duplicated");
              }

              htype |= htype_set;
              fmt++;
            }

            if (type == 'h') {
              BMO_slot_buffer_from_enabled_hflag(
                  bm, op, op->slots_in, slot_name, htype, va_arg(vlist, int));
            }
            else if (type == 'H') {
              BMO_slot_buffer_from_disabled_hflag(
                  bm, op, op->slots_in, slot_name, htype, va_arg(vlist, int));
            }
            else if (type == 'a') {
              if ((op->flag & BMO_FLAG_RESPECT_HIDE) == 0) {
                BMO_slot_buffer_from_all(bm, op, op->slots_in, slot_name, htype);
              }
              else {
                BMO_slot_buffer_from_disabled_hflag(
                    bm, op, op->slots_in, slot_name, htype, BM_ELEM_HIDDEN);
              }
            }
            else if (type == 'f') {
              BMO_slot_buffer_from_enabled_flag(
                  bm, op, op->slots_in, slot_name, htype, va_arg(vlist, int));
            }
            else if (type == 'F') {
              BMO_slot_buffer_from_disabled_flag(
                  bm, op, op->slots_in, slot_name, htype, va_arg(vlist, int));
            }
          }

          state = true;
          break;
        default:
          fprintf(stderr,
                  "%s: unrecognized bmop format char: '%c', %d in '%s'\n",
                  __func__,
                  *fmt,
                  (int)(fmt - ofmt),
                  ofmt);
          break;
      }
    }
    fmt++;
  }

  MEM_freeN(ofmt);
  return true;
error:

  /* TODO: explain exactly what is failing (not urgent). */
  fprintf(stderr, "%s: error parsing formatting string\n", __func__);

  fprintf(stderr, "string: '%s', position %d\n", _fmt, (int)(fmt - ofmt));
  fprintf(stderr, "         ");
  {
    int pos = (int)(fmt - ofmt);
    for (i = 0; i < pos; i++) {
      fprintf(stderr, " ");
    }
    fprintf(stderr, "^\n");
  }

  fprintf(stderr, "source code:  %s:%d\n", __FILE__, lineno);

  fprintf(stderr, "reason: %s\n", err_reason);

  MEM_freeN(ofmt);

  BMO_op_finish(bm, op);
  return false;

#undef GOTO_ERROR
}

bool BMO_op_initf(BMesh *bm, BMOperator *op, const int flag, const char *fmt, ...)
{
  va_list list;

  va_start(list, fmt);
  if (!BMO_op_vinitf(bm, op, flag, fmt, list)) {
    printf("%s: failed\n", __func__);
    va_end(list);
    return false;
  }
  va_end(list);

  return true;
}

bool BMO_op_callf(BMesh *bm, const int flag, const char *fmt, ...)
{
  va_list list;
  BMOperator op;

  va_start(list, fmt);
  if (!BMO_op_vinitf(bm, &op, flag, fmt, list)) {
    printf("%s: failed, format is:\n    \"%s\"\n", __func__, fmt);
    va_end(list);
    return false;
  }

  BMO_op_exec(bm, &op);
  BMO_op_finish(bm, &op);

  va_end(list);
  return true;
}
