/**
    libmaus2
    Copyright (C) 2021 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_SUFFIXSORT_BWTB3M_MERGETREE_HPP)
#define LIBMAUS2_SUFFIXSORT_BWTB3M_MERGETREE_HPP

#include <libmaus2/suffixsort/bwtb3m/MergeStrategyMergeBlock.hpp>
#include <libmaus2/suffixsort/bwtb3m/MergeStrategyBaseBlock.hpp>

namespace libmaus2
{
	namespace suffixsort
	{
		namespace bwtb3m
		{
			struct MergeTree;
			inline std::ostream & operator<<(std::ostream & out, MergeTree const & MT);

			struct MergeTree
			{
				public:
				MergeStrategyBlock::shared_ptr_type treeroot;
				std::map<uint64_t, MergeStrategyBlock *> node_map;
				std::vector<uint64_t> base_nodeid_vector;

				private:
				MergeTree & operator=(MergeTree const & M) = delete;
				MergeTree(MergeTree const & M) = delete;

				public:
				MergeTree(MergeStrategyBlock::shared_ptr_type r_treeroot)
				: treeroot(r_treeroot), node_map(), base_nodeid_vector()
				{
					if ( treeroot ) {
						treeroot->fillNodeMap(node_map);
						treeroot->collectBaseBlockIds(base_nodeid_vector);
					}
				}

				MergeTree(std::istream & in)
				:  treeroot(libmaus2::suffixsort::bwtb3m::MergeStrategyMergeInput::loadBlock(in)), node_map(), base_nodeid_vector()
				{
					if ( treeroot ) {
						treeroot->fillNodeMap(node_map);
						treeroot->collectBaseBlockIds(base_nodeid_vector);
					}
				}

				std::vector<uint64_t> const & getBaseNodeIdVector() const
				{
					return base_nodeid_vector;
				}

				std::vector<MergeStrategyBaseBlock *> getBaseBlockVector()
				{
					std::vector<MergeStrategyBaseBlock *> V;
					for ( auto u : base_nodeid_vector )
						V.push_back(
							reinterpret_cast<MergeStrategyBaseBlock *>(&((*this)[u]))
						);
					return V;
				}

				MergeStrategyBlock & operator[](uint64_t const id)
				{
					auto const it = node_map.find(id);

					if ( it == node_map.end() ) {
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "MergeTree::operator[](" << id << "): node does not exist" << std::endl;
						lme.finish();
						throw lme;
					}

					return *(it->second);
				}

				::libmaus2::suffixsort::BwtMergeBlockSortResult const & getSortResult() const
				{
					if ( ! treeroot )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "MergeTree::getSortResult: tree is empty" << std::endl;
						lme.finish();
						throw lme;
					}

					return treeroot->sortresult;
				}

				void serialise(std::ostream & out) const {
					if ( treeroot )
						treeroot->vserialise(out);
				}

				void fillGapRequestObjects(uint64_t const t)
				{
					if ( treeroot )
						treeroot->fillGapRequestObjects(t);
				}

				bool equal(MergeTree const & O) const
				{
					if ( treeroot && !O.treeroot )
						return false;
					else if ( O.treeroot && !treeroot )
						return false;
					else if ( ! treeroot )
						return true;
					else
						return treeroot->equal(*(O.treeroot));
				}

				bool checkSerialisation() const
				{
					// run tree serialisation/deserialisation loop
					std::ostringstream ostr;
					serialise(ostr);

					std::istringstream istr(ostr.str());
					MergeTree t_mergetree(istr);

					if ( ! equal(t_mergetree) )
						return false;

					std::ostringstream astr;
					astr << *this;
					std::ostringstream bstr;
					bstr << t_mergetree;

					if ( astr.str() != bstr.str() )
						return false;

					return true;
				}

				void getMergeBlocks(
					std::vector < MergeStrategyMergeInternalBlock * > & V_internal,
					std::vector < MergeStrategyMergeInternalSmallBlock * > & V_internal_small,
					std::vector < MergeStrategyMergeExternalBlock * > & V_external
				)
				{
					for ( auto P : node_map ) {
						auto p = P.second;

						if ( dynamic_cast<MergeStrategyMergeInternalBlock *>(p) )
							V_internal.push_back(dynamic_cast<MergeStrategyMergeInternalBlock *>(p));
						else if ( dynamic_cast<MergeStrategyMergeInternalSmallBlock *>(p) )
							V_internal_small.push_back(dynamic_cast<MergeStrategyMergeInternalSmallBlock *>(p));
						else if ( dynamic_cast<MergeStrategyMergeExternalBlock *>(p) )
							V_external.push_back(dynamic_cast<MergeStrategyMergeExternalBlock *>(p));
					}
				}
			};

			inline std::ostream & operator<<(std::ostream & out, MergeTree const & MT)
			{
				if ( MT.treeroot )
					out << *(MT.treeroot);
				else
					out << "(nullptr)";

				return out;
			}
		}
	}
}
#endif
