/* SPDX-FileCopyrightText: 2009 Blender Authors, Joshua Leung. All rights reserved.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup bli
 */

#include "MEM_guardedalloc.h"

#include "BLI_dlrbTree.h"
#include "BLI_listbase.h"

/* *********************************************** */
/* Tree API */

DLRBT_Tree *BLI_dlrbTree_new(void)
{
  /* just allocate for now */
  return MEM_callocN(sizeof(DLRBT_Tree), "DLRBT_Tree");
}

void BLI_dlrbTree_init(DLRBT_Tree *tree)
{
  if (tree == NULL) {
    return;
  }

  tree->first = tree->last = tree->root = NULL;
}

/* Helper for traversing tree and freeing sub-nodes */
static void recursive_tree_free_nodes(DLRBT_Node *node, DLRBT_NFree_FP free_cb)
{
  /* sanity check */
  if (node == NULL) {
    return;
  }

  /* free child nodes + subtrees */
  recursive_tree_free_nodes(node->left, free_cb);
  recursive_tree_free_nodes(node->right, free_cb);

  /* free self */
  if (free_cb) {
    free_cb(node);
  }
}

void BLI_dlrbTree_free(DLRBT_Tree *tree, DLRBT_NFree_FP free_cb)
{
  if (tree == NULL) {
    return;
  }

  /* if the list-base stuff is set, just use that (and assume its set),
   * otherwise, we'll need to traverse the tree...
   */
  if (tree->first) {
    /* free list */
    if (free_cb) {
      LISTBASE_FOREACH_MUTABLE (DLRBT_Node *, node, tree) {
        free_cb(node);
      }
      BLI_listbase_clear((ListBase *)tree);
    }
    else {
      BLI_freelistN((ListBase *)tree);
    }
  }
  else {
    /* traverse tree, freeing sub-nodes */
    recursive_tree_free_nodes(tree->root, free_cb);
  }

  /* clear pointers */
  tree->first = tree->last = tree->root = NULL;
}

/* ------- */

/* Helper function - used for traversing down the tree from the root to add nodes in order */
static void linkedlist_sync_add_node(DLRBT_Tree *tree, DLRBT_Node *node)
{
  /* sanity checks */
  if ((tree == NULL) || (node == NULL)) {
    return;
  }

  /* add left-node (and its subtree) */
  linkedlist_sync_add_node(tree, node->left);

  /* now add self
   * - must remove detach from other links first
   *   (for now, only clear own pointers)
   */
  node->prev = node->next = NULL;
  BLI_addtail((ListBase *)tree, (Link *)node);

  /* finally, add right node (and its subtree) */
  linkedlist_sync_add_node(tree, node->right);
}

void BLI_dlrbTree_linkedlist_sync(DLRBT_Tree *tree)
{
  /* sanity checks */
  if (tree == NULL) {
    return;
  }

  /* clear list-base pointers so that the new list can be added properly */
  tree->first = tree->last = NULL;

  /* start adding items from the root */
  linkedlist_sync_add_node(tree, tree->root);
}

/* *********************************************** */
/* Tree Search Utilities */

DLRBT_Node *BLI_dlrbTree_search(const DLRBT_Tree *tree,
                                DLRBT_Comparator_FP cmp_cb,
                                void *search_data)
{
  DLRBT_Node *node = (tree) ? tree->root : NULL;
  short found = 0;

  /* check that there is a comparator to use */
  /* TODO: if no comparator is supplied, try using the one supplied with the tree... */
  if (cmp_cb == NULL) {
    return NULL;
  }

  /* iteratively perform this search */
  while (node && found == 0) {
    /* check if traverse further or not
     * NOTE: it is assumed that the values will be unit values only
     */
    switch (cmp_cb(node, search_data)) {
      case -1: /* data less than node */
        if (node->left) {
          node = node->left;
        }
        else {
          found = 1;
        }
        break;

      case 1: /* data greater than node */
        if (node->right) {
          node = node->right;
        }
        else {
          found = 1;
        }
        break;

      default: /* data equals node */
        found = 1;
        break;
    }
  }

  /* return the nearest matching node */
  return node;
}

DLRBT_Node *BLI_dlrbTree_search_exact(const DLRBT_Tree *tree,
                                      DLRBT_Comparator_FP cmp_cb,
                                      void *search_data)
{
  DLRBT_Node *node = (tree) ? tree->root : NULL;
  short found = 0;

  /* check that there is a comparator to use */
  /* TODO: if no comparator is supplied, try using the one supplied with the tree... */
  if (cmp_cb == NULL) {
    return NULL;
  }

  /* iteratively perform this search */
  while (node && found == 0) {
    /* check if traverse further or not
     * NOTE: it is assumed that the values will be unit values only
     */
    switch (cmp_cb(node, search_data)) {
      case -1: /* data less than node */
        if (node->left) {
          node = node->left;
        }
        else {
          found = -1;
        }
        break;

      case 1: /* data greater than node */
        if (node->right) {
          node = node->right;
        }
        else {
          found = -1;
        }
        break;

      default: /* data equals node */
        found = 1;
        break;
    }
  }

  /* return the exactly matching node */
  return (found == 1) ? (node) : (NULL);
}

DLRBT_Node *BLI_dlrbTree_search_prev(const DLRBT_Tree *tree,
                                     DLRBT_Comparator_FP cmp_cb,
                                     void *search_data)
{
  DLRBT_Node *node;

  /* check that there is a comparator to use */
  /* TODO: if no comparator is supplied, try using the one supplied with the tree... */
  if (cmp_cb == NULL) {
    return NULL;
  }

  /* get the node which best matches this description */
  node = BLI_dlrbTree_search(tree, cmp_cb, search_data);

  if (node) {
    /* if the item we're searching for is greater than the node found, we've found the match */
    if (cmp_cb(node, search_data) > 0) {
      return node;
    }

    /* return the previous node otherwise */
    /* NOTE: what happens if there is no previous node? */
    return node->prev;
  }

  /* nothing matching was found */
  return NULL;
}

DLRBT_Node *BLI_dlrbTree_search_next(const DLRBT_Tree *tree,
                                     DLRBT_Comparator_FP cmp_cb,
                                     void *search_data)
{
  DLRBT_Node *node;

  /* check that there is a comparator to use */
  /* TODO: if no comparator is supplied, try using the one supplied with the tree... */
  if (cmp_cb == NULL) {
    return NULL;
  }

  /* get the node which best matches this description */
  node = BLI_dlrbTree_search(tree, cmp_cb, search_data);

  if (node) {
    /* if the item we're searching for is less than the node found, we've found the match */
    if (cmp_cb(node, search_data) < 0) {
      return node;
    }

    /* return the previous node otherwise */
    /* NOTE: what happens if there is no previous node? */
    return node->next;
  }

  /* nothing matching was found */
  return NULL;
}

short BLI_dlrbTree_contains(DLRBT_Tree *tree, DLRBT_Comparator_FP cmp_cb, void *search_data)
{
  /* check if an exact search throws up anything... */
  return (BLI_dlrbTree_search_exact(tree, cmp_cb, search_data) != NULL);
}

/* *********************************************** */
/* Tree Relationships Utilities */

/* get the 'grandparent' - the parent of the parent - of the given node */
static DLRBT_Node *get_grandparent(DLRBT_Node *node)
{
  if (node && node->parent) {
    return node->parent->parent;
  }
  return NULL;
}

/* get the sibling node (e.g. if node is left child of parent, return right child of parent) */
static DLRBT_Node *get_sibling(DLRBT_Node *node)
{
  if (node && node->parent) {
    if (node == node->parent->left) {
      return node->parent->right;
    }
    return node->parent->left;
  }

  /* sibling not found */
  return NULL;
}

/* get the 'uncle' - the sibling of the parent - of the given node */
static DLRBT_Node *get_uncle(DLRBT_Node *node)
{
  if (node) {
    /* return the child of the grandparent which isn't the node's parent */
    return get_sibling(node->parent);
  }

  /* uncle not found */
  return NULL;
}

/* *********************************************** */
/* Tree Rotation Utilities */

/* make right child of 'root' the new root */
static void rotate_left(DLRBT_Tree *tree, DLRBT_Node *root)
{
  DLRBT_Node **root_slot, *pivot;

  /* pivot is simply the root's right child, to become the root's parent */
  pivot = root->right;
  if (pivot == NULL) {
    return;
  }

  if (root->parent) {
    if (root == root->parent->left) {
      root_slot = &root->parent->left;
    }
    else {
      root_slot = &root->parent->right;
    }
  }
  else {
    root_slot = ((DLRBT_Node **)&tree->root); /* &((DLRBT_Node *)tree->root); */
  }

  /* - pivot's left child becomes root's right child
   * - root now becomes pivot's left child
   */
  root->right = pivot->left;
  if (pivot->left) {
    pivot->left->parent = root;
  }

  pivot->left = root;
  pivot->parent = root->parent;
  root->parent = pivot;

  /* make the pivot the new root */
  if (root_slot) {
    *root_slot = pivot;
  }
}

/* make the left child of the 'root' the new root */
static void rotate_right(DLRBT_Tree *tree, DLRBT_Node *root)
{
  DLRBT_Node **root_slot, *pivot;

  /* pivot is simply the root's left child, to become the root's parent */
  pivot = root->left;
  if (pivot == NULL) {
    return;
  }

  if (root->parent) {
    if (root == root->parent->left) {
      root_slot = &root->parent->left;
    }
    else {
      root_slot = &root->parent->right;
    }
  }
  else {
    root_slot = ((DLRBT_Node **)&tree->root); /* &((DLRBT_Node *)tree->root); */
  }

  /* - pivot's right child becomes root's left child
   * - root now becomes pivot's right child
   */
  root->left = pivot->right;
  if (pivot->right) {
    pivot->right->parent = root;
  }

  pivot->right = root;
  pivot->parent = root->parent;
  root->parent = pivot;

  /* make the pivot the new root */
  if (root_slot) {
    *root_slot = pivot;
  }
}

/* *********************************************** */
/* Post-Insertion Balancing */

/* forward defines for insertion checks */
static void insert_check_1(DLRBT_Tree *tree, DLRBT_Node *node);
static void insert_check_2(DLRBT_Tree *tree, DLRBT_Node *node);
static void insert_check_3(DLRBT_Tree *tree, DLRBT_Node *node);

/* ----- */

/* W. 1) Root must be black (so that the 2nd-generation can have a black parent) */
static void insert_check_1(DLRBT_Tree *tree, DLRBT_Node *node)
{
  if (node) {
    /* if this is the root, just ensure that it is black */
    if (node->parent == NULL) {
      node->tree_col = DLRBT_BLACK;
    }
    else {
      insert_check_2(tree, node);
    }
  }
}

/* W. 2+3) Parent of node must be black, otherwise recolor and flush */
static void insert_check_2(DLRBT_Tree *tree, DLRBT_Node *node)
{
  /* if the parent is not black, we need to change that... */
  if (node && node->parent && node->parent->tree_col) {
    DLRBT_Node *unc = get_uncle(node);

    /* if uncle and parent are both red, need to change them to black and make
     * the parent black in order to satisfy the criteria of each node having the
     * same number of black nodes to its leaves
     */
    if (unc && unc->tree_col) {
      DLRBT_Node *gp = get_grandparent(node);

      /* make the n-1 generation nodes black */
      node->parent->tree_col = unc->tree_col = DLRBT_BLACK;

      /* - make the grandparent red, so that we maintain alternating red/black property
       *  (it must exist, so no need to check for NULL here),
       * - as the grandparent may now cause inconsistencies with the rest of the tree,
       *   we must flush up the tree and perform checks/re-balancing/re-painting, using the
       *   grandparent as the node of interest
       */
      gp->tree_col = DLRBT_RED;
      insert_check_1(tree, gp);
    }
    else {
      /* we've got an unbalanced branch going down the grandparent to the parent,
       * so need to perform some rotations to re-balance the tree
       */
      insert_check_3(tree, node);
    }
  }
}

/* W. 4+5) Perform rotation on sub-tree containing the 'new' node, then do any. */
static void insert_check_3(DLRBT_Tree *tree, DLRBT_Node *node)
{
  DLRBT_Node *gp = get_grandparent(node);

  /* check that grandparent and node->parent exist
   * (jut in case... really shouldn't happen on a good tree) */
  if (node && node->parent && gp) {
    /* a left rotation will switch the roles of node and its parent, assuming that
     * the parent is the left child of the grandparent... otherwise, rotation direction
     * should be swapped
     */
    if ((node == node->parent->right) && (node->parent == gp->left)) {
      rotate_left(tree, node);
      node = node->left;
    }
    else if ((node == node->parent->left) && (node->parent == gp->right)) {
      rotate_right(tree, node);
      node = node->right;
    }

    /* fix old parent's color-tagging, and perform rotation on the old parent in the
     * opposite direction if needed for the current situation
     * NOTE: in the code above, node pointer is changed to point to the old parent
     */
    if (node) {
      /* get 'new' grandparent (i.e. grandparent for old-parent (node)) */
      gp = get_grandparent(node);

      /* modify the coloring of the grandparent and parent
       * so that they still satisfy the constraints */
      node->parent->tree_col = DLRBT_BLACK;
      gp->tree_col = DLRBT_RED;

      /* if there are several nodes that all form a left chain, do a right rotation to correct
       * this (or a rotation in the opposite direction if they all form a right chain) */
      if ((node == node->parent->left) && (node->parent == gp->left)) {
        rotate_right(tree, gp);
      }
      else {  // if ((node == node->parent->right) && (node->parent == gp->right))
        rotate_left(tree, gp);
      }
    }
  }
}

/* ----- */

void BLI_dlrbTree_insert(DLRBT_Tree *tree, DLRBT_Node *node)
{
  /* sanity checks */
  if ((tree == NULL) || (node == NULL)) {
    return;
  }

  /* firstly, the node we just added should be red by default */
  node->tree_col = DLRBT_RED;

  /* start from case 1, an trek through the tail-recursive insertion checks */
  insert_check_1(tree, node);
}

/* ----- */

DLRBT_Node *BLI_dlrbTree_add(DLRBT_Tree *tree,
                             DLRBT_Comparator_FP cmp_cb,
                             DLRBT_NAlloc_FP new_cb,
                             DLRBT_NUpdate_FP update_cb,
                             void *data)
{
  DLRBT_Node *parNode, *node = NULL;
  short new_node = 0;

  /* sanity checks */
  if (tree == NULL) {
    return NULL;
  }

  /* TODO: if no comparator is supplied, try using the one supplied with the tree... */
  if (cmp_cb == NULL) {
    return NULL;
  }
  /* TODO: if no allocator is supplied, try using the one supplied with the tree... */
  if (new_cb == NULL) {
    return NULL;
  }
  /* TODO: if no updater is supplied, try using the one supplied with the tree... */

  /* try to find the nearest node to this one */
  parNode = BLI_dlrbTree_search(tree, cmp_cb, data);

  /* add new node to the BST in the 'standard way' as appropriate
   * NOTE: we do not support duplicates in our tree...
   */
  if (parNode) {
    /* check how this new node compares with the existing ones
     * NOTE: it is assumed that the values will be unit values only
     */
    switch (cmp_cb(parNode, data)) {
      case -1: /* add new node as left child */
      {
        node = new_cb(data);
        new_node = 1;

        parNode->left = node;
        node->parent = parNode;
        break;
      }
      case 1: /* add new node as right child */
      {
        node = new_cb(data);
        new_node = 1;

        parNode->right = node;
        node->parent = parNode;
        break;
      }
      default: /* update the duplicate node as appropriate */
      {
        /* Return the updated node after calling the callback. */
        node = parNode;
        if (update_cb) {
          update_cb(node, data);
        }
        break;
      }
    }
  }
  else {
    /* no nodes in the tree yet... add a new node as the root */
    node = new_cb(data);
    new_node = 1;

    tree->root = node;
  }

  /* if a new node was added, it should be tagged as red, and then balanced as appropriate */
  if (new_node) {
    /* tag this new node as being 'red' */
    node->tree_col = DLRBT_RED;

    /* perform BST balancing steps:
     * start from case 1, an trek through the tail-recursive insertion checks
     */
    insert_check_1(tree, node);
  }

  /* return the node added */
  return node;
}

/* *********************************************** */
/* Remove */

/* TODO: this hasn't been coded yet, since this functionality was not needed by the author */

/* *********************************************** */
