/* 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;
}

/* Deletes from |tree| and returns an item matching |item|.
   Returns a null pointer if no matching item found. */
void *
trb_delete (struct trb_table *tree, const void *item)
{
  struct trb_node *p; /* Node to delete. */
  struct trb_node *q; /* Parent of |p|. */

  struct trb_node *x; /* Node we might want to recolor red (maybe |NULL|). */
  struct trb_node *f; /* Parent of |x|. */
  struct trb_node *g; /* Parent of |f|. */

  int dir, cmp;

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

  if (tree->trb_root == NULL)
    return NULL;

  q = (struct trb_node *) &tree->trb_root;
  p = tree->trb_root;
  dir = 0;
  for (;;)
    {
      cmp = tree->trb_compare (item, p->trb_data, tree->trb_param);
      if (cmp == 0)
        break;
      dir = cmp > 0;

      q = p;
      if (p->trb_tag[dir] == TRB_THREAD)
        return NULL;
      p = p->trb_link[dir];
    }
  item = p->trb_data;

  if (p->trb_tag[1] == TRB_THREAD)
    {
      if (p->trb_tag[0] == TRB_CHILD)
        {
          struct trb_node *t = p->trb_link[0];
          while (t->trb_tag[1] == TRB_CHILD)
            t = t->trb_link[1];
          t->trb_link[1] = p->trb_link[1];
          x = q->trb_link[dir] = p->trb_link[0];
        }
      else
        {
          q->trb_link[dir] = p->trb_link[dir];
          if (q != (struct trb_node *) &tree->trb_root)
            q->trb_tag[dir] = TRB_THREAD;
          x = NULL;
        }
      f = q;
    }
  else
    {
      enum trb_color t;
      struct trb_node *r = p->trb_link[1];

      if (r->trb_tag[0] == TRB_THREAD)
        {
          r->trb_link[0] = p->trb_link[0];
          r->trb_tag[0] = p->trb_tag[0];
          if (r->trb_tag[0] == TRB_CHILD)
            {
              struct trb_node *t = r->trb_link[0];
              while (t->trb_tag[1] == TRB_CHILD)
                t = t->trb_link[1];
              t->trb_link[1] = r;
            }
          q->trb_link[dir] = r;
          x = r->trb_tag[1] == TRB_CHILD ? r->trb_link[1] : NULL;
          t = r->trb_color;
          r->trb_color = p->trb_color;
          p->trb_color = t;
          f = r;
          dir = 1;
        }
      else
        {
          struct trb_node *s;

          for (;;)
            {
              s = r->trb_link[0];
              if (s->trb_tag[0] == TRB_THREAD)
                break;

              r = s;
            }

          if (s->trb_tag[1] == TRB_CHILD)
            x = r->trb_link[0] = s->trb_link[1];
          else
            {
              r->trb_link[0] = s;
              r->trb_tag[0] = TRB_THREAD;
              x = NULL;
            }

          s->trb_link[0] = p->trb_link[0];
          if (p->trb_tag[0] == TRB_CHILD)
            {
              struct trb_node *t = p->trb_link[0];
              while (t->trb_tag[1] == TRB_CHILD)
                t = t->trb_link[1];
              t->trb_link[1] = s;

              s->trb_tag[0] = TRB_CHILD;
            }

          s->trb_link[1] = p->trb_link[1];
          s->trb_tag[1] = TRB_CHILD;

          t = s->trb_color;
          s->trb_color = p->trb_color;
          p->trb_color = t;

          q->trb_link[dir] = s;
          f = r;
          dir = 0;
        }
    }

  if (p->trb_color == TRB_BLACK)
    {
      for (;;)
        {
          if (x != NULL && x->trb_color == TRB_RED)
            {
              x->trb_color = TRB_BLACK;
              break;
            }
          if (f == (struct trb_node *) &tree->trb_root)
            break;

          g = find_parent (tree, f);

          if (dir == 0)
            {
              struct trb_node *w = f->trb_link[1];

              if (w->trb_color == TRB_RED)
                {
                  w->trb_color = TRB_BLACK;
                  f->trb_color = TRB_RED;

                  f->trb_link[1] = w->trb_link[0];
                  w->trb_link[0] = f;
                  g->trb_link[g->trb_link[0] != f] = w;

                  g = w;
                  w = f->trb_link[1];
                }

              if ((w->trb_tag[0] == TRB_THREAD
                   || w->trb_link[0]->trb_color == TRB_BLACK)
                  && (w->trb_tag[1] == TRB_THREAD
                      || w->trb_link[1]->trb_color == TRB_BLACK))
                w->trb_color = TRB_RED;
              else
                {
                  if (w->trb_tag[1] == TRB_THREAD
                      || w->trb_link[1]->trb_color == TRB_BLACK)
                    {
                      struct trb_node *y = w->trb_link[0];
                      y->trb_color = TRB_BLACK;
                      w->trb_color = TRB_RED;
                      w->trb_link[0] = y->trb_link[1];
                      y->trb_link[1] = w;
                      w = f->trb_link[1] = y;

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

                  w->trb_color = f->trb_color;
                  f->trb_color = TRB_BLACK;
                  w->trb_link[1]->trb_color = TRB_BLACK;

                  f->trb_link[1] = w->trb_link[0];
                  w->trb_link[0] = f;
                  g->trb_link[g->trb_link[0] != f] = w;

                  if (w->trb_tag[0] == TRB_THREAD)
                    {
                      w->trb_tag[0] = TRB_CHILD;
                      f->trb_tag[1] = TRB_THREAD;
                      f->trb_link[1] = w;
                    }
                  break;
                }
            }
          else
            {
              struct trb_node *w = f->trb_link[0];

              if (w->trb_color == TRB_RED)
                {
                  w->trb_color = TRB_BLACK;
                  f->trb_color = TRB_RED;

                  f->trb_link[0] = w->trb_link[1];
                  w->trb_link[1] = f;
                  g->trb_link[g->trb_link[0] != f] = w;

                  g = w;
                  w = f->trb_link[0];
                }

              if ((w->trb_tag[0] == TRB_THREAD
                   || w->trb_link[0]->trb_color == TRB_BLACK)
                  && (w->trb_tag[1] == TRB_THREAD
                      || w->trb_link[1]->trb_color == TRB_BLACK))
                w->trb_color = TRB_RED;
              else
                {
                  if (w->trb_tag[0] == TRB_THREAD
                      || w->trb_link[0]->trb_color == TRB_BLACK)
                    {
                      struct trb_node *y = w->trb_link[1];
                      y->trb_color = TRB_BLACK;
                      w->trb_color = TRB_RED;
                      w->trb_link[1] = y->trb_link[0];
                      y->trb_link[0] = w;
                      w = f->trb_link[0] = y;

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

                  w->trb_color = f->trb_color;
                  f->trb_color = TRB_BLACK;
                  w->trb_link[0]->trb_color = TRB_BLACK;

                  f->trb_link[0] = w->trb_link[1];
                  w->trb_link[1] = f;
                  g->trb_link[g->trb_link[0] != f] = w;

                  if (w->trb_tag[1] == TRB_THREAD)
                    {
                      w->trb_tag[1] = TRB_CHILD;
                      f->trb_tag[0] = TRB_THREAD;
                      f->trb_link[0] = w;
                    }
                  break;
                }
            }

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

          dir = f->trb_link[0] != x;
        }
    }

  tree->trb_alloc->libavl_free (tree->trb_alloc, p);
  tree->trb_count--;
  return (void *) item;
}

