/* Inserts |item| into |tree| and returns a pointer to |item|'s address.
   If a duplicate item is found in the tree,
   returns a pointer to the duplicate without inserting |item|.
   Returns |NULL| in case of memory allocation failure. */
void **
prb_probe (struct prb_table *tree, void *item)
{
  struct prb_node *p; /* Traverses tree looking for insertion point. */
  struct prb_node *q; /* Parent of |p|; node at which we are rebalancing. */
  struct prb_node *n; /* Newly inserted node. */
  int dir;            /* Side of |q| on which |n| is inserted. */

  assert (tree != NULL && item != NULL);

  for (q = NULL, p = tree->prb_root; p != NULL; q = p, p = p->prb_link[dir])
    {
      int cmp = tree->prb_compare (item, p->prb_data, tree->prb_param);
      if (cmp == 0)
        return &p->prb_data;
      dir = cmp > 0;
    }

  n = tree->prb_alloc->libavl_malloc (tree->prb_alloc, sizeof *p);
  if (n == NULL)
    return NULL;

  tree->prb_count++;
  n->prb_link[0] = n->prb_link[1] = NULL;
  n->prb_parent = q;
  n->prb_data = item;
  if (q != NULL)
    q->prb_link[dir] = n;
  else
    tree->prb_root = n;
  n->prb_color = PRB_RED;

  q = n;
  for (;;)
    {
      struct prb_node *f; /* Parent of |q|. */
      struct prb_node *g; /* Grandparent of |q|. */

      f = q->prb_parent;
      if (f == NULL || f->prb_color == PRB_BLACK)
        break;

      g = f->prb_parent;
      if (g == NULL)
        break;

      if (g->prb_link[0] == f)
        {
          struct prb_node *y = g->prb_link[1];
          if (y != NULL && y->prb_color == PRB_RED)
            {
              f->prb_color = y->prb_color = PRB_BLACK;
              g->prb_color = PRB_RED;
              q = g;
            }
          else
            {
              struct prb_node *h; /* Great-grandparent of |q|. */

              h = g->prb_parent;
              if (h == NULL)
                h = (struct prb_node *) &tree->prb_root;

              if (f->prb_link[1] == q)
                {
                  f->prb_link[1] = q->prb_link[0];
                  q->prb_link[0] = f;
                  g->prb_link[0] = q;
                  f->prb_parent = q;
                  if (f->prb_link[1] != NULL)
                    f->prb_link[1]->prb_parent = f;

                  f = q;
                }

              g->prb_color = PRB_RED;
              f->prb_color = PRB_BLACK;

              g->prb_link[0] = f->prb_link[1];
              f->prb_link[1] = g;
              h->prb_link[h->prb_link[0] != g] = f;

              f->prb_parent = g->prb_parent;
              g->prb_parent = f;
              if (g->prb_link[0] != NULL)
                g->prb_link[0]->prb_parent = g;
              break;
            }
        }
      else
        {
          struct prb_node *y = g->prb_link[0];
          if (y != NULL && y->prb_color == PRB_RED)
            {
              f->prb_color = y->prb_color = PRB_BLACK;
              g->prb_color = PRB_RED;
              q = g;
            }
          else
            {
              struct prb_node *h; /* Great-grandparent of |q|. */

              h = g->prb_parent;
              if (h == NULL)
                h = (struct prb_node *) &tree->prb_root;

              if (f->prb_link[0] == q)
                {
                  f->prb_link[0] = q->prb_link[1];
                  q->prb_link[1] = f;
                  g->prb_link[1] = q;
                  f->prb_parent = q;
                  if (f->prb_link[0] != NULL)
                    f->prb_link[0]->prb_parent = f;

                  f = q;
                }

              g->prb_color = PRB_RED;
              f->prb_color = PRB_BLACK;

              g->prb_link[1] = f->prb_link[0];
              f->prb_link[0] = g;
              h->prb_link[h->prb_link[0] != g] = f;

              f->prb_parent = g->prb_parent;
              g->prb_parent = f;
              if (g->prb_link[1] != NULL)
                g->prb_link[1]->prb_parent = g;
              break;
            }
        }
    }
  tree->prb_root->prb_color = PRB_BLACK;

  return &n->prb_data;
}

