/*
    libmaus2
    Copyright (C) 2020-2021 German Tischler

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#if ! defined(LIBMAUS2_AVL_AVLSET_HPP)
#define LIBMAUS2_AVL_AVLSET_HPP

#include <stack>
#include <functional>
#include <vector>
#include <cassert>
#include <ostream>
#include <limits>

// #define AVL_TREE_DEBUG

#if defined(AVL_TREE_DEBUG)
#include <iostream>
#endif

#include <libmaus2/exception/LibMausException.hpp>

namespace libmaus2
{
	namespace avl
	{
		/**
		 * Adelson-Velsky, Landis ordered binary tree based set
		 **/
		template<
			typename _key_type,
			typename _order_type = std::less<_key_type>,
			typename _index_type = std::size_t
		>
		struct AVLSet
		{
			typedef _key_type key_type;
			typedef _index_type index_type;
			typedef _order_type order_type;
			typedef AVLSet<key_type,order_type,index_type> this_type;
			typedef this_type tree_type;

			static index_type const nullindex = std::numeric_limits<index_type>::max();

			struct ConstIterator
			{
				typedef std::forward_iterator_tag iterator_category;
				typedef key_type const & reference;
				typedef key_type const * pointer;
				typedef key_type value_type;
				using difference_type = std::intptr_t;

				tree_type const * tree;
				index_type cur;

				ConstIterator() : tree(nullptr), cur(nullindex) {}
				ConstIterator(tree_type const * rtree, index_type rcur) : tree(rtree), cur(rcur) {}
				ConstIterator(ConstIterator const & O) : tree(O.tree), cur(O.cur) {}

				ConstIterator & operator=(ConstIterator const & O)
				{
					if ( this != &O )
					{
						tree = O.tree;
						cur = O.cur;
					}

					return *this;
				}

				bool operator==(ConstIterator const & O) const
				{
					return
						tree == O.tree
						&&
						cur == O.cur;
				}

				bool operator!=(ConstIterator const & O) const
				{
					return !operator==(O);
				}

				reference operator*() const
				{
					return reinterpret_cast<reference>(tree->A[cur].k);
				}

				pointer operator->() const
				{
					if ( cur != nullindex )
						return reinterpret_cast<pointer>(&(tree->A[cur].k));
					else
						return nullptr;
				}

				ConstIterator operator++()
				{
					if ( cur != nullindex )
					{
						// if node has a right child
						if ( tree->A[cur].hasRight() )
						{
							// go right
							cur = tree->A[cur].r;

							// follow leftmost path
							while ( tree->A[cur].hasLeft() )
								cur = tree->A[cur].l;
						}
						else
						{
							// go up tree until we find a node not reached via a link to a right child
							while ( true )
							{
								index_type const p = cur;

								cur = tree->A[cur].p;

								if ( cur == nullindex || tree->A[cur].r != p )
									break;
							}
						}
					}
					else
					{
					}

					return *this;
				}

				ConstIterator operator++(int)
				{
					ConstIterator R = *this;

					++(*this);

					return R;
				}
			};

			struct ConstReverseIterator
			{
				typedef std::forward_iterator_tag iterator_category;
				typedef key_type const & reference;
				typedef key_type const * pointer;
				typedef key_type value_type;
				using difference_type = std::intptr_t;

				tree_type const * tree;
				index_type cur;

				ConstReverseIterator() : tree(nullptr), cur(nullindex) {}
				ConstReverseIterator(tree_type const * rtree, index_type rcur) : tree(rtree), cur(rcur) {}
				ConstReverseIterator(ConstReverseIterator const & O) : tree(O.tree), cur(O.cur) {}

				ConstReverseIterator & operator=(ConstReverseIterator const & O)
				{
					if ( this != &O )
					{
						tree = O.tree;
						cur = O.cur;
					}

					return *this;
				}

				bool operator==(ConstReverseIterator const & O) const
				{
					return
						tree == O.tree
						&&
						cur == O.cur;
				}

				bool operator!=(ConstReverseIterator const & O) const
				{
					return !operator==(O);
				}

				reference operator*() const
				{
					return reinterpret_cast<reference>(tree->A[cur].k);
				}

				pointer operator->() const
				{
					if ( cur != nullindex )
						return reinterpret_cast<pointer>(&(tree->A[cur].k));
					else
						return nullptr;
				}

				ConstReverseIterator operator++()
				{
					if ( cur != nullindex )
					{
						// if node has a left child
						if ( tree->A[cur].hasLeft() )
						{
							// go left
							cur = tree->A[cur].l;

							// follow rightmost path
							while ( tree->A[cur].hasRight() )
								cur = tree->A[cur].r;
						}
						else
						{
							// go up tree until we find a node not reached via a link to a left child
							while ( true )
							{
								index_type const p = cur;

								cur = tree->A[cur].p;

								if ( cur == nullindex || tree->A[cur].l != p )
									break;
							}
						}
					}
					else
					{
					}

					return *this;
				}

				ConstReverseIterator operator++(int)
				{
					ConstReverseIterator R = *this;

					++(*this);

					return R;
				}
			};

			typedef ConstIterator const_iterator;
			typedef ConstReverseIterator const_reverse_iterator;

			const_iterator cend() const
			{
				return const_iterator(this,nullindex);
			}

			const_iterator end() const
			{
				return const_iterator(this,nullindex);
			}

			const_reverse_iterator rend() const
			{
				return const_reverse_iterator(this,nullindex);
			}

			const_iterator begin() const
			{
				if ( empty() )
					return end();

				index_type cur = root;
				while ( cur != nullindex && A[cur].hasLeft() )
					cur = A[cur].l;

				return const_iterator(this,cur);
			}

			const_iterator cbegin() const
			{
				if ( empty() )
					return end();

				index_type cur = root;
				while ( cur != nullindex && A[cur].hasLeft() )
					cur = A[cur].l;

				return const_iterator(this,cur);
			}

			const_reverse_iterator rbegin() const
			{
				if ( empty() )
					return rend();

				index_type cur = root;
				while ( cur != nullindex && A[cur].hasRight() )
					cur = A[cur].r;

				return const_reverse_iterator(this,cur);
			}

			private:
			struct AVLSetNode
			{
				unsigned char h;
				index_type l;
				index_type r;
				index_type p;
				key_type k;
				std::size_t n;

				AVLSetNode(
					unsigned char rh,
					index_type rl,
					index_type rr,
					index_type rp,
					key_type rk,
					::std::size_t rn
				) : h(rh), l(rl), r(rr), p(rp), k(rk), n(rn) {}
				AVLSetNode(AVLSetNode const & O)
				: h(O.h), l(O.l), r(O.r), p(O.p), k(O.k), n(O.n)
				{

				}

				AVLSetNode & operator=(AVLSetNode const & O)
				{
					if ( this != &O )
					{
						h = O.h;
						l = O.l;
						r = O.r;
						p = O.p;
						k = O.k;
						n = O.n;
					}
					return *this;
				}

				bool hasParent() const
				{
					return p != nullindex;
				}
				bool hasLeft() const
				{
					return l != nullindex;
				}
				bool hasRight() const
				{
					return r != nullindex;
				}

				static std::string pointerToString(index_type index)
				{
					std::ostringstream ostr;
					if ( index == nullindex )
						ostr << "null";
					else
						ostr << index;

					return ostr.str();
				}

				std::ostream & print(std::ostream & out) const
				{
					out << "(h=" << static_cast<int>(h) << ",l=" << pointerToString(l) << ",r=" << pointerToString(r)
						<< ",p=" << pointerToString(p) << ",k=" << k << ",n=" << n << ")";

					return out;
				}
			};

			typedef AVLSetNode node_type;

			order_type order;

			// tree nodes memory
			std::shared_ptr<char[]> AC;
			// tree nodes currently used
			std::size_t a_f;
			// tree nodes capacity
			std::size_t a_n;
			// tree nodes array
			AVLSetNode * A;

			// free list
			std::shared_ptr<index_type[]> F;
			// free list number currently stored
			std::size_t f_f;
			// free list capacity
			std::size_t f_n;

			// pointer to root node
			index_type root;

			void callDestructors()
			{
				// explicitely call destructor for existing objects
				for ( std::size_t i = 0; i < a_f; ++i )
					A[i].~node_type();
			}

			index_type allocateNode(
				unsigned char rh,
				index_type rl,
				index_type rr,
				index_type rp,
				key_type rk,
				::std::size_t rn
			)
			{
				if ( ! f_f )
				{
					// if array A is fully used
					if ( a_f == a_n )
					{
						// new size of array in number of nodes
						std::size_t n_a_n = a_n ? (2*a_n) : 1;
						// allocate memory
						std::shared_ptr<char[]> NAC(new char[n_a_n * sizeof(AVLSetNode)]);

						node_type * to = reinterpret_cast<AVLSetNode *>(NAC.get());
						node_type * from = A;

						// copy existing data
						for ( std::size_t i = 0; i < a_n; ++i )
							new(&to[i]) node_type(from[i]);

						// call object destructors
						callDestructors();

						AC = NAC;
						A  = reinterpret_cast<AVLSetNode *>(AC.get());
						a_n = n_a_n;
					}

					index_type const id = a_f++;

					// placement new
					new (&(A[id])) AVLSetNode(rh,rl,rr,rp,rk,rn);

					return id;
				}
				else
				{
					index_type const id = F[--f_f];

					A[id] = node_type(rh,rl,rr,rp,rk,rn);

					return id;
				}
			}

			void freeNode(index_type const id)
			{
				if ( f_f == f_n )
				{
					std::size_t const n_f_n = f_n ? (2*f_n) : 1;
					std::shared_ptr<std::size_t[]> NF(new std::size_t[n_f_n]);
					std::copy(F.get(),F.get()+f_n,NF.get());

					F = NF;
					f_n = n_f_n;
				}

				F[f_f++] = id;
			}


			void setParentIf(index_type n, index_type p)
			{
				if ( n != nullindex )
					A[n].p = p;
			}

			bool isLeftChild(index_type const p, index_type const n)
			{
				if ( p == nullindex )
					return false;

				if ( A[p].l == n )
					return true;
				else if ( A[p].r == n )
					return false;
				else
					throw std::runtime_error("AVLSet::isLeftChild: internal inconsistency");
			}

			bool isRightChild(index_type const p, index_type const n)
			{
				if ( p == nullindex )
					return false;

				if ( A[p].r == n )
					return true;
				else if ( A[p].l == n )
					return false;
				else
					throw std::runtime_error("AVLSet::isRightChild: internal inconsistency");
			}

			void replaceInParent(index_type p, index_type y, index_type x)
			{
				if ( p != nullindex )
				{
					if ( y == A[p].l )
					{
						A[p].l = x;
					}
					else
					{
						assert ( A[p].r == y );
						A[p].r = x;
					}
				}
			}

			int getDepth(index_type cur) const
			{
				if ( cur == nullindex )
					return 0;

				typedef std::pair<index_type,int> stype;
				std::stack< stype > S;
				S.push(stype(cur,1));

				int maxd = 1;

				while ( !S.empty() )
				{
					stype top = S.top();
					S.pop();

					node_type const & N = A[top.first];

					if ( N.hasLeft() )
						S.push(stype(N.l,top.second+1));
					if ( N.hasRight() )
						S.push(stype(N.r,top.second+1));

					if ( top.second > maxd )
						maxd = top.second;
				}

				return maxd;
			}

			::std::size_t getCount(index_type cur) const
			{
				if ( cur == nullindex )
					return 0;

				std::stack< index_type > S;
				S.push(cur);

				::std::size_t count = 0;

				while ( !S.empty() )
				{
					index_type top = S.top();
					S.pop();

					node_type const & N = A[top];

					if ( N.hasLeft() )
						S.push(N.l);
					if ( N.hasRight() )
						S.push(N.r);

					count += 1;
				}

				return count;
			}

			::std::size_t getN(index_type const cur) const
			{
				if ( cur != nullindex )
					return A[cur].n;
				else
					return 0;
			}

			int getHeight(index_type const cur) const
			{
				if ( cur != nullindex )
					return A[cur].h;
				else
					return 0;
			}

			int getBalance(index_type const cur) const
			{
				if ( cur == nullindex )
					return 0;

				return
					getHeight(A[cur].r) - getHeight(A[cur].l);
			}

			index_type rotateRightLeft(index_type const x)
			{
				assert ( x != nullindex );
				index_type const p = A[x].p;
				index_type const y = A[x].r;
				assert ( y != nullindex );
				index_type const z = A[y].l;
				assert ( z != nullindex );

				index_type const t0 = A[x].l;
				index_type const t1 = A[z].l;
				index_type const t2 = A[z].r;
				index_type const t3 = A[y].r;

				A[x].l = t0;
				A[x].r = t1;
				A[x].h = 1+std::max(getHeight(t0),getHeight(t1));
				A[x].n = 1 + getN(t0) + getN(t1);

				A[y].l = t2;
				A[y].r = t3;
				A[y].h = 1+std::max(getHeight(t2),getHeight(t3));
				A[y].n = 1 + getN(t2) + getN(t3);

				A[z].l = x;
				A[z].r = y;
				A[z].h = 1+std::max(getHeight(x),getHeight(y));
				A[z].n = 1 + getN(x) + getN(y);

				setParentIf(t0,x);
				setParentIf(t1,x);
				setParentIf(t2,y);
				setParentIf(t3,y);
				setParentIf(x,z);
				setParentIf(y,z);
				setParentIf(z,p);

				replaceInParent(p,x,z);

				return z;
			}

			index_type rotateLeftRight(index_type const x)
			{
				assert ( x != nullindex );
				index_type const p = A[x].p;
				index_type const y = A[x].l;
				assert ( y != nullindex );
				index_type const z = A[y].r;
				assert ( z != nullindex );

				index_type const t0 = A[y].l;
				index_type const t1 = A[z].l;
				index_type const t2 = A[z].r;
				index_type const t3 = A[x].r;

				A[y].l = t0;
				A[y].r = t1;
				A[y].h = 1+std::max(getHeight(t0),getHeight(t1));
				A[y].n = 1 + getN(t0) + getN(t1);

				A[x].l = t2;
				A[x].r = t3;
				A[x].h = 1+std::max(getHeight(t2),getHeight(t3));
				A[x].n = 1 + getN(t2) + getN(t3);

				A[z].l = y;
				A[z].r = x;
				A[z].h = 1+std::max(getHeight(y),getHeight(x));
				A[z].n = 1 + getN(y) + getN(x);

				setParentIf(t0,y);
				setParentIf(t1,y);
				setParentIf(t2,x);
				setParentIf(t3,x);
				setParentIf(y,z);
				setParentIf(x,z);
				setParentIf(z,p);

				replaceInParent(p,x,z);

				return z;
			}

			index_type rotateRight(index_type y)
			{
				assert ( y != nullindex );
				assert ( A[y].hasLeft() );

				index_type const x = A[y].l;
				index_type const p = A[y].p;
				index_type const t0 = A[x].l;
				index_type const t1 = A[x].r;
				index_type const t2 = A[y].r;

				A[y].l = t1;
				A[y].r = t2;
				A[y].h = 1+std::max(getHeight(t1),getHeight(t2));
				A[y].n = 1 + getN(t1) + getN(t2);

				A[x].l = t0;
				A[x].r = y;
				A[x].h = 1+std::max(getHeight(t0),getHeight(y));
				A[x].n = 1 + getN(t0) + getN(y);

				setParentIf(t0,x);
				setParentIf(x,p);
				setParentIf(y,x);
				setParentIf(t1,y);
				setParentIf(t2,y);

				replaceInParent(p,y,x);

				return x;
			}

			index_type rotateLeft(index_type y)
			{
				assert ( y != nullindex );
				assert ( A[y].hasRight() );

				index_type const x = A[y].r;
				index_type const p = A[y].p;
				index_type const t0 = A[y].l;
				index_type const t1 = A[x].l;
				index_type const t2 = A[x].r;

				A[y].l = t0;
				A[y].r = t1;
				A[y].h = 1+std::max(getHeight(t0),getHeight(t1));
				A[y].n = 1 + getN(t0) + getN(t1);

				A[x].l = y;
				A[x].r = t2;
				A[x].h = 1+std::max(getHeight(y),getHeight(t2));
				A[x].n = 1 + getN(y) + getN(t2);

				setParentIf(x,p);
				setParentIf(t2,x);
				setParentIf(y,x);
				setParentIf(t0,y);
				setParentIf(t1,y);

				replaceInParent(p,y,x);

				return x;
			}

			void print(std::ostream & out, index_type root) const
			{
				struct StackNode
				{
					index_type n;
					int depth;
					int visit;

					StackNode()
					{}
					StackNode(index_type const rn, int const rdepth, int const rvisit)
					: n(rn), depth(rdepth), visit(rvisit) {}
				};
				std::stack< StackNode > S;
				S.push( StackNode(root,0,0) );

				while ( !S.empty() )
				{
					StackNode N = S.top();
					S.pop();

					assert ( N.n != nullindex );
					node_type const & X = A[N.n];

					if ( N.visit == 0 )
					{

						if ( X.hasRight() )
							S.push(StackNode(X.r,N.depth+1,0));

						S.push(StackNode(N.n,N.depth+1,N.visit+1));

						if ( X.hasLeft() )
							S.push(StackNode(X.l,N.depth+1,0));
					}
					else if ( N.visit == 1 )
					{
						out << std::string(N.depth,' ') << N.n << ":";
						X.print(out);
						out << "\n";
					}
				}
			}

			public:
			AVLSet() : order(order_type()), AC(), a_f(0), a_n(0), A(nullptr), F(), f_f(0), f_n(0), root(nullindex)
			{
			}

			AVLSet(order_type const & rorder) : order(rorder), AC(), a_f(0), a_n(0), A(nullptr), F(), f_f(0), f_n(0), root(nullindex)
			{
			}

			~AVLSet()
			{
				// call destructors for objects used in A
				callDestructors();
			}

			// get iterator to element at position index of sorted list of elements contained in tree
			const_iterator index(::std::size_t index) const
			{
				if ( empty() )
					return end();
				if ( !(index < A[root].n) )
					return end();

				index_type cur = root;

				while ( cur != nullindex )
				{
					::std::size_t n_left = getN(A[cur].l);

					if ( index < n_left )
					{
						assert ( A[cur].l != nullindex );
						cur = A[cur].l;
					}
					else if ( index == n_left )
					{
						return const_iterator(this,cur);
					}
					else
					{
						index -= n_left+1;
						cur = A[cur].r;
					}
				}

				// we should never end up here...
				return end();
			}

			// get iterator to element at position index of sorted list of elements contained in tree
			const_iterator index(::std::size_t index)
			{
				if ( empty() )
					return end();
				if ( !(index < A[root].n) )
					return end();

				index_type cur = root;

				while ( cur != nullindex )
				{
					::std::size_t n_left = getN(A[cur].l);

					if ( index < n_left )
					{
						assert ( A[cur].l != nullindex );
						cur = A[cur].l;
					}
					else if ( index == n_left )
					{
						return iterator(this,cur);
					}
					else
					{
						index -= n_left+1;
						cur = A[cur].r;
					}
				}

				// we should never end up here...
				return end();
			}

			::std::size_t size() const
			{
				return getN(root);
			}

			// return iterator to first element >= k (or end() if no such element is in tree)
			const_iterator lower_bound(key_type const & k) const
			{
				if ( empty() )
					return end();

				// search for k
				index_type cur = root;
				index_type prev = 0;

				while ( cur != nullindex )
				{
					// if k is smaller than current node value
					if ( order(k,A[cur].k) )
					{
						prev = cur;
						cur = A[cur].l;
					}
					// if k is greater than current node value
					else if ( order(A[cur].k,k) )
					{
						prev = cur;
						cur = A[cur].r;
					}
					// if k equals current node value
					else
					{
						return const_iterator(this,cur);
					}
				}

				assert ( cur == nullindex );
				assert ( prev != nullindex );

				const_iterator it(this,prev);

				assert ( it != end() );
				assert ( order(*it,k) || order(k,*it) );

				if ( order(*it,k) )
					++it;

				return it;
			}

			const_iterator find(key_type const & k) const
			{
				const_iterator const it = lower_bound(k);

				if ( it == end() || order(k,*it) )
					return end();
				else
					return it;
			}

			bool erase(key_type const & k)
			{
				const_iterator it = lower_bound(k);

				if ( it == end() || order(k,*it) )
					return false;

				assert (
					!order(k,*it)
					&&
					!order(*it,k)
				);

				return erase(it);
			}

			bool erase(const_iterator const it)
			{
				if ( it == end() )
					return false;

				// search for k
				index_type cur = it.cur;

				// while cur node has any children
				while ( A[cur].hasLeft() || A[cur].hasRight() )
				{
					index_type next;

					// if it has a left child
					if ( A[cur].hasLeft() )
					{
						// go to maximum in left subtree
						next = A[cur].l;

						while ( A[next].hasRight() )
							next = A[next].r;
					}
					// if it has a right child
					else
					{
						assert ( A[cur].hasRight() );

						// go to minimum in right subtree
						next = A[cur].r;

						while ( A[next].hasLeft() )
							next = A[next].l;
					}

					index_type const cur_p  = A[cur].p;
					index_type const next_p = A[next].p;

					replaceInParent(next_p,next,cur );
					replaceInParent(cur_p ,cur ,next);

					std::swap(A[cur].l,A[next].l);
					std::swap(A[cur].r,A[next].r);
					std::swap(A[cur].n,A[next].n);
					std::swap(A[cur].h,A[next].h);
					std::swap(A[cur].p,A[next].p);

					setParentIf(A[cur].l, cur);
					setParentIf(A[cur].r, cur);
					setParentIf(A[next].l, next);
					setParentIf(A[next].r, next);
				}

				assert ( cur != nullindex );
				assert ( A[cur].l == nullindex );
				assert ( A[cur].r == nullindex );

				// if node is the root of the tree
				if ( A[cur].p == nullindex )
				{
					assert ( cur == root );
					freeNode(cur);
					root = nullindex;
					return true;
				}

				index_type const era = cur;

				if ( isLeftChild(A[cur].p,cur) )
				{
					A[A[cur].p].l = nullindex;
				}
				else
				{
					assert ( isRightChild(A[cur].p,cur) );
					A[A[cur].p].r = nullindex;
				}

				// move to parent
				cur = A[cur].p;

				// free node
				freeNode(era);

				// traverse tree bottom up fron newly inserted value and perform rotations as necessary
				while ( cur != nullindex )
				{
					// left heavy?
					if ( getBalance(cur) < -1 )
					{
						assert ( A[cur].hasLeft() );

						// get left child
						index_type const l = A[cur].l;

						// is left child left heavy?
						if ( getBalance(l) <= 0 )
						{
							#if 0
							std::cerr << "[V] single right" << std::endl;
							#endif
							cur = rotateRight(cur);
						}
						else
						{
							#if 0
							std::cerr << "[V] left/right" << std::endl;
							#endif
							cur = rotateLeftRight(cur);
						}

						// assert ( getBalance(cur) == 0 );
					}
					// right heavy
					else if ( getBalance(cur) > 1 )
					{
						assert ( A[cur].hasRight() );

						index_type const r = A[cur].r;

						if ( getBalance(r) >= 0 )
						{
							#if 0
							std::cerr << "[V] single left" << std::endl;
							#endif
							cur = rotateLeft(cur);
						}
						else
						{
							#if 0
							std::cerr << "[V] right/left" << std::endl;
							#endif
							cur = rotateRightLeft(cur);
						}

						// assert ( getBalance(cur) == 0 );
					}

					assert ( (getBalance(cur) >= -1) && (getBalance(cur) <= 1) );

					A[cur].h = 1+std::max(getHeight(A[cur].l),getHeight(A[cur].r));
					A[cur].n = 1+getN(A[cur].l)+getN(A[cur].r);

					// set new root if node has no parent
					if ( A[cur].p == nullindex )
						root = cur;

					// move to parent
					cur = A[cur].p;
				}

				return true;
			}

			bool insert(key_type const & k)
			{
				if ( empty() )
				{
					assert ( root == nullindex );

					root = allocateNode(1,nullindex,nullindex,nullindex,k,1/*n*/);

					return root;
				}
				else
				{
					index_type cur = root;

					// traverse tree top down and insert node if not already present
					while ( true )
					{
						if ( order(k,A[cur].k) )
						{
							if ( A[cur].hasLeft() )
							{
								cur = A[cur].l;
							}
							else
							{
								index_type const n = allocateNode(1/*h*/,nullindex/*l*/,nullindex/*r*/,cur,k,1/*n*/);
								A[cur].l = n;

								break;
							}
						}
						else if ( order(A[cur].k,k) )
						{
							if ( A[cur].hasRight() )
							{
								cur = A[cur].r;
							}
							else
							{
								index_type const n = allocateNode(1/*h*/,nullindex/*l*/,nullindex/*r*/,cur,k,1/*n*/);
								A[cur].r = n;
								break;
							}
						}
						// key is already present
						else
						{
							// replace value
							return false;
						}
					}

					// traverse tree bottom up fron newly inserted value and perform rotations as necessary
					while ( cur != nullindex )
					{
						if ( getBalance(cur) < -1 )
						{
							assert ( A[cur].hasLeft() );

							index_type const l = A[cur].l;

							if ( getBalance(l) < 0 )
							{
								#if 0
								std::cerr << "[V] single right" << std::endl;
								#endif
								cur = rotateRight(cur);
							}
							else
							{
								#if 0
								std::cerr << "[V] left/right" << std::endl;
								#endif
								cur = rotateLeftRight(cur);
							}

							assert ( getBalance(cur) == 0 );
						}
						else if ( getBalance(cur) > 1 )
						{
							assert ( A[cur].hasRight() );

							index_type const r = A[cur].r;

							if ( getBalance(r) > 0 )
							{
								#if 0
								std::cerr << "[V] single left" << std::endl;
								#endif
								cur = rotateLeft(cur);
							}
							else
							{
								#if 0
								std::cerr << "[V] right/left" << std::endl;
								#endif
								cur = rotateRightLeft(cur);
							}

							assert ( getBalance(cur) == 0 );
						}

						// balance should now be in range
						assert ( getBalance(cur) >= -1 && getBalance(cur) <= 1 );

						A[cur].h = 1+std::max(getHeight(A[cur].l),getHeight(A[cur].r));
						A[cur].n = 1+getN(A[cur].l)+getN(A[cur].r);

						// set new root if node has no parent
						if ( A[cur].p == nullindex )
							root = cur;

						// move to parent
						cur = A[cur].p;
					}

					return true;
				}
			}

			void clear()
			{
				index_type cur = root;

				if ( cur != nullindex )
				{
					std::stack< index_type > S;
					S.push(cur);

					while ( !S.empty() )
					{
						index_type top = S.top();
						S.pop();

						node_type const & N = A[top];

						if ( N.hasLeft() )
							S.push(N.l);
						if ( N.hasRight() )
							S.push(N.r);

						freeNode(top);
					}

					root = nullindex;
				}
			}

			bool empty() const
			{
				return (root == nullindex);
			}

			void checkDepth(std::ostream & errstr) const
			{
				if ( root != nullindex )
				{
					std::stack<index_type> S;
					S.push(root);

					while ( !S.empty() )
					{
						index_type const cur = S.top();
						S.pop();
						node_type const & N = A[cur];

						bool const ok = (getDepth(cur) == getHeight(cur));

						if ( ! ok )
						{
							errstr << "[E] failed depth check for node " << cur << std::endl;
							assert ( ok );
						}

						assert ( getDepth(cur) == 1+std::max(getDepth(A[cur].l),getDepth(A[cur].r)) );

						if ( N.hasRight() )
							S.push(N.r);
						if ( N.hasLeft() )
							S.push(N.l);
					}
				}
			}

			void checkBalance(std::ostream & errstr) const
			{
				if ( root != nullindex )
				{
					std::stack<index_type> S;
					S.push(root);

					while ( !S.empty() )
					{
						index_type const cur = S.top();
						S.pop();
						node_type const & N = A[cur];

						bool const ok = getBalance(cur) >= -1 && getBalance(cur) <= 1;

						if ( ! ok )
						{
							errstr << "[E] failed balance check for node " << cur << std::endl;
							assert ( ok );
						}

						if ( N.hasRight() )
							S.push(N.r);
						if ( N.hasLeft() )
							S.push(N.l);
					}
				}
			}

			void checkCount(std::ostream & /* errstr */) const
			{
				if ( root != nullindex )
				{
					std::stack<index_type> S;
					S.push(root);

					while ( !S.empty() )
					{
						index_type const cur = S.top();
						S.pop();
						node_type const & N = A[cur];

						assert ( getCount(cur) == getN(cur) );

						if ( N.hasRight() )
							S.push(N.r);
						if ( N.hasLeft() )
							S.push(N.l);
					}
				}
			}

			void checkParent(std::ostream & /* errstr */) const
			{
				if ( root != nullindex )
				{
					std::stack<index_type> S;
					S.push(root);

					while ( !S.empty() )
					{
						index_type const cur = S.top();
						S.pop();
						node_type const & N = A[cur];

						if ( A[cur].hasLeft() )
							assert ( A[A[cur].l].p == cur );
						if ( A[cur].hasRight() )
							assert ( A[A[cur].r].p == cur );

						if ( N.hasRight() )
							S.push(N.r);
						if ( N.hasLeft() )
							S.push(N.l);
					}
				}
			}

			void check(std::ostream & errstr) const
			{
				checkParent(errstr);
				checkDepth(errstr);
				checkBalance(errstr);
				checkCount(errstr);
			}

			void print(std::ostream & out) const
			{
				if ( !empty() )
					print(out,root);
			}

			// get iterator to element at position index of sorted list of elements contained in tree
			const_iterator operator[](::std::size_t rindex) const
			{
				return index(rindex);
			}

		};
	}
}
#endif
