/*
    libmaus2
    Copyright (C) 2020 German Tischler-Höhle

    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_PARALLEL_SIMPLETHREADPOOLTHREADIDTREE_HPP)
#define LIBMAUS2_PARALLEL_SIMPLETHREADPOOLTHREADIDTREE_HPP

#include <libmaus2/parallel/SimpleThreadPoolThreadIdNode.hpp>
#include <libmaus2/parallel/StdMutex.hpp>

namespace libmaus2
{
	namespace parallel
	{
		struct SimpleThreadPoolThreadIdTree
		{
			static SimpleThreadPoolThreadIdTree globalTree;
			static StdMutex globalTreeLock;

			libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadIdNode> Anode;
			SimpleThreadPoolThreadIdNode * root;

			SimpleThreadPoolThreadIdTree(
				SimpleThreadPoolThreadId const * A,
				SimpleThreadPoolThreadId const * E
			) : Anode(E-A), root(nullptr)
			{
				uint64_t const n = E-A;
				for ( uint64_t i = 1; i < n; ++i )
					assert ( A[i-1] < A[i] );

				struct StackNode
				{
					uint64_t left;
					uint64_t right;
					SimpleThreadPoolThreadIdNode * parent;
					bool is_left;

					StackNode() {}
					StackNode(
						uint64_t rleft,
						uint64_t rright,
						SimpleThreadPoolThreadIdNode * rparent,
						bool ris_left
					) : left(rleft), right(rright), parent(rparent), is_left(ris_left)
					{

					}
				};

				std::stack<StackNode> S;
				uint64_t o = 0;

				if ( E-A )
					S.push(StackNode(0/*left*/,E-A/*right*/,nullptr/*parent*/,false));

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

					uint64_t const d = p.right-p.left;
					uint64_t const m = p.left + d/2;

					assert ( m >= p.left );
					assert ( m < p.right );

					uint64_t const lo = o++;

					assert ( lo < Anode.size() );
					SimpleThreadPoolThreadIdNode & curnode = Anode[lo];
					curnode = SimpleThreadPoolThreadIdNode(A[m]);

					#if 0
					std::cerr << "pushed " << curnode.data.toString() << " at index " << lo;
					if ( p.parent )
						std::cerr << " parent " << p.parent->data.toString();
					std::cerr << std::endl;
					#endif

					if ( p.parent )
					{
						if ( p.is_left )
							p.parent->left  = &curnode;
						else
							p.parent->right = &curnode;
					}
					else
					{
						root = &Anode[lo];
					}

					if ( m+1 < p.right )
						S.push(StackNode(m+1,p.right,&Anode[lo],false /* is left */));
					if ( p.left < m )
						S.push(StackNode(p.left,m,&Anode[lo],true /* is left */));
				}

				#if 0
				for ( uint64_t i = 0; i < o; ++i )
				{
					std::cerr << "node[" << i << "]=" << Anode[i].data.toString();
					if ( Anode[i].left )
						std::cerr << " left " << Anode[i].left->data.toString();
					if ( Anode[i].right )
						std::cerr << " right " << Anode[i].right->data.toString();
					std::cerr << std::endl;
				}
				#endif
			}

			SimpleThreadPoolThreadIdNode const * find(std::thread::id const & rid) const
			{
				if ( root )
					return root->find(rid);
				else
					return nullptr;
			}

			SimpleThreadPoolThreadId const * findId(std::thread::id const & rid) const
			{
				SimpleThreadPoolThreadIdNode const * p = find(rid);

				if ( p )
					return &(p->data);
				else
					return nullptr;
			}


			std::ostream & print(std::ostream & out) const
			{
				if ( root )
					return root->print(out);
				else
					return out;
			}

			libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> enumerate() const
			{
				if ( root )
					return root->enumerate();
				else
					return libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId>();
			}

			void add(SimpleThreadPoolThreadId const * a_A, SimpleThreadPoolThreadId const * e_A)
			{
				libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> const I = enumerate();
				libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> const M = merge(a_A,e_A,I.begin(),I.end());

				#if 0
				for ( uint64_t i = 0; i < M.size(); ++i )
					std::cerr << "add[" << i << "]=" << M[i].toString() << std::endl;
				#endif

				SimpleThreadPoolThreadIdTree T(M.begin(),M.end());
				*this = T;
			}

			void remove(SimpleThreadPoolThreadId const * a_A, SimpleThreadPoolThreadId const * e_A)
			{
				libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> const I = enumerate();
				libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> const M = remove(I.begin(),I.end(),a_A,e_A);
				SimpleThreadPoolThreadIdTree T(M.begin(),M.end());
				*this = T;
			}

			static void addGlobal(SimpleThreadPoolThreadId const * a_A, SimpleThreadPoolThreadId const * e_A)
			{
				libmaus2::parallel::StdMutex::scope_lock_type slock(globalTreeLock);
				globalTree.add(a_A,e_A);
			}

			static void removeGlobal(SimpleThreadPoolThreadId const * a_A, SimpleThreadPoolThreadId const * e_A)
			{
				libmaus2::parallel::StdMutex::scope_lock_type slock(globalTreeLock);
				globalTree.remove(a_A,e_A);
			}

			static std::ostream & printGlobal(std::ostream & out)
			{
				libmaus2::parallel::StdMutex::scope_lock_type slock(globalTreeLock);
				return globalTree.print(out);
			}

			static SimpleThreadPoolThreadId const * findIdGlobal(std::thread::id const & rid)
			{
				libmaus2::parallel::StdMutex::scope_lock_type slock(globalTreeLock);
				return globalTree.findId(rid);
			}

			static libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> merge(
				SimpleThreadPoolThreadId const * a_A,
				SimpleThreadPoolThreadId const * e_A,
				SimpleThreadPoolThreadId const * a_B,
				SimpleThreadPoolThreadId const * e_B
			)
			{
				uint64_t const n0 = e_A-a_A;
				uint64_t const n1 = e_B-a_B;
				libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> O(n0+n1);
				uint64_t o = 0;

				while ( a_A < e_A && a_B < e_B )
					if ( *a_A < *a_B )
						O[o++] = *(a_A++);
					else
						O[o++] = *(a_B++);

				while ( a_A < e_A )
					O[o++] = *(a_A++);
				while ( a_B < e_B )
					O[o++] = *(a_B++);

				assert ( o == n0+n1 );

				return O;
			}

			// compute A - B
			static libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> remove(
				SimpleThreadPoolThreadId const * a_A,
				SimpleThreadPoolThreadId const * e_A,
				SimpleThreadPoolThreadId const * a_B,
				SimpleThreadPoolThreadId const * e_B
			)
			{
				SimpleThreadPoolThreadId const * t_a_A = a_A;
				SimpleThreadPoolThreadId const * t_a_B = a_B;

				uint64_t o = 0;

				while ( a_A < e_A && a_B < e_B )
					// not in B, so keep
					if ( *a_A < *a_B )
					{
						++o;
						++a_A;
					}
					// not in A
					else if ( *a_B < *a_A )
					{
						++a_B;
					}
					// in both, discard
					else
					{
						++a_A;
						++a_B;
					}

				while ( a_A < e_A )
				{
					++o;
					++a_A;
				}
				while ( a_B < e_B )
				{
					++a_B;
				}

				libmaus2::autoarray::AutoArray<SimpleThreadPoolThreadId> O(o);
				a_A = t_a_A;
				a_B = t_a_B;

				o = 0;

				while ( a_A < e_A && a_B < e_B )
					// not in B, so keep
					if ( *a_A < *a_B )
					{
						O[o++] = *(a_A++);
					}
					// not in A
					else if ( *a_B < *a_A )
					{
						++a_B;
					}
					// in both, discard
					else
					{
						++a_A;
						++a_B;
					}

				while ( a_A < e_A )
				{
					O[o++] = *(a_A++);
				}
				while ( a_B < e_B )
				{
					++a_B;
				}

				assert ( o == O.size() );

				return O;
			}
		};
	}
}
#endif
