/* Returns the parent of |node| within |tree|,
   or a pointer to |trb_root| if |s| is the root of the tree. */
static struct trb_node *
find_parent (struct trb_table *tree, struct trb_node *node)
{
  if (node != tree->trb_root)
    {
      struct trb_node *x, *y;

      for (x = y = node; ; x = x->trb_link[0], y = y->trb_link[1])
        if (y->trb_tag[1] == TRB_THREAD)
          {
            struct trb_node *p = y->trb_link[1];
            if (p == NULL || p->trb_link[0] != node)
              {
                while (x->trb_tag[0] == TRB_CHILD)
                  x = x->trb_link[0];
                p = x->trb_link[0];
              }
            return p;
          }
        else if (x->trb_tag[0] == TRB_THREAD)
          {
            struct trb_node *p = x->trb_link[0];
            if (p == NULL || p->trb_link[1] != node)
              {
                while (y->trb_tag[1] == TRB_CHILD)
                  y = y->trb_link[1];
                p = y->trb_link[1];
              }
            return p;
          }
    }
  else
    return (struct trb_node *) &tree->trb_root;
}

/* 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 **
trb_probe (struct trb_table *tree, void *item)
{
  struct trb_node *p; /* Traverses tree looking for insertion point. */
  struct trb_node *n; /* Newly inserted node. */
  int dir;            /* Side of |p| on which |n| is inserted. */

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

  if (tree->trb_root != NULL)
    for (p = tree->trb_root; ; p = p->trb_link[dir])
      {
        int cmp = tree->trb_compare (item, p->trb_data, tree->trb_param);
        if (cmp == 0)
          return &p->trb_data;
        dir = cmp > 0;

        if (p->trb_tag[dir] == TRB_THREAD)
          break;
      }
  else
    {
      p = (struct trb_node *) &tree->trb_root;
      dir = 0;
    }

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

  tree->trb_count++;
  n->trb_data = item;
  n->trb_tag[0] = n->trb_tag[1] = TRB_THREAD;
  n->trb_link[dir] = p->trb_link[dir];
  if (tree->trb_root != NULL)
    {
      p->trb_tag[dir] = TRB_CHILD;
      n->trb_link[!dir] = p;
    }
  else
    n->trb_link[1] = NULL;
  p->trb_link[dir] = n;
  n->trb_color = TRB_RED;

  p = n;
  for (;;)
    {
      struct trb_node *f, *g;

      f = find_parent (tree, p);
      if (f == (struct trb_node *) &tree->trb_root
          || f->trb_color == TRB_BLACK)
        break;

      g = find_parent (tree, f);
      if (g == (struct trb_node *) &tree->trb_root)
        break;

      if (g->trb_link[0] == f)
        {
          struct trb_node *y = g->trb_link[1];
          if (g->trb_tag[1] == TRB_CHILD && y->trb_color == TRB_RED)
            {
              f->trb_color = y->trb_color = TRB_BLACK;
              g->trb_color = TRB_RED;
              p = g;
            }
          else
            {
              struct trb_node *c, *x;

              if (f->trb_link[0] == p)
                y = f;
              else
                {
                  x = f;
                  y = x->trb_link[1];
                  x->trb_link[1] = y->trb_link[0];
                  y->trb_link[0] = x;
                  g->trb_link[0] = y;

                  if (y->trb_tag[0] == TRB_THREAD)
                    {
                      y->trb_tag[0] = TRB_CHILD;
                      x->trb_tag[1] = TRB_THREAD;
                      x->trb_link[1] = y;
                    }
                }

              c = find_parent (tree, g);
              c->trb_link[c->trb_link[0] != g] = y;

              x = g;
              x->trb_color = TRB_RED;
              y->trb_color = TRB_BLACK;

              x->trb_link[0] = y->trb_link[1];
              y->trb_link[1] = x;

              if (y->trb_tag[1] == TRB_THREAD)
                {
                  y->trb_tag[1] = TRB_CHILD;
                  x->trb_tag[0] = TRB_THREAD;
                  x->trb_link[0] = y;
                }
              break;
            }
        }
      else
        {
          struct trb_node *y = g->trb_link[0];
          if (g->trb_tag[0] == TRB_CHILD && y->trb_color == TRB_RED)
            {
              f->trb_color = y->trb_color = TRB_BLACK;
              g->trb_color = TRB_RED;
              p = g;
            }
          else
            {
              struct trb_node *c, *x;

              if (f->trb_link[1] == p)
                y = f;
              else
                {
                  x = f;
                  y = x->trb_link[0];
                  x->trb_link[0] = y->trb_link[1];
                  y->trb_link[1] = x;
                  g->trb_link[1] = y;

                  if (y->trb_tag[1] == TRB_THREAD)
                    {
                      y->trb_tag[1] = TRB_CHILD;
                      x->trb_tag[0] = TRB_THREAD;
                      x->trb_link[0] = y;
                    }
                }

              c = find_parent (tree, g);
              c->trb_link[c->trb_link[0] != g] = y;

              x = g;
              x->trb_color = TRB_RED;
              y->trb_color = TRB_BLACK;

              x->trb_link[1] = y->trb_link[0];
              y->trb_link[0] = x;

              if (y->trb_tag[0] == TRB_THREAD)
                {
                  y->trb_tag[0] = TRB_CHILD;
                  x->trb_tag[1] = TRB_THREAD;
                  x->trb_link[1] = y;
                }
              break;
            }
        }
    }
  tree->trb_root->trb_color = TRB_BLACK;

  return &n->trb_data;
}
