/**
 *
 * Copyright 2021 German Tischler-Höhle. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of Miltenyi Biotec.
 **/
#if ! defined(LIBMAUS2_AVL_ATOMICAVLKEYPTRSET_HPP)
#define LIBMAUS2_AVL_ATOMICAVLKEYPTRSET_HPP

#include <atomic>
#include <sstream>
#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/util/atomic_shared_ptr.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 AtomicAVLKeyPtrSet
		{
			typedef _key_type key_type;
			typedef _index_type index_type;
			typedef _order_type order_type;
			typedef AtomicAVLKeyPtrSet<key_type,order_type,index_type> this_type;
			typedef this_type tree_type;

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

			struct ConstIterator
			{
				typedef std::forward_iterator_tag iterator_category;
				typedef libmaus2::util::atomic_shared_ptr<key_type> const & reference;
				typedef libmaus2::util::atomic_shared_ptr<key_type> const * pointer;
				typedef libmaus2::util::atomic_shared_ptr<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 libmaus2::util::atomic_shared_ptr<key_type> const & reference;
				typedef libmaus2::util::atomic_shared_ptr<key_type> const * pointer;
				typedef libmaus2::util::atomic_shared_ptr<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 AtomicAVLKeyPtrSetNode
			{
				std::atomic<unsigned char> h;
				std::atomic<index_type> l;
				std::atomic<index_type> r;
				std::atomic<index_type> p;
				libmaus2::util::atomic_shared_ptr<key_type> k;
				std::atomic<std::size_t> n;

				AtomicAVLKeyPtrSetNode(
					unsigned char rh,
					index_type rl,
					index_type rr,
					index_type rp,
					libmaus2::util::shared_ptr<key_type> rk,
					::std::size_t rn
				) : h(rh), l(rl), r(rr), p(rp), k(rk), n(rn) {}
				AtomicAVLKeyPtrSetNode(AtomicAVLKeyPtrSetNode const & O)
				: h(O.h.load()), l(O.l.load()), r(O.r.load()), p(O.p.load()), k(O.k.load()), n(O.n.load())
				{

				}

				AtomicAVLKeyPtrSetNode & operator=(AtomicAVLKeyPtrSetNode const & O)
				{
					if ( this != &O )
					{
						h.store(O.h.load());
						l.store(O.l.load());
						r.store(O.r.load());
						p.store(O.p.load());
						k.store(O.k.load());
						n.store(O.n.load());
					}
					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.load())) << ",n=" << n << ")";

					return out;
				}
			};

			typedef AtomicAVLKeyPtrSetNode node_type;
			order_type order;

			// memory for tree nodes
			libmaus2::util::atomic_shared_ptr< char[] > AC;
			// pointer to tree node array
			std::atomic < node_type * > A;
			// capacity of A
			std::atomic < std::size_t > a_n;
			// current number of elements valid in A
			std::atomic < std::size_t > a_f;

			libmaus2::util::atomic_shared_ptr< std::atomic<index_type>[] > F;
			std::atomic < std::size_t > f_n;
			std::atomic < std::size_t > f_f;

			index_type root; // pointer to root node

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

			index_type allocateNode(
				unsigned char rh,
				index_type rl,
				index_type rr,
				index_type rp,
				libmaus2::util::shared_ptr<key_type> rk,
				::std::size_t rn
			)
			{
				if ( ! f_f.load() )
				{
					std::size_t const l_a_n = a_n.load();
					std::size_t const l_a_f = a_f.load();

					if ( l_a_f == l_a_n )
					{
						std::size_t const n_a_n = l_a_n ? (2*l_a_n) : 1;

						libmaus2::util::shared_ptr<char[]> TAC(new char[n_a_n * sizeof(node_type)]);
						node_type * LA = reinterpret_cast<node_type *>(TAC.get());

						node_type * from = A.load();
						node_type * to = LA;

						for ( std::size_t i = 0; i < l_a_n; ++i )
							new(&to[i]) node_type(from[i]);

						callDestructors();

						AC.store(TAC);
						A.store(LA);

						a_n.store(n_a_n);
					}

					index_type const id = a_f++;

					new (&A.load()[id]) node_type(rh,rl,rr,rp,rk,rn);

					return id;
				}
				else
				{
					index_type const id = F.load()[--f_f].load();

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

					return id;
				}
			}

			void freeNode(index_type const id)
			{
				std::size_t const l_f_n = f_n.load();
				std::size_t const l_f_f = f_f.load();

				if ( l_f_f == l_f_n )
				{
					std::size_t const n_f_n = l_f_n ? (2*l_f_n) : 1;

					libmaus2::util::shared_ptr< std::atomic<index_type>[] > LF(
						new std::atomic<index_type>[n_f_n]
					);

					std::atomic<index_type> * toF = LF.get();
					std::atomic<index_type> * fromF = F.load().get();

					for ( std::size_t i = 0; i < l_f_n; ++i )
						toF[i].store(fromF[i].load());

					F.store(LF);
					f_n.store(n_f_n);
				}

				F.load()[f_f++].store(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("AtomicAVLKeyPtrSet::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("AtomicAVLKeyPtrSet::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";
					}
				}
			}

			bool compare(
				libmaus2::util::shared_ptr<key_type> const & ka,
				libmaus2::util::shared_ptr<key_type> const & kb
			) const
			{
				return order(*ka,*kb);
			}

			bool compare(
				libmaus2::util::shared_ptr<key_type> const & ka,
				libmaus2::util::atomic_shared_ptr<key_type> const & kb
			) const
			{
				return compare(ka,kb.load());
			}

			bool compare(
				libmaus2::util::atomic_shared_ptr<key_type> const & ka,
				libmaus2::util::shared_ptr<key_type> const & kb
			) const
			{
				return compare(ka.load(),kb);
			}

			bool compare(
				libmaus2::util::atomic_shared_ptr<key_type> const & ka,
				libmaus2::util::atomic_shared_ptr<key_type> const & kb
			) const
			{
				return compare(ka.load(),kb.load());
			}

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

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

			~AtomicAVLKeyPtrSet()
			{
				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();
			}

			const_iterator operator[](::std::size_t rindex) const
			{
				return index(rindex);
			}


			::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(libmaus2::util::shared_ptr<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 ( compare(k,A[cur].k) )
					{
						prev = cur;
						cur = A[cur].l;
					}
					// if k is greater than current node value
					else if ( compare(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 ( compare(*it,k) || compare(k,*it) );

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

				return it;
			}

			const_iterator find(libmaus2::util::shared_ptr<key_type> const & k) const
			{
				const_iterator const it = lower_bound(k);

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

			bool erase(libmaus2::util::shared_ptr<key_type> const & k)
			{
				const_iterator it = lower_bound(k);

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

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

				return erase(it);
			}

			template<typename type>
			static void swapAtomic(std::atomic<type> & a, std::atomic<type> & b)
			{
				type const t = a.load();
				a.store(b.load());
				b.store(t);
			}

			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);

					swapAtomic(A[cur].l,A[next].l);
					swapAtomic(A[cur].r,A[next].r);
					swapAtomic(A[cur].n,A[next].n);
					swapAtomic(A[cur].h,A[next].h);
					swapAtomic(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(libmaus2::util::shared_ptr<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 ( compare(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 ( compare(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);
			}
		};
	}
}
#endif
