%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1995-2000, 2003-2007, 2011 The University of Melbourne.
% Copyright (C) 2014-2019, 2021, 2023 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: rbtree.m.
% Main author: petdr.
% Stability: medium.
%
% Contains an implementation of red black trees.
%
% *** Exit conditions of main predicates ***
% insert:
%   fails if key already in tree.
% update:
%   changes value of key already in tree.  fails if key doesn't exist.
% transform_value:
%   looks up an existing value in the tree, applies a transformation to the
%   value and then updates the value.  fails if the key doesn't exist.
% set:
%   inserts or updates. Never fails.
%
% insert_duplicate:
%   inserts duplicate keys into the tree, never fails.  Search doesn't
%   yet support looking for duplicates.
%
% delete:
%   deletes a node from the tree if it exists.
% remove:
%   fails if node to remove doesn't exist in the tree.
%
% lookup:
%   Throws an exception if key looked up doesn't exist.
% search:
%   Fails if key looked up doesn't exist.
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- module rbtree.
:- interface.

:- import_module assoc_list.
:- import_module list.

%---------------------------------------------------------------------------%

:- type rbtree(Key, Value).

    % Initialise the data structure.
    %
:- func init = rbtree(K, V).
:- pred init(rbtree(K, V)::uo) is det.

    % Check whether a tree is empty.
    %
:- pred is_empty(rbtree(K, V)::in) is semidet.

    % Initialise an rbtree containing the given key-value pair.
    %
:- func singleton(K, V) = rbtree(K, V).

    % Inserts a new key-value pair into the tree.
    % Fails if key already in the tree.
    %
:- pred insert(K::in, V::in, rbtree(K, V)::in, rbtree(K, V)::out) is semidet.

    % Updates the value associated with a key.
    % Fails if the key does not exist.
    %
:- pred update(K::in, V::in, rbtree(K, V)::in, rbtree(K, V)::out) is semidet.

    % Update the value at the given key by applying the supplied
    % transformation to it.  Fails if the key is not found.  This is faster
    % than first searching for the value and then updating it.
    %
:- pred transform_value(pred(V, V)::in(pred(in, out) is det), K::in,
    rbtree(K, V)::in, rbtree(K, V)::out) is semidet.

    % Sets a value regardless of whether key exists or not.
    %
:- func set(rbtree(K, V), K, V) = rbtree(K, V).
:- pred set(K::in, V::in, rbtree(K, V)::in, rbtree(K, V)::out) is det.

    % Insert a duplicate key into the tree.
    %
:- func insert_duplicate(rbtree(K, V), K, V) = rbtree(K, V).
:- pred insert_duplicate(K::in, V::in,
    rbtree(K, V)::in, rbtree(K, V)::out) is det.

:- pred member(rbtree(K, V)::in, K::out, V::out) is nondet.

    % Search for a key-value pair using the key.
    % Fails if the key does not exist.
    %
:- pred search(rbtree(K, V)::in, K::in, V::out) is semidet.

    % Lookup the value associated with a key.
    % Throws an exception if the key does not exist.
    %
:- func lookup(rbtree(K, V), K) = V.
:- pred lookup(rbtree(K, V)::in, K::in, V::out) is det.

    % Search for a key-value pair using the key.  If there is no entry
    % for the given key, returns the pair for the next lower key instead.
    % Fails if there is no key with the given or lower value.
    %
:- pred lower_bound_search(rbtree(K, V)::in, K::in, K::out, V::out)
    is semidet.

    % Search for a key-value pair using the key.  If there is no entry
    % for the given key, returns the pair for the next lower key instead.
    % Throws an exception if there is no key with the given or lower value.
    %
:- pred lower_bound_lookup(rbtree(K, V)::in, K::in, K::out, V::out) is det.

    % Search for a key-value pair using the key.  If there is no entry
    % for the given key, returns the pair for the next higher key instead.
    % Fails if there is no key with the given or higher value.
    %
:- pred upper_bound_search(rbtree(K, V)::in, K::in, K::out, V::out)
    is semidet.

    % Search for a key-value pair using the key.  If there is no entry
    % for the given key, returns the pair for the next higher key instead.
    % Throws an exception if there is no key with the given or higher value.
    %
:- pred upper_bound_lookup(rbtree(K, V)::in, K::in, K::out, V::out) is det.

    % Delete the key-value pair associated with a key.
    % Does nothing if the key does not exist.
    %
:- func delete(rbtree(K, V), K) = rbtree(K, V).
:- pred delete(K::in, rbtree(K, V)::in, rbtree(K, V)::out) is det.

    % Remove the key-value pair associated with a key.
    % Fails if the key does not exist.
    %
:- pred remove(K::in, V::out, rbtree(K, V)::in, rbtree(K, V)::out)
    is semidet.

    % Deletes the node with the minimum key from the tree,
    % and returns the key and value fields.
    %
:- pred remove_smallest(K::out, V::out,
    rbtree(K, V)::in, rbtree(K, V)::out) is semidet.

    % Deletes the node with the maximum key from the tree,
    % and returns the key and value fields.
    %
:- pred remove_largest(K::out, V::out,
    rbtree(K, V)::in, rbtree(K, V)::out) is semidet.

    % Returns an in-order list of all the keys in the rbtree.
    %
:- func keys(rbtree(K, V)) = list(K).
:- pred keys(rbtree(K, V)::in, list(K)::out) is det.

    % Returns a list of values such that the keys associated with the
    % values are in-order.
    %
:- func values(rbtree(K, V)) = list(V).
:- pred values(rbtree(K, V)::in, list(V)::out) is det.

    % Count the number of elements in the tree.
    %
:- func count(rbtree(K, V)) = int.
:- pred count(rbtree(K, V)::in, int::out) is det.

:- func assoc_list_to_rbtree(assoc_list(K, V)) = rbtree(K, V).
:- pred assoc_list_to_rbtree(assoc_list(K, V)::in, rbtree(K, V)::out) is det.

:- func from_assoc_list(assoc_list(K, V)) = rbtree(K, V).

:- func rbtree_to_assoc_list(rbtree(K, V)) = assoc_list(K, V).
:- pred rbtree_to_assoc_list(rbtree(K, V)::in, assoc_list(K, V)::out)
    is det.

:- func to_assoc_list(rbtree(K, V)) = assoc_list(K, V).

:- func foldl(func(K, V, T) = T, rbtree(K, V), T) = T.
:- pred foldl(pred(K, V, T, T), rbtree(K, V), T, T).
:- mode foldl(in(pred(in, in, in, out) is det), in, in, out) is det.
:- mode foldl(in(pred(in, in, mdi, muo) is det), in, mdi, muo) is det.
:- mode foldl(in(pred(in, in, di, uo) is det), in, di, uo) is det.
:- mode foldl(in(pred(in, in, in, out) is semidet), in, in, out)
    is semidet.
:- mode foldl(in(pred(in, in, mdi, muo) is semidet), in, mdi, muo)
    is semidet.
:- mode foldl(in(pred(in, in, di, uo) is semidet), in, di, uo)
    is semidet.

:- pred foldl2(pred(K, V, T, T, U, U), rbtree(K, V), T, T, U, U).
:- mode foldl2(in(pred(in, in, in, out, in, out) is det),
    in, in, out, in, out) is det.
:- mode foldl2(in(pred(in, in, in, out, mdi, muo) is det),
    in, in, out, mdi, muo) is det.
:- mode foldl2(in(pred(in, in, in, out, di, uo) is det),
    in, in, out, di, uo) is det.
:- mode foldl2(in(pred(in, in, di, uo, di, uo) is det),
    in, di, uo, di, uo) is det.
:- mode foldl2(in(pred(in, in, in, out, in, out) is semidet),
    in, in, out, in, out) is semidet.
:- mode foldl2(in(pred(in, in, in, out, mdi, muo) is semidet),
    in, in, out, mdi, muo) is semidet.
:- mode foldl2(in(pred(in, in, in, out, di, uo) is semidet),
    in, in, out, di, uo) is semidet.

:- pred foldl3(pred(K, V, T, T, U, U, W, W), rbtree(K, V),
    T, T, U, U, W, W).
:- mode foldl3(in(pred(in, in, in, out, in, out, in, out) is det),
    in, in, out, in, out, in, out) is det.
:- mode foldl3(in(pred(in, in, in, out, in, out, in, out) is semidet),
    in, in, out, in, out, in, out) is semidet.
:- mode foldl3(in(pred(in, in, in, out, in, out, di, uo) is det),
    in, in, out, in, out, di, uo) is det.
:- mode foldl3(in(pred(in, in, in, out, di, uo, di, uo) is det),
    in, in, out, di, uo, di, uo) is det.
:- mode foldl3(in(pred(in, in, di, uo, di, uo, di, uo) is det),
    in, di, uo, di, uo, di, uo) is det.

:- pred foldl_values(pred(V, A, A), rbtree(K, V), A, A).
:- mode foldl_values(in(pred(in, in, out) is det), in, in, out) is det.
:- mode foldl_values(in(pred(in, mdi, muo) is det), in, mdi, muo) is det.
:- mode foldl_values(in(pred(in, di, uo) is det), in, di, uo) is det.
:- mode foldl_values(in(pred(in, in, out) is semidet), in, in, out) is semidet.
:- mode foldl_values(in(pred(in, mdi, muo) is semidet), in, mdi, muo)
    is semidet.
:- mode foldl_values(in(pred(in, di, uo) is semidet), in, di, uo) is semidet.

:- pred foldl2_values(pred(V, A, A, B, B), rbtree(K, V), A, A, B, B).
:- mode foldl2_values(in(pred(in, in, out, in, out) is det), in, in, out,
    in, out) is det.
:- mode foldl2_values(in(pred(in, in, out, mdi, muo) is det), in, in, out,
    mdi, muo) is det.
:- mode foldl2_values(in(pred(in, in, out, di, uo) is det), in, in, out,
    di, uo) is det.
:- mode foldl2_values(in(pred(in, in, out, in, out) is semidet), in, in, out,
    in, out) is semidet.
:- mode foldl2_values(in(pred(in, in, out, mdi, muo) is semidet), in, in, out,
    mdi, muo) is semidet.
:- mode foldl2_values(in(pred(in, in, out, di, uo) is semidet), in, in, out,
    di, uo) is semidet.

:- func foldr(func(K, V, A) = A, rbtree(K, V), A) = A.
:- pred foldr(pred(K, V, A, A), rbtree(K, V), A, A).
:- mode foldr(in(pred(in, in, in, out) is det), in, in, out) is det.
:- mode foldr(in(pred(in, in, mdi, muo) is det), in, mdi, muo) is det.
:- mode foldr(in(pred(in, in, di, uo) is det), in, di, uo) is det.
:- mode foldr(in(pred(in, in, in, out) is semidet), in, in, out) is semidet.
:- mode foldr(in(pred(in, in, mdi, muo) is semidet), in, mdi, muo) is semidet.
:- mode foldr(in(pred(in, in, di, uo) is semidet), in, di, uo) is semidet.

:- pred foldr2(pred(K, V, A, A, B, B), rbtree(K, V), A, A, B, B).
:- mode foldr2(in(pred(in, in, in, out, in, out) is det),
    in, in, out, in, out) is det.
:- mode foldr2(in(pred(in, in, in, out, mdi, muo) is det),
    in, in, out, mdi, muo) is det.
:- mode foldr2(in(pred(in, in, in, out, di, uo) is det),
    in, in, out, di, uo) is det.
:- mode foldr2(in(pred(in, in, di, uo, di, uo) is det),
    in, di, uo, di, uo) is det.
:- mode foldr2(in(pred(in, in, in, out, in, out) is semidet),
    in, in, out, in, out) is semidet.
:- mode foldr2(in(pred(in, in, in, out, mdi, muo) is semidet),
    in, in, out, mdi, muo) is semidet.
:- mode foldr2(in(pred(in, in, in, out, di, uo) is semidet),
    in, in, out, di, uo) is semidet.

:- pred foldr_values(pred(V, A, A), rbtree(K, V), A, A).
:- mode foldr_values(in(pred(in, in, out) is det), in, in, out) is det.
:- mode foldr_values(in(pred(in, mdi, muo) is det), in, mdi, muo) is det.
:- mode foldr_values(in(pred(in, di, uo) is det), in, di, uo) is det.
:- mode foldr_values(in(pred(in, in, out) is semidet), in, in, out) is semidet.
:- mode foldr_values(in(pred(in, mdi, muo) is semidet), in, mdi, muo)
    is semidet.
:- mode foldr_values(in(pred(in, di, uo) is semidet), in, di, uo) is semidet.

:- func map_values(func(K, V) = W, rbtree(K, V)) = rbtree(K, W).
:- pred map_values(pred(K, V, W), rbtree(K, V), rbtree(K, W)).
:- mode map_values(in(pred(in, in, out) is det), in, out) is det.
:- mode map_values(in(pred(in, in, out) is semidet), in, out) is semidet.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- implementation.

:- import_module bool.
:- import_module int.
:- import_module maybe.
:- import_module pair.
:- import_module require.

%---------------------------------------------------------------------------%

% Special conditions that must be satisfied by Red-Black trees:
%   * The root node must be black.
%   * There can never be 2 red nodes in a row.

:- type rbtree(K, V)
    --->    empty
    ;       red(K, V, rbtree(K, V), rbtree(K, V))
    ;       black(K, V, rbtree(K, V), rbtree(K, V)).

%---------------------------------------------------------------------------%

init = RBT :-
    rbtree.init(RBT).

init(empty).

is_empty(Tree) :-
    Tree = empty.

singleton(K, V) = black(K, V, empty, empty).

%---------------------------------------------------------------------------%

insert(K, V, empty, black(K, V, empty, empty)).
insert(_K, _V, red(_, _, _, _), _Tree) :-
    error($pred, "root node cannot be red!").
insert(K, V, black(K0, V0, L0, R0), Tree) :-
    rbtree.insert_2(black(K0, V0, L0, R0), K, V, Tree0),
    % Ensure that the root of the tree is black.
    (
        Tree0 = black(_, _, _, _),
        Tree = Tree0
    ;
        Tree0 = red(K1, V1, L1, R1),
        Tree = black(K1, V1, L1, R1)
    ;
        Tree0 = empty,
        error($pred, "new tree is empty")
    ).

    % insert_2:
    %
    % We traverse down the tree until we find the correct spot to insert.
    % Then as we fall back out of the recursions we look for possible
    % rotation cases.
    %
:- pred insert_2(rbtree(K, V)::in, K::in, V::in, rbtree(K, V)::out)
    is semidet.

% Red node always inserted at the bottom as it will be rotated into the
% correct place as we move back up the tree.
insert_2(empty, K, V, red(K, V, empty, empty)).
insert_2(red(K0, V0, L0, R0), K, V, Tree) :-
    % Work out which side of the rbtree to insert.
    compare(Result, K, K0),
    (
        Result = (<),
        rbtree.insert_2(L0, K, V, NewL),
        Tree = red(K0, V0, NewL, R0)
    ;
        Result = (>),
        rbtree.insert_2(R0, K, V, NewR),
        Tree = red(K0, V0, L0, NewR)
    ;
        Result = (=),
        fail
    ).
% Only ever need to look for a possible rotation if we are in a black node.
% The rotation criteria is when there is 2 red nodes in a row.
insert_2(black(K0, V0, L0, R0), K, V, Tree) :-
    ( if
        L0 = red(LK, LV, LL, LR),
        R0 = red(RK, RV, RL, RR)
    then
        % On the way down the rbtree we split any 4-nodes we find.
        % This converts the current node to a red node, so we call
        % the red node version of rbtree.insert_2/4.
        L = black(LK, LV, LL, LR),
        R = black(RK, RV, RL, RR),
        Tree0 = red(K0, V0, L, R),
        rbtree.insert_2(Tree0, K, V, Tree)
    else
        % Work out which side of the rbtree to insert.
        compare(Result, K, K0),
        (
            Result = (<),
            rbtree.insert_2(L0, K, V, NewL),
            ( if
                % Only need to start looking for a rotation case
                % if the current node is black(known), and its
                % new child red.
                NewL = red(LK, LV, LL, LR)
            then
                % Check to see if a grandchild is red and if so rotate.
                ( if LL = red(_LLK, _LLV, _LLL, _LLR) then
                    TreeR = red(K0, V0, LR, R0),
                    Tree = black(LK, LV, LL, TreeR)
                else if LR = red(LRK, LRV, LRL, LRR) then
                    TreeL = red(LK, LV, LL, LRL),
                    TreeR = red(K0, V0, LRR, R0),
                    Tree = black(LRK, LRV, TreeL, TreeR)
                else
                    Tree = black(K0, V0, NewL, R0)
                )
            else
                Tree = black(K0, V0, NewL, R0)
            )
        ;
            Result = (>),
            rbtree.insert_2(R0, K, V, NewR),
            ( if
                % Only need to start looking for a rotation case
                % if the current node is black(known), and its
                % new child red.
                NewR = red(RK, RV, RL, RR)
            then
                % Check to see if a grandchild is red and if so rotate.
                ( if RL = red(RLK, RLV, RLL, RLR) then
                    TreeL = red(K0, V0, L0, RLL),
                    TreeR = red(RK, RV, RLR, RR),
                    Tree = black(RLK, RLV, TreeL, TreeR)
                else if RR = red(_RRK, _RRV, _RRL, _RRR) then
                    TreeL = red(K0, V0, L0, RL),
                    Tree = black(RK, RV, TreeL, RR)
                else
                    Tree = black(K0, V0, L0, NewR)
                )
            else
                Tree = black(K0, V0, L0, NewR)
            )
        ;
            Result = (=),
            fail
        )
    ).

%---------------------------------------------------------------------------%

update(_K, _V, empty, _T) :-
    fail.
update(K, V, red(K0, V0, L, R), Tree) :-
    compare(Result, K, K0),
    (
        Result = (=),
        Tree = red(K, V, L, R)
    ;
        Result = (<),
        rbtree.update(K, V, L, NewL),
        Tree = red(K0, V0, NewL, R)
    ;
        Result = (>),
        rbtree.update(K, V, R, NewR),
        Tree = red(K0, V0, L, NewR)
    ).
update(K, V, black(K0, V0, L, R), Tree) :-
    compare(Result, K, K0),
    (
        Result = (=),
        Tree = black(K, V, L, R)
    ;
        Result = (<),
        rbtree.update(K, V, L, NewL),
        Tree = black(K0, V0, NewL, R)
    ;
        Result = (>),
        rbtree.update(K, V, R, NewR),
        Tree = black(K0, V0, L, NewR)
    ).

%---------------------------------------------------------------------------%

transform_value(_P, _K, empty, _T) :-
    fail.
transform_value(P, K, red(K0, V0, L, R), Tree) :-
    compare(Result, K, K0),
    (
        Result = (=),
        P(V0, NewV),
        Tree = red(K, NewV, L, R)
    ;
        Result = (<),
        rbtree.transform_value(P, K, L, NewL),
        Tree = red(K0, V0, NewL, R)
    ;
        Result = (>),
        rbtree.transform_value(P, K, R, NewR),
        Tree = red(K0, V0, L, NewR)
    ).
transform_value(P, K, black(K0, V0, L, R), Tree) :-
    compare(Result, K, K0),
    (
        Result = (=),
        P(V0, NewV),
        Tree = black(K, NewV, L, R)
    ;
        Result = (<),
        rbtree.transform_value(P, K, L, NewL),
        Tree = black(K0, V0, NewL, R)
    ;
        Result = (>),
        rbtree.transform_value(P, K, R, NewR),
        Tree = black(K0, V0, L, NewR)
    ).

%---------------------------------------------------------------------------%

set(!.RBT, K, V) = !:RBT :-
    rbtree.set(K, V, !RBT).

set(K, V, empty, black(K, V, empty, empty)).
set(_K, _V, red(_, _, _, _), _Tree) :-
    error($pred, "root node cannot be red!").
set(K, V, black(K0, V0, L0, R0), Tree) :-
    rbtree.set_2(black(K0, V0, L0, R0), K, V, Tree0),
    % Ensure that the root of the tree is black.
    (
        Tree0 = black(_, _, _, _),
        Tree = Tree0
    ;
        Tree0 = red(K1, V1, L1, R1),
        Tree = black(K1, V1, L1, R1)
    ;
        Tree0 = empty,
        error($pred, "new tree is empty")
    ).

    % set_2:
    %
    % We traverse down the tree until we find the correct spot to insert, or
    % update. Then as we fall back out of the recursions we look for possible
    % rotation cases.
    %
:- pred set_2(rbtree(K, V), K, V, rbtree(K, V)).
:- mode set_2(di, di, di, uo) is det.
:- mode set_2(in, in, in, out) is det.

% Red node always inserted at the bottom as it will be rotated into the
% correct place as we move back up the tree.
set_2(empty, K, V, red(K, V, empty, empty)).
set_2(red(K0, V0, L0, R0), K, V, Tree) :-
    % Work out which side of the rbtree to insert.
    compare(Result, K, K0),
    (
        Result = (=),
        Tree = red(K, V, L0, R0)
    ;
        Result = (<),
        rbtree.set_2(L0, K, V, NewL),
        Tree = red(K0, V0, NewL, R0)
    ;
        Result = (>),
        rbtree.set_2(R0, K, V, NewR),
        Tree = red(K0, V0, L0, NewR)
    ).
set_2(black(K0, V0, L0, R0), K, V, Tree) :-
    ( if
        L0 = red(LK, LV, LL, LR),
        R0 = red(RK, RV, RL, RR)
    then
        % On the way down the rbtree we split any 4-nodes we find.
        L = black(LK, LV, LL, LR),
        R = black(RK, RV, RL, RR),
        Tree0 = red(K0, V0, L, R),
        rbtree.set_2(Tree0, K, V, Tree)
    else
        % Work out which side of the rbtree to insert.
        compare(Result, K, K0),
        (
            Result = (=),
            Tree = black(K, V, L0, R0)
        ;
            Result = (<),
            rbtree.set_2(L0, K, V, NewL),
            ( if
                % Only need to start looking for a rotation case
                % if the current node is black(known), and its
                % new child red.
                NewL = red(LK, LV, LL, LR)
            then
                % Check to see if a grandchild is red and if so rotate.
                ( if LL = red(_LLK, _LLV, _LLL, _LLR) then
                    TreeR = red(K0, V0, LR, R0),
                    Tree = black(LK, LV, LL, TreeR)
                else if LR = red(LRK, LRV, LRL, LRR) then
                    TreeL = red(LK, LV, LL, LRL),
                    TreeR = red(K0, V0, LRR, R0),
                    Tree = black(LRK, LRV, TreeL, TreeR)
                else
                    % NewL2 == NewL, but this hack
                    % needed for unique modes to work.
                    NewL2 = red(LK, LV, LL, LR),
                    Tree = black(K0, V0, NewL2, R0)
                )
            else
                Tree = black(K0, V0, NewL, R0)
            )
        ;
            Result = (>),
            rbtree.set_2(R0, K, V, NewR),
            ( if
                % Only need to start looking for a rotation case
                % if the current node is black(known), and its
                % new child red.
                NewR = red(RK, RV, RL, RR)
            then
                % Check to see if a grandchild is red and if so rotate.
                ( if RL = red(RLK, RLV, RLL, RLR) then
                    TreeL = red(K0, V0, L0, RLL),
                    TreeR = red(RK, RV, RLR, RR),
                    Tree = black(RLK, RLV, TreeL, TreeR)
                else if RR = red(_RRK, _RRV, _RRL, _RRR) then
                    TreeL = red(K0, V0, L0, RL),
                    Tree = black(RK, RV, TreeL, RR)
                else
                    % NewR2 == NewR, but this hack
                    % needed for unique modes to work.
                    NewR2 = red(RK, RV, RL, RR),
                    Tree = black(K0, V0, L0, NewR2)
                )
            else
                Tree = black(K0, V0, L0, NewR)
            )
        )
    ).

%---------------------------------------------------------------------------%

insert_duplicate(!.RBT, K, V) = !:RBT :-
    rbtree.insert_duplicate(K, V, !RBT).

insert_duplicate(K, V, empty, black(K, V, empty, empty)).
insert_duplicate(_K, _V, red(_, _, _, _), _Tree) :-
    error($pred, "root node cannot be red!").
insert_duplicate(K, V, black(K0, V0, L0, R0), Tree) :-
    rbtree.insert_duplicate_2(black(K0, V0, L0, R0), K, V, Tree0),
    % Ensure that the root of the tree is black.
    ( if Tree0 = red(K1, V1, L1, R1) then
        Tree = black(K1, V1, L1, R1)
    else
        Tree = Tree0
    ).

    % insert_duplicate_2:
    %
    % We traverse down the tree until we find the correct spot to insert.
    % Then as we fall back out of the recursions we look for possible
    % rotation cases.
    %
:- pred insert_duplicate_2(rbtree(K, V), K, V, rbtree(K, V)).
:- mode insert_duplicate_2(in, in, in, out) is det.

% Red node always inserted at the bottom as it will be rotated into the
% correct place as we move back up the tree.
insert_duplicate_2(empty, K, V, red(K, V, empty, empty)).
insert_duplicate_2(red(K0, V0, L0, R0), K, V, Tree) :-
    % Work out which side of the rbtree to insert.
    compare(Result, K, K0),
    (
        Result = (<),
        rbtree.insert_duplicate_2(L0, K, V, NewL),
        Tree = red(K0, V0, NewL, R0)
    ;
        Result = (>),
        rbtree.insert_duplicate_2(R0, K, V, NewR),
        Tree = red(K0, V0, L0, NewR)
    ;
        Result = (=),
        rbtree.insert_duplicate_2(L0, K, V, NewL),
        Tree = red(K0, V0, NewL, R0)
    ).
% Only ever need to look for a possible rotation if we are in a black node.
% The rotation criteria is when there is 2 red nodes in a row.
insert_duplicate_2(black(K0, V0, L0, R0), K, V, Tree) :-
    ( if
        L0 = red(LK, LV, LL, LR),
        R0 = red(RK, RV, RL, RR)
    then
        % On the way down the rbtree we split any 4-nodes we find.
        % This converts the current node to a red node, so we call
        % the red node version of rbtree.insert_duplicate_2/4.
        L = black(LK, LV, LL, LR),
        R = black(RK, RV, RL, RR),
        Tree0 = red(K0, V0, L, R),
        rbtree.insert_duplicate_2(Tree0, K, V, Tree)
    else
        % Work out which side of the rbtree to insert.
        compare(Result, K, K0),
        (
            Result = (<),
            rbtree.insert_duplicate_2(L0, K, V, NewL),
            ( if
                % Only need to start looking for a rotation case
                % if the current node is black(known), and its
                % new child red.
                NewL = red(LK, LV, LL, LR)
            then
                % Check to see if a grandchild is red and if so rotate.
                ( if LL = red(_LLK1, _LLV1, _LLL1, _LLR1) then
                    TreeR = red(K0, V0, LR, R0),
                    Tree = black(LK, LV, LL, TreeR)
                else if LR = red(LRK, LRV, LRL, LRR) then
                    TreeL = red(LK, LV, LL, LRL),
                    TreeR = red(K0, V0, LRR, R0),
                    Tree = black(LRK, LRV, TreeL, TreeR)
                else
                    Tree = black(K0, V0, NewL, R0)
                )
            else
                Tree = black(K0, V0, NewL, R0)
            )
        ;
            Result = (>),
            rbtree.insert_duplicate_2(R0, K, V, NewR),
            ( if
                % Only need to start looking for a rotation case
                % if the current node is black(known), and its
                % new child red.
                NewR = red(RK, RV, RL, RR)
            then
                % Check to see if a grandchild is red and if so rotate.
                ( if RL = red(RLK, RLV, RLL, RLR) then
                    TreeL = red(K0, V0, L0, RLL),
                    TreeR = red(RK, RV, RLR, RR),
                    Tree = black(RLK, RLV, TreeL, TreeR)
                else if RR = red(_RRK, _RRV, _RRL, _RRR) then
                    TreeL = red(K0, V0, L0, RL),
                    Tree = black(RK, RV, TreeL, RR)
                else
                    Tree = black(K0, V0, L0, NewR)
                )
            else
                Tree = black(K0, V0, L0, NewR)
            )
        ;
            Result = (=),
            rbtree.insert_duplicate_2(L0, K, V, NewL),
            ( if
                % Only need to start looking for a rotation case if the
                % current node is black(known), and its new child red.
                NewL = red(LK, LV, LL, LR)
            then
                % Check to see if a grandchild is red and if so rotate.
                ( if LL = red(_LLK2, _LLV2, _LLL2, _LLR2) then
                    TreeR = red(K0, V0, LR, R0),
                    Tree = black(LK, LV, LL, TreeR)
                else if LR = red(LRK, LRV, LRL, LRR) then
                    TreeL = red(LK, LV, LL, LRL),
                    TreeR = red(K0, V0, LRR, R0),
                    Tree = black(LRK, LRV, TreeL, TreeR)
                else
                    Tree = black(K0, V0, NewL, R0)
                )
            else
                Tree = black(K0, V0, NewL, R0)
            )
        )
    ).

%---------------------------------------------------------------------------%

member(empty, _K, _V) :- fail.
member(red(K0, V0, Left, Right), K, V) :-
    (
        K = K0,
        V = V0
    ;
        rbtree.member(Left, K, V)
    ;
        rbtree.member(Right, K, V)
    ).
member(black(K0, V0, Left, Right), K, V) :-
    (
        K = K0,
        V = V0
    ;
        rbtree.member(Left, K, V)
    ;
        rbtree.member(Right, K, V)
    ).

%---------------------------------------------------------------------------%

search(Tree, K, V) :-
    ( Tree = red(K0, V0, Left, Right)
    ; Tree = black(K0, V0, Left, Right)
    ),
    compare(Result, K, K0),
    (
        Result = (=),
        V = V0
    ;
        Result = (<),
        rbtree.search(Left, K, V)
    ;
        Result = (>),
        rbtree.search(Right, K, V)
    ).

lookup(RBT, K) = V :-
    rbtree.lookup(RBT, K, V).

lookup(T, K, V) :-
    ( if rbtree.search(T, K, V0) then
        V = V0
    else
        report_lookup_error("rbtree.lookup: key not found", K, V)
    ).

%---------------------------------------------------------------------------%

lower_bound_search(Tree, SearchK, K, V) :-
    ( Tree = red(K0, V0, Left, Right)
    ; Tree = black(K0, V0, Left, Right)
    ),
    compare(Result, SearchK, K0),
    (
        Result = (=),
        K = K0,
        V = V0
    ;
        Result = (<),
        rbtree.lower_bound_search(Left, SearchK, K, V)
    ;
        Result = (>),
        ( if rbtree.lower_bound_search(Right, SearchK, Kp, Vp) then
            K = Kp,
            V = Vp
        else
            K = K0,
            V = V0
        )
    ).

lower_bound_lookup(T, SearchK, K, V) :-
    ( if rbtree.lower_bound_search(T, SearchK, K0, V0) then
        K = K0,
        V = V0
    else
        report_lookup_error("rbtree.lower_bound_lookup: key not found",
            SearchK, V)
    ).

%---------------------------------------------------------------------------%

upper_bound_search(Tree, SearchK, K, V) :-
    ( Tree = red(K0, V0, Left, Right)
    ; Tree = black(K0, V0, Left, Right)
    ),
    compare(Result, SearchK, K0),
    (
        Result = (=),
        K = K0,
        V = V0
    ;
        Result = (<),
        ( if rbtree.upper_bound_search(Left, SearchK, Kp, Vp) then
            K = Kp,
            V = Vp
        else
            K = K0,
            V = V0
        )
    ;
        Result = (>),
        rbtree.upper_bound_search(Right, SearchK, K, V)
    ).

upper_bound_lookup(T, SearchK, K, V) :-
    ( if rbtree.upper_bound_search(T, SearchK, K0, V0) then
        K = K0,
        V = V0
    else
        report_lookup_error("rbtree.upper_bound_lookup: key not found",
            SearchK, V)
    ).

%---------------------------------------------------------------------------%

delete(!.RBT, K) = !:RBT :-
    rbtree.delete(K, !RBT).

delete(K, !Tree) :-
    rbtree.delete_2(!.Tree, K, no, _, !:Tree).

    % delete_2(Tree0, Key, MustRemove, MaybeValue, Tree):
    %
    % Search the tree Tree0, looking for a node with key Key to delete.
    % If MustRemove is `yes' and we don't find the key, fail.
    % If we find the key, return it in MaybeValue and delete the node.
    % Tree is the resulting tree, whether a node was removed or not.
    %
    % Deletion algorithm:
    %
    % Search down the tree, looking for the node to delete.  O(log N)
    % When we find it, there are 4 possible conditions ->
    %   * Leaf node
    %       Remove node  O(1)
    %   * Left subtree of node to be deleted exists
    %       Move maximum key of Left subtree to current node. O(log N)
    %   * Right subtree of node to be deleted exists
    %       Move minimum key of Right subtree to current node. O(log N)
    %   * Both left and right subtrees of node to be deleted exist
    %       Move maximum key of Left subtree to current node. O(log N)
    %
    % Algorithm O(log N).
    %
:- pred delete_2(rbtree(K, V), K, bool, maybe(V), rbtree(K, V)).
:- mode delete_2(in, in, in, out, out) is semidet.
:- mode delete_2(in, in, in(bound(no)), out, out) is det.

delete_2(empty, _K, no, no, empty).
delete_2(red(K0, V0, L, R), K, MustRemove, MaybeV, Tree) :-
    compare(Result, K, K0),
    (
        Result = (=),
        ( if rbtree.remove_largest(NewK, NewV, L, NewL) then
            Tree = red(NewK, NewV, NewL, R)
        else
            % L must be empty.
            ( if rbtree.remove_smallest(NewK, NewV, R, NewR) then
                Tree = red(NewK, NewV, empty, NewR)
            else
                % R must be empty
                Tree = empty
            )
        ),
        MaybeV = yes(V0)
    ;
        Result = (<),
        rbtree.delete_2(L, K, MustRemove, MaybeV, NewL),
        Tree = red(K0, V0, NewL, R)
    ;
        Result = (>),
        rbtree.delete_2(R, K, MustRemove, MaybeV, NewR),
        Tree = red(K0, V0, L, NewR)
    ).
delete_2(black(K0, V0, L, R), K, MustRemove, MaybeV, Tree) :-
    compare(Result, K, K0),
    (
        Result = (=),
        ( if rbtree.remove_largest(NewK, NewV, L, NewL) then
            Tree = black(NewK, NewV, NewL, R)
        else
            % L must be empty
            ( if rbtree.remove_smallest(NewK, NewV, R, NewR) then
                Tree = black(NewK, NewV, empty, NewR)
            else
                % R must be empty
                Tree = empty
            )
        ),
        MaybeV = yes(V0)
    ;
        Result = (<),
        rbtree.delete_2(L, K, MustRemove, MaybeV, NewL),
        Tree = black(K0, V0, NewL, R)
    ;
        Result = (>),
        rbtree.delete_2(R, K, MustRemove, MaybeV, NewR),
        Tree = black(K0, V0, L, NewR)
    ).

%---------------------------------------------------------------------------%

remove(K, V, !Tree) :-
    rbtree.delete_2(!.Tree, K, yes, yes(V), !:Tree).

remove_smallest(_K, _V, empty, _Tree) :-
    fail.
remove_smallest(NewK, NewV, red(K0, V0, L, R), Tree) :-
    (
        L = empty,
        NewK = K0,
        NewV = V0,
        Tree = R
    ;
        ( L = red(_, _, _, _)
        ; L = black(_, _, _, _)
        ),
        rbtree.remove_smallest(NewK, NewV, L, NewL),
        Tree = red(K0, V0, NewL, R)
    ).
remove_smallest(NewK, NewV, black(K0, V0, L, R), Tree) :-
    (
        L = empty,
        NewK = K0,
        NewV = V0,
        Tree = R
    ;
        ( L = red(_, _, _, _)
        ; L = black(_, _, _, _)
        ),
        rbtree.remove_smallest(NewK, NewV, L, NewL),
        Tree = black(K0, V0, NewL, R)
    ).

remove_largest(_K, _V, empty, _Tree) :-
    fail.
remove_largest(NewK, NewV, red(K0, V0, L, R), Tree) :-
    (
        R = empty,
        NewK = K0,
        NewV = V0,
        Tree = L
    ;
        ( R = red(_, _, _, _)
        ; R = black(_, _, _, _)
        ),
        rbtree.remove_largest(NewK, NewV, R, NewR),
        Tree = red(K0, V0, L, NewR)
    ).
remove_largest(NewK, NewV, black(K0, V0, L, R), Tree) :-
    (
        R = empty,
        NewK = K0,
        NewV = V0,
        Tree = L
    ;
        ( R = red(_, _, _, _)
        ; R = black(_, _, _, _)
        ),
        rbtree.remove_largest(NewK, NewV, R, NewR),
        Tree = black(K0, V0, L, NewR)
    ).

%---------------------------------------------------------------------------%

keys(RBT) = Ks :-
    rbtree.keys(RBT, Ks).

keys(empty, []).
keys(red(K0, _V0, L, R), List) :-
    rbtree.keys(L, List0),
    rbtree.keys(R, List1),
    list.append(List0, [K0 | List1], List).
keys(black(K0, _V0, L, R), List) :-
    rbtree.keys(L, List0),
    rbtree.keys(R, List1),
    list.append(List0, [K0 | List1], List).

%---------------------------------------------------------------------------%

values(RBT) = Vs :-
    rbtree.values(RBT, Vs).

values(empty, []).
values(red(_K0, V0, L, R), List) :-
    rbtree.values(L, List0),
    rbtree.values(R, List1),
    list.append(List0, [V0 | List1], List).
values(black(_K0, V0, L, R), List) :-
    rbtree.values(L, List0),
    rbtree.values(R, List1),
    list.append(List0, [V0 | List1], List).

%---------------------------------------------------------------------------%

count(RBT) = N :-
    rbtree.count(RBT, N).

count(empty, 0).
count(red(_K, _V, L, R), N) :-
    rbtree.count(L, NO),
    rbtree.count(R, N1),
    N = 1 + NO + N1.
count(black(_K, _V, L, R), N) :-
    rbtree.count(L, NO),
    rbtree.count(R, N1),
    N = 1 + NO + N1.

%---------------------------------------------------------------------------%

assoc_list_to_rbtree(AL) = RBT :-
    rbtree.assoc_list_to_rbtree(AL, RBT).

assoc_list_to_rbtree([], empty).
assoc_list_to_rbtree([K - V | T], Tree) :-
    rbtree.assoc_list_to_rbtree(T, Tree0),
    rbtree.set(K, V, Tree0, Tree).

from_assoc_list(AList) = rbtree.assoc_list_to_rbtree(AList).

%---------------------------------------------------------------------------%

rbtree_to_assoc_list(RBT) = AL :-
    rbtree.rbtree_to_assoc_list(RBT, AL).

rbtree_to_assoc_list(empty, []).
rbtree_to_assoc_list(red(K0, V0, Left, Right), L) :-
    rbtree.rbtree_to_assoc_list(Left, L0),
    rbtree.rbtree_to_assoc_list(Right, L1),
    list.append(L0, [K0 - V0|L1], L).
rbtree_to_assoc_list(black(K0, V0, Left, Right), L) :-
    rbtree.rbtree_to_assoc_list(Left, L0),
    rbtree.rbtree_to_assoc_list(Right, L1),
    list.append(L0, [K0 - V0|L1], L).

to_assoc_list(T) = rbtree.rbtree_to_assoc_list(T).

%---------------------------------------------------------------------------%

foldl(F, T, A) = B :-
    P = ( pred(W::in, X::in, Y::in, Z::out) is det :- Z = F(W, X, Y) ),
    rbtree.foldl(P, T, A, B).

foldl(_Pred, empty, !Acc).
foldl(Pred, red(K, V, Left, Right), !Acc) :-
    rbtree.foldl(Pred, Left, !Acc),
    Pred(K, V, !Acc),
    rbtree.foldl(Pred, Right, !Acc).
foldl(Pred, black(K, V, Left, Right), !Acc) :-
    rbtree.foldl(Pred, Left, !Acc),
    Pred(K, V, !Acc),
    rbtree.foldl(Pred, Right, !Acc).

%---------------------------------------------------------------------------%

foldl2(_, empty, !Acc1, !Acc2).
foldl2(Pred, red(K, V, Left, Right), !Acc1, !Acc2) :-
    rbtree.foldl2(Pred, Left, !Acc1, !Acc2),
    Pred(K, V, !Acc1, !Acc2),
    rbtree.foldl2(Pred, Right, !Acc1, !Acc2).
foldl2(Pred, black(K, V, Left, Right), !Acc1, !Acc2) :-
    rbtree.foldl2(Pred, Left, !Acc1, !Acc2),
    Pred(K, V, !Acc1, !Acc2),
    rbtree.foldl2(Pred, Right, !Acc1, !Acc2).

%---------------------------------------------------------------------------%

foldl3(_, empty, !Acc1, !Acc2, !Acc3).
foldl3(Pred, red(K, V, Left, Right), !Acc1, !Acc2, !Acc3) :-
    rbtree.foldl3(Pred, Left, !Acc1, !Acc2, !Acc3),
    Pred(K, V, !Acc1, !Acc2, !Acc3),
    rbtree.foldl3(Pred, Right, !Acc1, !Acc2, !Acc3).
foldl3(Pred, black(K, V, Left, Right), !Acc1, !Acc2, !Acc3) :-
    rbtree.foldl3(Pred, Left, !Acc1, !Acc2, !Acc3),
    Pred(K, V, !Acc1, !Acc2, !Acc3),
    rbtree.foldl3(Pred, Right, !Acc1, !Acc2, !Acc3).

%---------------------------------------------------------------------------%

foldl_values(_Pred, empty, !Acc).
foldl_values(Pred, red(_K, V, Left, Right), !Acc) :-
    rbtree.foldl_values(Pred, Left, !Acc),
    Pred(V, !Acc),
    rbtree.foldl_values(Pred, Right, !Acc).
foldl_values(Pred, black(_K, V, Left, Right), !Acc) :-
    rbtree.foldl_values(Pred, Left, !Acc),
    Pred(V, !Acc),
    rbtree.foldl_values(Pred, Right, !Acc).

%---------------------------------------------------------------------------%

foldl2_values(_Pred, empty, !Acc1, !Acc2).
foldl2_values(Pred, red(_K, V, Left, Right), !Acc1, !Acc2) :-
    rbtree.foldl2_values(Pred, Left, !Acc1, !Acc2),
    Pred(V, !Acc1, !Acc2),
    rbtree.foldl2_values(Pred, Right, !Acc1, !Acc2).
foldl2_values(Pred, black(_K, V, Left, Right), !Acc1, !Acc2) :-
    rbtree.foldl2_values(Pred, Left, !Acc1, !Acc2),
    Pred(V, !Acc1, !Acc2),
    rbtree.foldl2_values(Pred, Right, !Acc1, !Acc2).

%---------------------------------------------------------------------------%

foldr(F, T, A) = B :-
    P = ( pred(W::in, X::in, Y::in, Z::out) is det :- Z = F(W, X, Y) ),
    rbtree.foldr(P, T, A, B).

foldr(_Pred, empty, !Acc).
foldr(Pred, red(K, V, Left, Right), !Acc) :-
    rbtree.foldr(Pred, Right, !Acc),
    Pred(K, V, !Acc),
    rbtree.foldr(Pred, Left, !Acc).
foldr(Pred, black(K, V, Left, Right), !Acc) :-
    rbtree.foldr(Pred, Right, !Acc),
    Pred(K, V, !Acc),
    rbtree.foldr(Pred, Left, !Acc).

%---------------------------------------------------------------------------%

foldr2(_, empty, !Acc1, !Acc2).
foldr2(Pred, red(K, V, Left, Right), !Acc1, !Acc2) :-
    rbtree.foldr2(Pred, Right, !Acc1, !Acc2),
    Pred(K, V, !Acc1, !Acc2),
    rbtree.foldr2(Pred, Left, !Acc1, !Acc2).
foldr2(Pred, black(K, V, Left, Right), !Acc1, !Acc2) :-
    rbtree.foldr2(Pred, Right, !Acc1, !Acc2),
    Pred(K, V, !Acc1, !Acc2),
    rbtree.foldr2(Pred, Left, !Acc1, !Acc2).

%---------------------------------------------------------------------------%

foldr_values(_Pred, empty, !Acc).
foldr_values(Pred, red(_K, V, Left, Right), !Acc) :-
    rbtree.foldr_values(Pred, Right, !Acc),
    Pred(V, !Acc),
    rbtree.foldr_values(Pred, Left, !Acc).
foldr_values(Pred, black(_K, V, Left, Right), !Acc) :-
    rbtree.foldr_values(Pred, Right, !Acc),
    Pred(V, !Acc),
    rbtree.foldr_values(Pred, Left, !Acc).

%---------------------------------------------------------------------------%

map_values(F, T1) = T2 :-
    P = ( pred(X::in, Y::in, Z::out) is det :- Z = F(X, Y) ),
    rbtree.map_values(P, T1, T2).

map_values(_Pred, empty, empty).
map_values(Pred, Tree0, Tree) :-
    Tree0 = red(K0, V0, Left0, Right0),
    Pred(K0, V0, W0),
    rbtree.map_values(Pred, Left0, Left),
    rbtree.map_values(Pred, Right0, Right),
    Tree = red(K0, W0, Left, Right).
map_values(Pred, Tree0, Tree) :-
    Tree0 = black(K0, V0, Left0, Right0),
    Pred(K0, V0, W0),
    rbtree.map_values(Pred, Left0, Left),
    rbtree.map_values(Pred, Right0, Right),
    Tree = black(K0, W0, Left, Right).

%---------------------------------------------------------------------------%
:- end_module rbtree.
%---------------------------------------------------------------------------%
