program tree;
type TreedataType = (TDString, TDInteger);
type Treedata = record
        case typ:Treedatatype of 
        TDString:(stringdata:string);
        TDInteger:(intdata:integer);
    end;
type TreeNode = record
        parent: ^TreeNode;
        left,right: ^TreeNode;
        height: integer;
	key: ^string;
	data: ^Treedata;
    end;

type TreeRef = ^TreeNode;

type TreeWalkState = record
		currentNode:TreeRef;
	end;

var t:TreeRef;
    k:string;
    d:TreeData;
    i:integer;
    searchres:^Treedata;
    walkState:TreeWalkState;
    walkRes:TreeRef;

procedure mem_dump; external;

function makeTreeNode(var d:TreeData;var key:string;nparent:TreeRef):TreeRef;
var newNode:TreeRef;
    newKey:^string;
begin
	new(newNode);
        new(newKey,length(key));
	{ new(newKey); }
	new(newNode^.data);
	newKey^ := key;
	with newNode^ do
	begin
		key := newKey;
		parent := nparent;
		left := nil;
		right := nil;
		height := 1;
		data^ := d;
	end;
	makeTreeNode := newNode;
end;

function MeasureTree(root:TreeRef):integer;
var leftHeight, rightHeight:integer;
begin
	if root = nil then
		MeasureTree := 0
	else
	begin
		if root^.left <> nil then
			leftHeight := root^.left^.height
		else
			leftHeight := 0;
		if root^.right <> nil then
			rightHeight := root^.right^.height
		else
			rightHeight := 0;
		if rightHeight > leftHeight then
			MeasureTree := rightHeight + 1
		else
			MeasureTree := leftHeight + 1;
	end;
end;

function GetTreeBalance(root:TreeRef):integer;
begin
	if root = nil then
		GetTreeBalance := 0
	else
		GetTreeBalance := MeasureTree(root^.left) - MeasureTree(root^.right);
end;

function RotateTreeRight(x:TreeRef):TreeRef;
var z,tmp:TreeRef;
begin
	writeln('RotateTreeRight at ', x^.key^);
	z := x^.left;
	tmp := z^.right;
	z^.right := x;
	z^.parent := x^.parent;
	x^.parent := z;
	x^.left := tmp;
	if tmp <> nil then
		tmp^.parent := x;
	x^.height := MeasureTree(x);
	z^.height := MeasureTree(z);
	RotateTreeRight := z;
end;

function RotateTreeLeft(x:TreeRef):TreeRef;
var z,tmp:TreeRef;
begin
	writeln('RotateTreeLeft at ', x^.key^);
	z := x^.right;
	tmp := z^.left;
	z^.left := x;
	z^.parent := x^.parent;
	x^.parent := z;
	x^.right := tmp;
	if tmp <> nil then
		tmp^.parent := x;
	x^.height := MeasureTree(x);
	z^.height := MeasureTree(z);
	RotateTreeLeft := z;
end;

function TreeInsert4(root:TreeRef;var key:string;var data:TreeData;
	parent:TreeRef):TreeRef;
var balance:integer;
begin
	if root = nil then
		root := makeTreeNode(data, key, parent)
	else
	if key < root^.key^ then
		root^.left := TreeInsert4(root^.left, key, data, root)
	else
		root^.right := TreeInsert4(root^.right, key, data, root);

	root^.height := MeasureTree(root);

	balance := GetTreeBalance(root);
	if balance > 1 then
	begin
		if key < root^.left^.key^ then
			root := RotateTreeRight(root)
		else
		begin
			root^.left := RotateTreeLeft(root^.left);
			root := RotateTreeRight(root);
		end;
	end
	else
	if balance < -1 then
	begin
		if key > root^.right^.key^ then
			root := RotateTreeLeft(root)
		else
		begin
			root^.right := RotateTreeRight(root^.right);
			root := RotateTreeLeft(root);
		end;
	end;

	TreeInsert4 := root;
end;

procedure TreeInsert(var root:TreeRef;var key:string;var data:TreeData);
begin
	root := TreeInsert4(root,key,data,nil);
end;

procedure DisposeTreeNode(node:TreeRef);
begin
	dispose(node^.key);
	dispose(node^.data);
	dispose(node);
end;

function TreeLeftmost(node:TreeRef):TreeRef;
begin
	TreeLeftmost := nil;
	if node <> nil then
	begin
		repeat
			TreeLeftmost := node;
			node := node^.left;
		until node = nil;
	end;
end;

procedure PrintTreeRef(node:TreeRef);
begin
	if node = nil then
		write('nil')
	else
		write(node^.key^);
end;

procedure PrintTreeNode(node:TreeRef);
begin
	write(' -');
	PrintTreeRef(node);
	if node <> nil then
	begin
		write(' ^');
		PrintTreeRef(node^.parent);
		write(' <');
		PrintTreeRef(node^.left);
		write(' >');
		PrintTreeRef(node^.right);
	end;
	writeln;
end;

function TreeDeleteFn(root:TreeRef;var key:string):TreeRef;
var tmp,oldParent:TreeRef;
    balance:integer;
begin
	if root <> nil then
	begin
		if key < root^.key^ then
			root^.left := TreeDeleteFn(root^.left, key)
		else
		if key > root^.key^ then
			root^.right := TreeDeleteFn(root^.right, key)
		else
		begin
			if root^.left = nil then
			begin
				tmp := root;
				oldParent := root^.parent;
				root := root^.right;
				if root <> nil then
					root^.parent := oldParent;
				DisposeTreeNode(tmp);
			end
			else
			if root^.right = nil then
			begin
				tmp := root;
				oldParent := root^.parent;
				root := root^.left;
				if root <> nil then
					root^.parent := oldParent;
				DisposeTreeNode(tmp);
			end
			else
			begin
				writeln('TreeDelete search leftmost from ', root^.key^);
				PrintTreeNode(root);
				tmp := TreeLeftmost(root^.right);
				if maxlength(tmp^.key^) <> maxlength(root^.key^) then
				begin	(* reallocate key, the swapped key might have a different length *)
					write('reallocating key ', length(root^.key^));
					dispose(root^.key);
					new(root^.key, length(tmp^.key^));
					writeln(' -> ', maxlength(root^.key^));
				end;
				root^.key^ := tmp^.key^;
				root^.data^ := tmp^.data^;
				writeln('TreeDelete delete leftmost ', tmp^.key^);
				PrintTreeNode(tmp);
				writeln('oldParent: ');
				PrintTreeNode(tmp^.parent);
				oldParent := tmp^.parent;
				if oldParent^.left = tmp then
					oldParent^.left := TreeDeleteFn(oldParent^.left, tmp^.key^)
				else
				if oldParent^.right = tmp then
					oldParent^.right := TreeDeleteFn(oldParent^.right, tmp^.key^)
				else
					writeln('TreeDelete internal error');
			end;

			if root <> nil then
			begin
				root^.height := MeasureTree(root);
				balance := GetTreeBalance(root);
				if balance > 1 then
				begin
					if GetTreeBalance(root^.left) >=0 then
						root := RotateTreeRight(root)
					else
					begin
						root^.left := RotateTreeLeft(root^.left);
						root := RotateTreeRight(root);
					end;
				end
				else
				if balance < -1 then
				begin
					if GetTreeBalance(root^.right) <= 0 then
						root^.right := RotateTreeLeft(root)
					else
					begin
						root^.right := RotateTreeRight(root^.right);
						root := RotateTreeLeft(root);
					end;
				end;
			end;
		end;
	end;
	TreeDeleteFn := root;
end;

procedure TreeDelete(var root:TreeRef;var key:string);
begin
	root := TreeDeleteFn(root,key);
end;

function TreeSearch(root:TreeRef;var key:string):^TreeData;
begin
	if root <> nil then
	begin
		if key = root^.key^ then
			TreeSearch := root^.data
		else
		if key < root^.key^ then
			TreeSearch := TreeSearch(root^.left, key)
		else
			TreeSearch := TreeSearch(root^.right, key);
	end
	else
		TreeSearch := nil;
end;

procedure TreeWalkStart(t:TreeRef; var state:TreeWalkState);
begin
	(* start at leftmost node of the tree *)
	state.currentNode := TreeLeftmost(t);
end;

procedure TreeWalkNext(var state:TreeWalkState;var res:TreeRef);
var last,current,old,right:TreeRef;
begin
	current := state.currentNode;

	res := current;

	if current <> nil then
	begin
			(* descending right *)
		if current^.right <> nil then
		begin
			state.currentNode := TreeLeftmost(current^.right);
		end
		else	(* ascending *)
		begin
			old := current;
			repeat
				last := current;
				current := current^.parent;
				if current <> nil then
					right := current^.right;
			until (right <> last) or (current = nil); (* ascend left edges *)
			state.currentNode := current;
		end;
	end;
end;

procedure indent(i:integer);
var c:integer;
begin
	for c := 1 to i do
		write('  ');
end;

procedure PrintStringTree(node:TreeRef;level:integer);
begin
	if node <> nil then
	begin
		if node^.left <> nil then
			PrintStringTree(node^.left, level + 1);
		indent(level);
		PrintTreeNode(node);
		if node^.right <> nil then
			PrintStringTree(node^.right, level + 1);
	end;
end;

procedure DoASearch(t:TreeRef; s:string);
var res:^TreeData;
begin
	res := TreeSearch(t, s);
	write('searching for ',s);
	if res = nil then
		writeln(' nil')
	else
		writeln(res^.stringdata);
end;

begin
	mem_dump;
	{
	t := nil;
	k := 'test1';
	d.typ := TDString;
	d.stringdata := 'data1';
	TreeInsert(t,k,d);

	k := 'test0';
	d.typ := TDString;
	d.stringdata := 'data0';
	TreeInsert(t,k,d);

	k := 'test3';
	d.typ := TDString;
	d.stringdata := 'data3';
	TreeInsert(t,k,d);

	k := 'test2';
	d.typ := TDString;
	d.stringdata := 'data2';
	TreeInsert(t,k,d);

	k := 'test4';
	d.typ := TDString;
	d.stringdata := 'data4';
	TreeInsert(t,k,d);

	writeln('root: ', t^.key^);

	PrintStringTree(t,1);
	}
	writeln('------------');

	t := nil;
	d.typ := TDString;
	d.stringdata := 'data';

	for i := 1 to 30 do
	begin
		str(i,k);
		d.stringdata := 'data' + k;
		k := 'test' + k;
		TreeInsert(t,k,d);

		if i >99 then
		begin
			writeln('root: ', t^.key^);
			PrintStringTree(t,1);
			writeln('------------');
			readln;
		end;
	end;

	writeln('root: ', t^.key^);
	PrintStringTree(t,1);
	writeln('------------');


	DoASearch(t,'test21');
	DoASearch(t,'test2');
	DoASearch(t,'test17');

	k := 'test17';
	TreeDelete(t,k);
	writeln('root: ', t^.key^);
	PrintStringTree(t,1);
	writeln('------------');
	DoASearch(t,'test17');



	TreeWalkStart(t, walkState);
	repeat
		TreeWalkNext(walkState, walkRes);
		if walkRes <> nil then
			writeln(walkRes^.data^.stringdata);
	until walkRes = nil;



	for i := 1 to 30 do
	begin
		str(i,k);
		k := 'test' + k;
		writeln('deleting ', k);
		TreeDelete(t,k);
	end;
	if t <> nil then
		writeln('root: ', t^.key^)
	else
		writeln('root: nil');
	PrintStringTree(t,1);
	writeln('------------');

	mem_dump;
end.
