#include <cassert>
#include <cmath>
#include <map>
#include <algorithm>
#include <stdexcept>
#include <sstream>
#include <ostream>

#include <boost/graph/copy.hpp>

#include "ggl/chem/MoleculeUtil.hh"
#include "ggl/chem/AromaticityPerception.hh"

namespace ggl {
namespace chem {

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

	bool
	AromaticityPerception::AdjacencyComp::
	operator() ( AromaticityPerception::AdjacencyData* e1, AromaticityPerception::AdjacencyData* e2)  {

		assert (e1 != NULL);
		assert (e2 != NULL);

		// check if (e1 smaller e2)

		 // check for equality
		bool bothSameOpenEdges = e1->openEdges == e2->openEdges;
		bool bothSameRemValence = e1->remValence == e2->remValence;

		 // less conditions
		bool e1NoOpenEdge = e1->openEdges == 0;
		bool e2NoOpenEdge = e2->openEdges == 0;
		bool e1LessOpenEdges = e1->openEdges < e2->openEdges;
		bool e1LessRemValence = e1->remValence < e2->remValence;
		bool e1LessAromEdges = e1->aromaticEdges < e2->aromaticEdges;
		bool e1OnlySingleEdges = e1->openEdges == e1->remValence;
		bool e2OnlySingleEdges = e2->openEdges == e2->remValence;

		////////////////// e1/e2 has no edges left

		  // no open edge check
		if (e1NoOpenEdge || e2NoOpenEdge) {
			  // e1 has to have no open edge to be smaller
			return	(!e1NoOpenEdge && e2NoOpenEdge);
		}

		////////////  both have at least 1 open edge

		  // single edges are smallest
		if (e1OnlySingleEdges && ! e2OnlySingleEdges) {
			return true;
		}

		  // single edges tie break via remaining valence
		if (e1OnlySingleEdges && e2OnlySingleEdges) {
			return e1LessRemValence || (bothSameRemValence && e1LessAromEdges);
		}

		  // final sorting via number of open edges
		return	e1LessOpenEdges
				||
				(bothSameOpenEdges && e1LessRemValence)
				||
				(bothSameOpenEdges && bothSameRemValence && e1LessAromEdges)
				;

	}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

	void
	AromaticityPerception::
	correctAromaticity( Molecule & mol, const bool checkValence )
	 throw (std::runtime_error)
	{
		  // clear temporary data
		clearData();
		  // identify all rings
		findAllRings( mol );

		  // prune rings to remove fused representation
		pruneFusedRings( allRings );

		  // prune rings that cannot be aromatic
		pruneNonSingleDoubleBondRings( allRings, mol );

		  // identify all aromatic rings
		identifyAromaticEdges(mol);

		  // relabel according to identified aromatic rings
		relabelMolecule(mol, checkValence);
	}

////////////////////////////////////////////////////////////////////////////////

	void
	AromaticityPerception::
	pruneNonSingleDoubleBondRings( std::vector< RingDescriptor* > & rings
					, const Molecule & mol )
	{

		  // access to edge label
		boost::property_map<	Molecule , PropEdgeLabel >
			::const_type edgeLabel = boost::get( PropEdgeLabel(), mol );

		const MoleculeUtil::BondLabelData * bond = NULL;

		  // check each ring
		for (size_t i=0; i<rings.size(); ) {

			bool remove = false;
			  // check each bond if not single, double, or aromatic bond
			for (EdgeSet::const_iterator e = rings.at(i)->edges.begin(); !remove && e != rings.at(i)->edges.end(); ++e) {
				  // get bond data
				bond = MoleculeUtil::getBondData( edgeLabel[
										  boost::edge( boost::vertex(e->first,mol), boost::vertex(e->second,mol), mol).first
				                                                                          ] );
				assert(bond != NULL);
				remove = bond->valence > 2 || bond->valence < 1;
			}
			  // check if this ring has to be removed
			if (remove) {
				  // delete element
				delete rings[i];
				  // delete entry
				rings.erase(rings.begin()+i);
			} else {
				  // go to next element
				++i;
			}
		}

	}

////////////////////////////////////////////////////////////////////////////////

	void
	AromaticityPerception::
	pruneFusedRings( std::vector< RingDescriptor* > & rings )
	{

		  // sort rings by size
		RingSizeLess ringSizeLess;
		std::sort( rings.begin(), rings.end(), ringSizeLess );

//std::cerr <<"\nNEXT\n";
		  // prune rings
		size_t numOfprunedRings = 0;
		for (int i=rings.size()-1; i>=0; i--) {
//std::cerr <<"next ring = ";
//for (RingList::const_iterator x=rings.at(i)->ring.begin(); x!=rings.at(i)->ring.end(); ++x) {
//	std::cerr <<*x <<" ";
//}
			bool canBePruned = false;
			for( size_t c=0; !canBePruned && c < (size_t)i; ++c ) {
				  // skip already deleted rings
				if (rings.at(c) == NULL) {
					continue;
				}
				  // stop if ring is of equal size or larger
				if (rings.at(c)->edges.size() >= rings.at(i)->edges.size()) {
					break;
				}
				  // check for containment of this ring c within ring i

				  // get iterators on edge sets to compare
				EdgeSet::const_iterator largeEdge = rings.at(i)->edges.begin(), largeEnd = rings.at(i)->edges.end();
				EdgeSet::const_iterator curEdge = rings.at(c)->edges.begin(), curEnd = rings.at(c)->edges.end();
				  // sequential check
				size_t curOverlap = 0;
				  // check only until up to two differences have been found
				while(largeEdge != largeEnd && curEdge != curEnd) {
					  // check if overlap
					if (*largeEdge == *curEdge) {
						++curOverlap;
						++largeEdge;
						++curEdge;
					  // check which counter to increase
					} else if (*largeEdge < *curEdge) {
						++largeEdge;
					} else {
						++curEdge;
					}
				}
				  // check if smaller current ring is contained excluding one edge
				  // if only one edge is left : ring is contained
				canBePruned = (curOverlap+1 == rings.at(c)->edges.size());

			}
			  // prune ring if obsolete
			if (canBePruned) {
				  // remove ring descriptor at position i
				delete rings[i];
				  // set to NULL to remember deletion
				rings[i] = NULL;
				  // count pruning
				numOfprunedRings++;
//std::cerr <<" pruned";
			}
//std::cerr <<"\n";
		}

		  // shift remaining rings to the front
		if (numOfprunedRings>0) {
			size_t fillPos = 0;
			for( size_t i=0; i<rings.size(); ++i ) {
				if (rings.at(i) != NULL) {
					  // check if overwrite is needed
					if (fillPos < i) {
						rings[fillPos] = rings[i];
					}
					  // increase overwrite counter
					fillPos++;
				}
			}
			  // drop deleted elements
			rings.resize(rings.size()-numOfprunedRings);
		}

	}

////////////////////////////////////////////////////////////////////////////////

	void
	AromaticityPerception::
	relabelMolecule( Molecule & result, const bool checkValence )
	 throw (std::runtime_error)
	{
//		  // new molecule to be filled
//		Molecule result(mol);

		  // property maps for access
		boost::property_map<	Molecule , PropNodeLabel >
			::type resultNodeLabel = boost::get( PropNodeLabel(), result );

		boost::property_map<	Molecule , PropNodeIndex >
			::type resultNodeIndex = boost::get( PropNodeIndex(), result );

		boost::property_map<	Molecule , PropEdgeLabel >
			::type resultEdgeLabel = boost::get( PropEdgeLabel(), result );

		  // collect all nodes participating in aromatic edges
		std::set< size_t > aromaticNodes;
		for (EdgeSet::const_iterator e = aromaticEdges.begin();
				e != aromaticEdges.end(); ++e)
		{
			aromaticNodes.insert(e->first);
			aromaticNodes.insert(e->second);
		}

		  // relabel aromatic nodes
		boost::graph_traits<Molecule>::vertex_iterator vi, vi_end;
		boost::tie(vi, vi_end) = vertices( result );
		for (; vi != vi_end; ++vi) {
			  // check if the node should be aromatic
			const bool shouldBeAromatic = aromaticNodes.find(resultNodeIndex[*vi]) != aromaticNodes.end();
			  // get atom data of current label to check if currently aromatic
			std::string nodeLabel = resultNodeLabel[*vi];
			const std::string atomLabel = MoleculeUtil::getAtom(nodeLabel);
			const MoleculeUtil::AtomLabelData * atomData
				= MoleculeUtil::getAtomData(atomLabel);
			assert( atomData != NULL /*unknown atom label*/);
			  // check if relabeling is needed
			if (	((atomData->isAromatic != 0 && !shouldBeAromatic)
					||	(atomData->isAromatic == 0 && shouldBeAromatic))
				&& (MoleculeUtil::getAromaticPendant(atomLabel) != NULL))
			{
				  // relabel
				  // get aromatic/non-aromatic pendant
				const std::string* newAtom = MoleculeUtil::getAromaticPendant(atomLabel);
				assert( newAtom != NULL /*obviously we tried to relabel a non-aromatic atom*/);
				  // replace atom label within node label (can be complex label)
				nodeLabel.replace( nodeLabel.find(atomLabel), newAtom->size(), *newAtom );
				  // set new node label
				resultNodeLabel[*vi] = nodeLabel;
			}
		}

		  // relabel edges

		  // get access to edge descriptors
		boost::graph_traits<Molecule>::edge_iterator ei, e_end;
		boost::tie(ei, e_end) = boost::edges( result );
		EdgeSet arom2nonaromEdges;
		  // dummy label to set for all edges that are currently aromatic but
		  // have to be relabeled to non-aromatic
		const std::string UNCERTAIN_EDGE_LABEL = std::string("????");
		  // check all edges if they have to be relabeled
		for (; ei != e_end; ++ei) {
			Edge curEdge( resultNodeIndex[boost::source(*ei,result)]
								, resultNodeIndex[boost::target(*ei,result)] );
			if (curEdge.first > curEdge.second) {
				curEdge = Edge(curEdge.second, curEdge.first);
			}
			  // check if the edge should be aromatic
			const bool shouldBeAromatic = aromaticEdges.find(curEdge) != aromaticEdges.end();
			std::string bondLabel = resultEdgeLabel[*ei];
			const MoleculeUtil::BondLabelData * bondData
						= MoleculeUtil::getBondData( bondLabel );
			assert( bondData != NULL /*unknown bond label*/);
			  // check if aromatic relabeling needed
			if ( bondData->isAromatic == 0 && shouldBeAromatic ) {
				const std::string* newBond = MoleculeUtil::getAromaticPendant(bondLabel);
				assert( newBond != NULL /*obviously we tried to relabel a non-aromatic bond*/);
				resultEdgeLabel[*ei] = *newBond;
			} else
			  // check if non-aromatic relabeling needed
			if ( bondData->isAromatic != 0 && !shouldBeAromatic ) {
				arom2nonaromEdges.insert(curEdge);
				resultEdgeLabel[*ei] = UNCERTAIN_EDGE_LABEL;
			}
		}

		  // relabel formerly aromatic edges to non-aromatic

		if ( !arom2nonaromEdges.empty() ) {

			  // get labels for single and double bonds from MoleculeUtil
			const MoleculeUtil::BondDataMap & bondDataMap = MoleculeUtil::getBondData();
			std::string singleBondLabel="", doubleBondLabel="", aromBondLabel="";
			for (MoleculeUtil::BondDataMap::const_iterator bd=bondDataMap.begin();
					bd != bondDataMap.end(); ++bd)
			{
				if (bd->second.valence == 1 && bd->second.isAromatic == 0) {
					singleBondLabel = bd->first;
				}
				if (bd->second.valence == 2 && bd->second.isAromatic == 0) {
					doubleBondLabel = bd->first;
				}
				if (bd->second.valence == 1 && bd->second.isAromatic != 0) {
					aromBondLabel = bd->first;
				}
			}
			assert(singleBondLabel != "");
			assert(doubleBondLabel != "");
			assert(aromBondLabel != "");

			std::vector< AdjacencyData* > nodes2relabel(boost::num_vertices(result), NULL);
			std::vector< AdjacencyData* >::const_iterator nIt;

			size_t nodes2relabelCount = 0;
			for( EdgeSet::const_iterator e = arom2nonaromEdges.begin();
					e != arom2nonaromEdges.end(); ++e )
			{
				AdjacencyData *curNode = NULL;
				curNode = nodes2relabel[ e->first ];
				  // check if adjacent from node was already handled, if not do
				if (curNode == NULL) {
					boost::graph_traits<Molecule>::vertex_descriptor node
						= boost::vertex( e->first, result );
					int remainingValence = (int)MoleculeUtil::getAtomData( resultNodeLabel[node] )->valence;
					remainingValence += MoleculeUtil::getCharge( resultNodeLabel[node] );

					size_t adjAromaticEdges = 0;
					size_t openEdges = 0;
					  // iterate over all adjacent edges and reduce remaining valence
					boost::graph_traits<Molecule>::out_edge_iterator oei, oe_end;
					boost::tie(oei, oe_end) = boost::out_edges( node, result );
					for (; oei != oe_end; ++oei) {
						  // update information based on already labeled edges
						if (resultEdgeLabel[*oei] != UNCERTAIN_EDGE_LABEL) {
							remainingValence -= (int)MoleculeUtil::getBondData( resultEdgeLabel[*oei] )->valence;
							adjAromaticEdges += (MoleculeUtil::getBondData( resultEdgeLabel[*oei] )->isAromatic!=0?1:0);
						} else {
							  // count edges to be relabeled
							++openEdges;
						}
					}
					assert(adjAromaticEdges == 0 || adjAromaticEdges >= 2  /*either involved in at least one aromatic ring or not at all*/);
					  // insert new node information
					nodes2relabel[e->first] = new AdjacencyData( e->first, remainingValence, openEdges, adjAromaticEdges );
					++nodes2relabelCount;
				}
				  // check if adjacent target node was already handled, if not do
				curNode = nodes2relabel[ e->second ];
				if (curNode == NULL) {
					boost::graph_traits<Molecule>::vertex_descriptor node
						= boost::vertex( e->second, result );
					int remainingValence = (int)MoleculeUtil::getAtomData( resultNodeLabel[node] )->valence;
					remainingValence += MoleculeUtil::getCharge( resultNodeLabel[node] );

					size_t adjAromaticEdges = 0;
					size_t openEdges = 0;
					  // iterate over all adjacent edges and reduce remaining valence
					boost::graph_traits<Molecule>::out_edge_iterator oei, oe_end;
					boost::tie(oei, oe_end) = boost::out_edges( node, result );
					for (; oei != oe_end; ++oei) {
						  // update information based on already labeled edges
						if (resultEdgeLabel[*oei] != UNCERTAIN_EDGE_LABEL) {
							remainingValence -= (int)MoleculeUtil::getBondData( resultEdgeLabel[*oei] )->valence;
							adjAromaticEdges += (MoleculeUtil::getBondData( resultEdgeLabel[*oei] )->isAromatic!=0?1:0);
						} else {
							  // count edges to be relabeled
							++openEdges;
						}
					}
					assert(adjAromaticEdges == 0 || adjAromaticEdges >= 2  /*either involved in at least one aromatic ring or not at all*/);
					  // insert new node information
					nodes2relabel[e->second] = new AdjacencyData( e->second, remainingValence, openEdges, adjAromaticEdges );
					++nodes2relabelCount;
				}
			}
			  // get sorted access to the adjacent nodes
			std::vector< AdjacencyData* > nodes2relabelSorted(nodes2relabelCount,NULL);
			size_t i=0;
			for (nIt = nodes2relabel.begin(); nIt != nodes2relabel.end(); ++nIt) {
				if( *nIt != NULL) {
					  // store pointer to according AdjacencyData object
					nodes2relabelSorted[i] = *nIt;
					++i;
				}
			}
			assert( i == nodes2relabelCount );

//			std::cerr <<"\n DEBUG after init :\n";
//			for (size_t x=0; x < nodes2relabelSorted.size(); ++x) {
//				std::cerr <<"    " <<x <<" " <<nodes2relabelSorted.at(x);
//				if (nodes2relabelSorted.at(x) != NULL) {
//					std::cerr <<" "<< "id " <<nodes2relabelSorted.at(x)->nodeID <<" rem " <<nodes2relabelSorted.at(x)->remValence <<" oe " <<nodes2relabelSorted.at(x)->openEdges <<" ae " <<nodes2relabelSorted.at(x)->aromaticEdges;
//				}
//				std::cerr <<"\n";
//			}

			std::sort( nodes2relabelSorted.begin(), nodes2relabelSorted.end(), AdjacencyComp() );

//			std::cerr <<"\n DEBUG after first sort :\n";
//			for (size_t x=0; x < nodes2relabelSorted.size(); ++x) {
//				std::cerr <<"    " <<x <<" " <<nodes2relabelSorted.at(x);
//				if (nodes2relabelSorted.at(x) != NULL) {
//					std::cerr <<" "<< "id " <<nodes2relabelSorted.at(x)->nodeID <<" rem " <<nodes2relabelSorted.at(x)->remValence <<" oe " <<nodes2relabelSorted.at(x)->openEdges <<" ae " <<nodes2relabelSorted.at(x)->aromaticEdges;
//				}
//				std::cerr <<"\n";
//			}

			try {

			// iterate relabeling of front entry + sort till all done or
			// no deterministic relabeling possible
			while ( !nodes2relabelSorted.empty() && (*nodes2relabelSorted.begin())->openEdges > 0 )
			{

				AdjacencyData & curNode = *(*nodes2relabelSorted.begin());

//std::cerr <<" DEBUG curNode : id = "<<curNode.nodeID <<" val = " <<curNode.valence <<" edges = " <<curNode.edges <<" arom = " <<curNode.aromaticEdges<<"\n";
				// iterate over all uncertain edges and relabel
				boost::graph_traits<Molecule>::out_edge_iterator oei, oe_end;
				boost::tie(oei, oe_end) = boost::out_edges( boost::vertex( curNode.nodeID, result ), result );

				  // check if only single bonds left
				if ( curNode.remValence == curNode.openEdges ) {
					// find all edges and label with valence 1
					for (; oei != oe_end; ++oei) {
						if (resultEdgeLabel[*oei] == UNCERTAIN_EDGE_LABEL) {
							resultEdgeLabel[*oei] = singleBondLabel;
							// update adjacent node information
							size_t targetNode = resultNodeIndex[boost::target( *oei, result )];
							assert(nodes2relabel[ targetNode ] != NULL);
							// check if valence of target node compatible
							if ( checkValence && (nodes2relabel[ targetNode ]->openEdges == 0 || nodes2relabel[ targetNode ]->remValence == 0) ) {
								throw std::runtime_error("ggl::chem::AromaticityPerception::relabelMolecule : tried to add single bond but target has no valence left");
							}
							nodes2relabel[ targetNode ]->openEdges--;
							nodes2relabel[ targetNode ]->remValence -= 1;
							// check if remaining valence of target node sufficient
							if ( checkValence && (nodes2relabel[ targetNode ]->openEdges > nodes2relabel[ targetNode ]->remValence) )
							{
								throw std::runtime_error("ggl::chem::AromaticityPerception::relabelMolecule : after single bond insert not enough valence left within target node");
							}
						}
					}
				} else
				if ( curNode.openEdges == 1 ) {
					// find edge and label according to valence
					for (; oei != oe_end; ++oei) {
						if (resultEdgeLabel[*oei] == UNCERTAIN_EDGE_LABEL) {
							size_t edgeValence = 0;
							// relabel according to valence
							switch (curNode.remValence) {
							case 1 : resultEdgeLabel[*oei] = singleBondLabel;
								edgeValence = 1;
								break;
							case 2 :
								  // check if node is already involved in aromatic ring
								if (curNode.aromaticEdges >= 2) {
									  // one electron will be used by the aromatic ring
									  // thus only one left for a single bond
									resultEdgeLabel[*oei] = singleBondLabel;
									edgeValence = 1;
								} else {
									  // two electrons available for the bond
									resultEdgeLabel[*oei] = doubleBondLabel;
									edgeValence = 2;
								}
								break;
							case 3 :
								  // check if involved in aromatic ring -> error if not
								if (curNode.aromaticEdges < 2) {
									throw std::runtime_error("ggl::chem::AromaticityPerception::relabelMolecule : remaining atom valence left for a single bond relabeling is too high (>2), no canonical labeling possible");
								}
								  // two electrons available for the bond
								resultEdgeLabel[*oei] = doubleBondLabel;
								edgeValence = 2;
								break;
							case 0 :
								throw std::runtime_error("ggl::chem::AromaticityPerception::relabelMolecule : remaining atom valence for bond relabeling is zero, no relabeling possible");
							default:
								throw std::runtime_error("ggl::chem::AromaticityPerception::relabelMolecule : remaining atom valence left for a single bond relabeling is too high (>2), no canonical labeling possible");
							}
							// update adjacent node information
							size_t targetNode = resultNodeIndex[boost::target( *oei, result )];
							assert(nodes2relabel[targetNode] != NULL);
							// check if valence of target node compatible
							if ( checkValence && (nodes2relabel[ targetNode ]->openEdges == 0 || nodes2relabel[ targetNode ]->remValence < edgeValence) ) {
								throw std::runtime_error("ggl::chem::AromaticityPerception::relabelMolecule : tried to add bond but target has no valence left");
							}
							nodes2relabel[ targetNode ]->openEdges--;
							nodes2relabel[ targetNode ]->remValence -= edgeValence;
							// check if remaining valence of target node sufficient
							if ( checkValence && (nodes2relabel[ targetNode ]->openEdges > nodes2relabel[ targetNode ]->remValence) )
							{
//for (EdgeSet::const_iterator ae=aromaticEdges.begin(); ae!=aromaticEdges.end(); ++ae) {
//	std::cerr <<" " <<ae->first <<"-" <<ae->second;
//}
//for (std::vector< AdjacencyData* >::const_iterator nIt = nodes2relabelSorted.begin(); nIt != nodes2relabelSorted.end(); ++nIt, ++i) {
//	std::cerr <<" list : id = "<<(*nIt)->nodeID <<" val = " <<(*nIt)->valence <<" edges = " <<(*nIt)->edges <<" arom = " <<(*nIt)->aromaticEdges;
//}
//std::cerr	<<"\n\n"
//		<<"current relabeled graph:\n"
//		<<Molecule_Graph(result)
//		<<"\n"
//		<<"last edge = ("<<curNode.nodeID <<","<<targetNode<<")\n";

								throw std::runtime_error("ggl::chem::AromaticityPerception::relabelMolecule : after bond insert not enough valence left within target node");
							}
						}
					}
				} else {

					// NO DETERMINISTIC NON-AROMATIC LABELING POSSIBLE !!!
					// due to ordering: all following nodes have the same problem
					// or are already correctly relabeled

//					std::cout <<"\n ERROR : deterministic labeling not possible !\n cur node = "
//							<<curNode.nodeID <<" open = " <<curNode.openEdges <<" val = " <<curNode.remValence <<" arom = " <<curNode.aromaticEdges <<"\n"
//							<<Molecule_Graph(result)
//							<<std::endl;

					break;  // -> break and apply special handling

				}

				  // update curNode data
				curNode.remValence = 0;
				curNode.openEdges = 0;

				--nodes2relabelCount;

				  // update sorting due to changes in adjacent nodes' data
				std::swap( *nodes2relabelSorted.begin(), *(nodes2relabelSorted.begin()+nodes2relabelCount) );
				std::sort( nodes2relabelSorted.begin(), nodes2relabelSorted.begin()+nodes2relabelCount, AdjacencyComp() );

			}

			  // check if there are any edges left to be labeled non-aromatic
			  // but no deterministic labeling possible
			  // --> make these bonds/rings aromatic
			if (!nodes2relabelSorted.empty() && (*nodes2relabelSorted.begin())->openEdges > 0) {
				  // container that will hold all remaining edges to rename
				EdgeSet remainingEdges;
				boost::tie(ei, e_end) = boost::edges( result );
				  // check all edges if they have to be relabeled
				  // TODO slow : replace by iteration of ring bonds or dedicated container of non-aromatic bonds
				for (; ei != e_end; ++ei) {
					  // add uncertain edge to container
					if (resultEdgeLabel[*ei] == UNCERTAIN_EDGE_LABEL) {
						Edge curEdge( resultNodeIndex[boost::source(*ei,result)]
											, resultNodeIndex[boost::target(*ei,result)] );
						if (curEdge.first > curEdge.second) {
							curEdge = Edge(curEdge.second, curEdge.first);
						}
						remainingEdges.insert(curEdge);
					}
				}
				  // iterate until all edges have been assigned
				while( !remainingEdges.empty() ) {
					  // find RingDescriptor that covers most remaining edges
					RingDescriptor *nextRing = NULL;
					size_t maxEdgeOverlap = 0;
					for (size_t i=0; i<allRings.size(); ++i) {
						  // check if prediction was non-aromatic
						if (allRings.at(i)->predState == RingDescriptor::NonAromatic) {
							size_t curOverlap = 0;
							EdgeSet::const_iterator remainEdge = remainingEdges.begin(), remainEnd = remainingEdges.end();
							EdgeSet::const_iterator curEdge = allRings.at(i)->edges.begin(), curEnd = allRings.at(i)->edges.end();
							while(remainEdge != remainEnd && curEdge != curEnd) {
								  // check if overlap
								if (*remainEdge == *curEdge) {
									++curOverlap;
									++remainEdge;
									++curEdge;
								  // check which counter to increase
								} else if (*remainEdge < *curEdge) {
									++remainEdge;
								} else {
									++curEdge;
								}
							}
							  // check if new maximum found
							if (curOverlap > maxEdgeOverlap) {
								  // update maximum data
								maxEdgeOverlap = curOverlap;
								nextRing = allRings[i];
							}
						}
					}

					  // check if the remaining edges are part of a known ring
					if (nextRing == NULL) {

						// TODO check if now a deterministic labeling is possible ?!?!

						std::stringstream err;
						err	<<"\n RUNTIME ERROR in ggl::chem::AromaticityPerception::relabelMolecule :\n\n"
								<<" No deterministic non-aromatic relabeling possible. \n"
								<<"\n"
								<<" Remaining (formerly aromatic) edges that cannot be assigned are not part of any (predicted) ring.\n"
								<<" Edges = ";
						for (EdgeSet::const_iterator edge=remainingEdges.begin(); edge!=remainingEdges.end(); ++edge) {
							err <<" " <<edge->first <<"-" <<edge->second <<", ";
						}
	//					err <<"\n";
	//					for (std::vector< AdjacencyData* >::const_iterator nIt = nodes2relabelSorted.begin(); nIt != nodes2relabelSorted.end(); ++nIt, ++i) {
	//						err <<" list : id = "<<(*nIt)->nodeID <<" val = " <<(*nIt)->valence <<" edges = " <<(*nIt)->edges <<" arom = " <<(*nIt)->aromaticEdges<<"\n";
	//					}
						err	<<"\n"
								<<" Current relabeled graph:\n"
								<<Molecule_Graph(result)
								<<"\n";

						throw std::runtime_error(err.str());

					}

					  // make this ring aromatic as well
					nextRing->predState = RingDescriptor::Aromatic;

					  // relabel all nodes if not already aromatic
					for (RingList::const_iterator curID = nextRing->ring.begin(); curID != nextRing->ring.end(); ++curID) {
						  // get access to according node
						Molecule::vertex_descriptor curNode = boost::vertex( *curID, result );
						  // get atom data of current label to check if currently aromatic
						std::string nodeLabel = resultNodeLabel[curNode];
						const std::string atomLabel = MoleculeUtil::getAtom(nodeLabel);
						const MoleculeUtil::AtomLabelData * atomData
							= MoleculeUtil::getAtomData(atomLabel);
						assert( atomData != NULL /*unknown atom label*/);
						  // check if relabeling is needed
						if (	(atomData->isAromatic == 0)
							&& (MoleculeUtil::getAromaticPendant(atomLabel) != NULL))
						{
							  // relabel
							  // get aromatic/non-aromatic pendant
							const std::string* newAtom = MoleculeUtil::getAromaticPendant(atomLabel);
							assert( newAtom != NULL /*obviously we tried to relabel a non-aromatic atom*/);
							  // replace atom label within node label (can be complex label)
							nodeLabel.replace( nodeLabel.find(atomLabel), newAtom->size(), *newAtom );
							  // set new node label
							resultNodeLabel[curNode] = nodeLabel;
						}
					}

					  // relabel ring edges to aromatic labels
					for (EdgeSet::const_iterator curEdge = nextRing->edges.begin(); curEdge != nextRing->edges.end(); ++curEdge) {
						  // get access to according edge
						Molecule::edge_descriptor curResEdge = boost::edge( boost::vertex(curEdge->first,result)
																		, boost::vertex(curEdge->second,result), result ).first;
						  // check if edge label still unknown
						if (resultEdgeLabel[curResEdge] == UNCERTAIN_EDGE_LABEL) {
							  // make this an aromatic edge
							resultEdgeLabel[curResEdge] = aromBondLabel;
							  // remove from remaining edges
							remainingEdges.erase(*curEdge);
							  // skip remaining handling for this edge
							continue;
						}
						  // check if the edge should be aromatic
						std::string bondLabel = resultEdgeLabel[curResEdge];
						const MoleculeUtil::BondLabelData * bondData
									= MoleculeUtil::getBondData( bondLabel );
						assert( bondData != NULL /*unknown bond label*/);
						  // check if aromatic relabeling needed
						if ( bondData->isAromatic == 0 ) {
							resultEdgeLabel[curResEdge] = aromBondLabel;
						}
					}
				} // while remainingEdges not empty
			}

			} catch (std::runtime_error & e) {
				 // remove local data
				for (nIt = nodes2relabel.begin(); nIt != nodes2relabel.end(); ++nIt) {
					if( *nIt != NULL) {
						delete (*nIt);
					}
				}
				  // remove temporary data
				clearData();
				  // forward error to next level
				throw std::runtime_error(e.what());
			}

			 // remove local data
			for (nIt = nodes2relabel.begin(); nIt != nodes2relabel.end(); ++nIt) {
				if( *nIt != NULL) {
					delete (*nIt);
				}
			}

		} // IF relabeling aromatic -> non-aromatic necessary

	}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

	AromaticityPerception::
	RingDescriptor::
	RingDescriptor()
	 : edges()
		, ring()
		, predState(Unknown)
		, predCertainty(0)
	{
	}

////////////////////////////////////////////////////////////////////////////////

	AromaticityPerception::
	RingDescriptor::
	RingDescriptor(	const RingList & ringList )
	 : edges()
		, ring(ringList)
		, predState(Unknown)
		, predCertainty(0)
	{
		  // generate edge list description
		RingList::const_iterator cur=ringList.begin(), last=ringList.begin();
		for (cur++; cur!=ringList.end(); ++cur,++last) {
			if (*cur<*last) {
				edges.insert( Edge(*cur,*last) );
			} else {
				edges.insert( Edge(*last,*cur) );
			}
		}
	}

////////////////////////////////////////////////////////////////////////////////

}} // namespaces


////////////////////////////////////////////////////////////////
// Orkid Media Engine
// Copyright 1996-2020, Michael T. Mayers.
// Distributed under the Boost Software License - Version 1.0 - August 17, 2003
// see http://www.boost.org/LICENSE_1_0.txt
////////////////////////////////////////////////////////////////

#include <ork/kernel/orklut.hpp>
#include <ork/math/plane.h>
#include <ork/lev2/gfx/meshutil/submesh.h>
#include <Eigen/Dense>
#include <Eigen/Sparse>

template class ork::orklut<std::string, ork::meshutil::submesh_ptr_t>;

namespace ork::meshutil {

const vertexpool vertexpool::EmptyPool;

/////////////////////////////////////////////////////////////////////////
submesh::submesh(const vertexpool& vpool)
    : _vtxpool(vpool)
    , _surfaceArea(0)
    , _mergeEdges(true) {
  for (int i = 0; i < kmaxsidesperpoly; i++)
    _polyTypeCounter[i] = 0;
}
/////////////////////////////////////////////////////////////////////////
// eigen to submesh converter for interfacing
//  with various python/numpy packages
/////////////////////////////////////////////////////////////////////////
submesh_ptr_t submeshFromEigen(
    const Eigen::MatrixXd& verts, //
    const Eigen::MatrixXi& faces,
    const Eigen::MatrixXd& uvs,
    const Eigen::MatrixXd& colors,
    const Eigen::MatrixXd& normals,
    const Eigen::MatrixXd& binormals,
    const Eigen::MatrixXd& tangents) {
  auto rval           = std::make_shared<submesh>();
  size_t numVerts     = verts.rows();
  size_t numFaces     = faces.rows();
  size_t sidesPerFace = faces.cols();
  size_t numUvs       = uvs.rows();
  size_t numColors    = colors.rows();
  size_t numNormals   = normals.rows();
  size_t numBinormals = binormals.rows();
  size_t numTangents  = tangents.rows();
  /////////////////////////////////////////////
  OrkAssert(verts.cols() == 3);                                          // make sure we have vec3's
  auto generateVertex = [&](int faceindex, int facevtxindex) -> vertex { //
    vertex outv;
    const Eigen::MatrixXi& face = faces.row(faceindex);
    int per_vert_index          = face(facevtxindex);
    /////////////////////////////////////////////
    // position
    /////////////////////////////////////////////
    auto inp_pos = verts.row(per_vert_index);
    outv.mPos    = fvec3(inp_pos(0), inp_pos(1), inp_pos(2));
    /////////////////////////////////////////////
    // normal
    /////////////////////////////////////////////
    auto donormal = [&](int index) {
      OrkAssert(normals.cols() == 3);
      auto inp  = normals.row(index);
      outv.mNrm = fvec3(inp(0), inp(1), inp(2));
    };
    if (numNormals == numVerts) // per vertex
      donormal(per_vert_index);
    else if (numNormals == numFaces) // per face
      donormal(faceindex);
    else if (numNormals == 0) {
    } // no normals
    else
      OrkAssert(false);
    /////////////////////////////////////////////
    // binormal
    /////////////////////////////////////////////
    auto dobinormal = [&](int index) {
      OrkAssert(binormals.cols() == 3);
      auto inp                 = binormals.row(index);
      outv.mUV[0].mMapBiNormal = fvec3(inp(0), inp(1), inp(2));
    };
    if (numBinormals == numVerts) // per vertex
      dobinormal(per_vert_index);
    else if (numBinormals == numFaces) // per face
      dobinormal(faceindex);
    else if (numBinormals == 0) {
    } // no binormals
    else
      OrkAssert(false);
    /////////////////////////////////////////////
    // tangent
    /////////////////////////////////////////////
    auto dotangent = [&](int index) {
      OrkAssert(tangents.cols() == 3);
      auto inp                = tangents.row(index);
      outv.mUV[0].mMapTangent = fvec3(inp(0), inp(1), inp(2));
    };
    if (numTangents == numVerts) // per vertex
      dotangent(per_vert_index);
    else if (numTangents == numFaces) // per face
      dotangent(faceindex);
    else if (numTangents == 0) {
    } // no tangents
    else
      OrkAssert(false);
    /////////////////////////////////////////////
    // texturecoord
    /////////////////////////////////////////////
    auto dotexcoord = [&](int index) {
      OrkAssert(uvs.cols() == 2);
      auto inp                 = uvs.row(index);
      outv.mUV[0].mMapTexCoord = fvec2(inp(0), inp(1));
    };
    if (numUvs == numVerts) // per vertex
      dotexcoord(per_vert_index);
    else if (numUvs == numFaces) // per face
      dotexcoord(faceindex);
    else if (numUvs == 0) {
    } // no texcoords
    else
      OrkAssert(false);
    /////////////////////////////////////////////
    // color
    /////////////////////////////////////////////
    auto docolor = [&](int index) {
      auto inp = colors.row(index);
      switch (colors.cols()) {
        case 1: // luminance
          outv.mCol[0] = fvec4(inp(0), inp(0), inp(0), 1);
          break;
        case 3: // rgb
          outv.mCol[0] = fvec4(inp(0), inp(1), inp(2), 1);
          break;
        case 4: // rgba
          outv.mCol[0] = fvec4(inp(0), inp(1), inp(2), inp(3));
          break;
        default:
          OrkAssert(false);
          break;
      }
    };
    if (numColors == numVerts)
      docolor(per_vert_index);
    else if (numColors == numFaces)
      docolor(faceindex);
    else if (numColors == 1)
      docolor(0);
    else if (numColors == 0)
      outv.mCol[0] = fvec4(1, 1, 1, 1);
    else
      OrkAssert(false);
    return outv;
  }; // auto generateVertex = [&](int faceindex, int facevtxindex) -> vertex { //
  /////////////////////////////////////////////
  for (int f = 0; f < numFaces; f++) {
    switch (sidesPerFace) {
      case 3: {
        auto o0 = rval->newMergeVertex(generateVertex(f, 0));
        auto o1 = rval->newMergeVertex(generateVertex(f, 1));
        auto o2 = rval->newMergeVertex(generateVertex(f, 2));
        rval->MergePoly(poly(o0, o1, o2));
        break;
      }
      case 4: {
        auto o0 = rval->newMergeVertex(generateVertex(f, 0));
        auto o1 = rval->newMergeVertex(generateVertex(f, 1));
        auto o2 = rval->newMergeVertex(generateVertex(f, 2));
        auto o3 = rval->newMergeVertex(generateVertex(f, 3));
        rval->MergePoly(poly(o0, o1, o2, o3));
        break;
      }
      default:
        OrkAssert(false);
        break;
    }
  }
  return rval;
}
/////////////////////////////////////////////////////////////////////////
submesh::~submesh() {
}
/////////////////////////////////////////////////////////////////////////
svar64_t submesh::annotation(const char* annokey) const {
  static const char* defret("");
  auto it = _annotations.find(std::string(annokey));
  if (it != _annotations.end()) {
    return (*it).second;
  }
  return defret;
}
/////////////////////////////////////////////////////////////////////////
void submesh::MergeAnnos(const AnnotationMap& mrgannos, bool boverwrite) {
  for (AnnotationMap::const_iterator it = mrgannos.begin(); it != mrgannos.end(); it++) {
    const std::string& key      = it->first;
    const auto& val             = it->second;
    AnnotationMap::iterator itf = _annotations.find(key);
    if (itf == _annotations.end()) {
      _annotations[key] = val;
    } else if (boverwrite) {
      itf->second = val;
    }
  }
}
///////////////////////////////////////////////////////////////////////////////
void submesh::ImportPolyAnnotations(const annopolylut& apl) {
  int inumpolys = (int)_orderedPolys.size();
  for (int ip = 0; ip < inumpolys; ip++) {
    auto ply            = _orderedPolys[ip];
    const AnnoMap* amap = apl.Find(*this, *ply);
    if (amap) {
      ply->SetAnnoMap(amap);
    }
  }
}
///////////////////////////////////////////////////////////////////////////////
void submesh::ExportPolyAnnotations(annopolylut& apl) const {
  int inumpolys = (int)_orderedPolys.size();
  for (int ip = 0; ip < inumpolys; ip++) {
    auto ply            = _orderedPolys[ip];
    U64 uhash           = apl.HashItem(*this, *ply);
    const AnnoMap* amap = ply->GetAnnoMap();
    apl.mAnnoMap[uhash] = amap;
  }
}
///////////////////////////////////////////////////////////////////////////////
const AABox& submesh::aabox() const {
  if (_aaBoxDirty) {
    _aaBox.BeginGrow();
    int inumvtx = (int)RefVertexPool().GetNumVertices();
    for (int i = 0; i < inumvtx; i++) {
      const vertex& v = RefVertexPool().GetVertex(i);
      _aaBox.Grow(v.mPos);
    }
    _aaBox.EndGrow();
    _aaBoxDirty = false;
  }
  return _aaBox;
}
///////////////////////////////////////////////////////////////////////////////
const edge& submesh::RefEdge(U64 edgekey) const {
  auto it = _edgemap.find(edgekey);
  OrkAssert(it != _edgemap.end());
  return *it->second;
}
///////////////////////////////////////////////////////////////////////////////
vertex_ptr_t submesh::newMergeVertex(const vertex& vtx) {
  _aaBoxDirty = true;
  return _vtxpool.newMergeVertex(vtx);
}
///////////////////////////////////////////////////////////////////////////////
poly& submesh::RefPoly(int i) {
  OrkAssert(orkvector<int>::size_type(i) < _orderedPolys.size());
  return *_orderedPolys[i];
}
///////////////////////////////////////////////////////////////////////////////
const poly& submesh::RefPoly(int i) const {
  OrkAssert(orkvector<int>::size_type(i) < _orderedPolys.size());
  return *_orderedPolys[i];
}
///////////////////////////////////////////////////////////////////////////////
const orkvector<poly_ptr_t>& submesh::RefPolys() const {
  return _orderedPolys;
}
/////////////////////////////////////////////////////////////////////////
void submesh::FindNSidedPolys(orkvector<int>& output, int inumsides) const {
  int inump = (int)_orderedPolys.size();
  for (int i = 0; i < inump; i++) {
    const poly& ply = RefPoly(i);
    if (ply.GetNumSides() == inumsides) {
      output.push_back(i);
    }
  }
}
///////////////////////////////////////////////////////////////////////////////
int submesh::GetNumPolys(int inumsides) const {
  int iret = 0;
  if (0 == inumsides) {
    iret = (int)_orderedPolys.size();
  } else {
    OrkAssert(inumsides < kmaxsidesperpoly);
    iret = _polyTypeCounter[inumsides];
  }
  return iret;
}
///////////////////////////////////////////////////////////////////////////////
void submesh::GetEdges(const poly& ply, orkvector<edge>& Edges) const {
  int icnt  = 0;
  int icntf = 0;
  for (int is = 0; is < ply.GetNumSides(); is++) {
    U64 ue  = ply.mEdges[is]->GetHashKey();
    auto it = _edgemap.find(ue);
    if (it != _edgemap.end()) {
      Edges.push_back(*it->second);
      icntf++;
    }
    icnt++;
  }
}
///////////////////////////////////////////////////////////////////////////////
void submesh::GetAdjacentPolys(int ply, orkset<int>& output) const {
  orkvector<edge> edges;
  GetEdges(RefPoly(ply), edges);
  for (orkvector<edge>::const_iterator edgeIter = edges.begin(); edgeIter != edges.end(); edgeIter++) {
    orkset<int> connectedPolys;
    GetConnectedPolys(*edgeIter, connectedPolys);
    for (orkset<int>::const_iterator it2 = connectedPolys.begin(); it2 != connectedPolys.end(); it2++) {
      int ic = *it2;
      if (ic != ply) {
        output.insert(connectedPolys.begin(), connectedPolys.end());
      }
    }
  }
}
///////////////////////////////////////////////////////////////////////////////
edge_constptr_t submesh::edgeBetween(int aind, int bind) const {
  const poly& a = RefPoly(aind);
  const poly& b = RefPoly(bind);
  for (int eaind = 0; eaind < a.miNumSides; eaind++)
    for (int ebind = 0; ebind < b.miNumSides; ebind++)
      if (a.mEdges[eaind] == b.mEdges[ebind])
        return std::const_pointer_cast<const edge>(a.mEdges[eaind]);
  return nullptr;
}
///////////////////////////////////////////////////////////////////////////////
void submesh::GetConnectedPolys(const edge& ed, orkset<int>& output) const {
  U64 keyA    = ed.GetHashKey();
  auto itfind = _edgemap.find(keyA);
  if (itfind != _edgemap.end()) {
    auto edfound = itfind->second;
    int inump    = edfound->GetNumConnectedPolys();
    for (int ip = 0; ip < inump; ip++) {
      int ipi = edfound->GetConnectedPoly(ip);
      output.insert(ipi);
    }
  }
}
///////////////////////////////////////////////////////////////////////////////
void submesh::MergeSubMesh(const submesh& inp_mesh) {
  float ftimeA     = float(OldSchool::GetRef().GetLoResTime());
  int inumpingroup = inp_mesh.GetNumPolys();
  for (int i = 0; i < inumpingroup; i++) {
    const poly& ply = inp_mesh.RefPoly(i);
    int inumpv      = ply.GetNumSides();
    poly NewPoly;
    NewPoly.miNumSides = inumpv;
    for (int iv = 0; iv < inumpv; iv++) {
      int ivi               = ply.GetVertexID(iv);
      const vertex& src_vtx = inp_mesh.RefVertexPool().GetVertex(ivi);
      NewPoly._vertices[iv] = newMergeVertex(src_vtx);
    }
    NewPoly.SetAnnoMap(ply.GetAnnoMap());
    MergePoly(NewPoly);
  }
  float ftimeB = float(OldSchool::GetRef().GetLoResTime());
  float ftime  = (ftimeB - ftimeA);
  orkprintf("<<PROFILE>> <<submesh::MergeSubMesh %f seconds>>\n", ftime);
}
///////////////////////////////////////////////////////////////////////////////
void submesh::MergePoly(const poly& ply) {
  int ipolyindex = GetNumPolys();
  poly nply      = ply;
  int inumv      = ply.GetNumSides();
  OrkAssert(inumv <= kmaxsidesperpoly);
  ///////////////////////////////
  // zero area poly removal
  switch (inumv) {
    case 3: {
      if ((ply._vertices[0]->_poolindex == ply._vertices[1]->_poolindex) ||
          (ply._vertices[1]->_poolindex == ply._vertices[2]->_poolindex) ||
          (ply._vertices[2]->_poolindex == ply._vertices[0]->_poolindex)) {
        orkprintf(
            "Mesh::MergePoly() removing zero area tri<%d %d %d>\n",
            ply._vertices[0]->_poolindex,
            ply._vertices[1]->_poolindex,
            ply._vertices[2]->_poolindex);

        return;
      }
      break;
    }
    case 4: {
      if ((ply._vertices[0]->_poolindex == ply._vertices[1]->_poolindex) ||
          (ply._vertices[0]->_poolindex == ply._vertices[2]->_poolindex) ||
          (ply._vertices[0]->_poolindex == ply._vertices[3]->_poolindex) ||
          (ply._vertices[1]->_poolindex == ply._vertices[2]->_poolindex) ||
          (ply._vertices[1]->_poolindex == ply._vertices[3]->_poolindex) ||
          (ply._vertices[2]->_poolindex == ply._vertices[3]->_poolindex)) {
        orkprintf(
            "Mesh::MergePoly() removing zero area quad<%d %d %d %d>\n",
            ply._vertices[0]->_poolindex,
            ply._vertices[1]->_poolindex,
            ply._vertices[2]->_poolindex,
            ply._vertices[3]->_poolindex);

        return;
      }
      break;
    }
    default:
      break;
      // TODO n-sided polys
  }
  //////////////////////////////
  // dupe check
  U64 ucrc   = ply.HashIndices();
  auto itfhm = _polymap.find(ucrc);
  ///////////////////////////////
  if (itfhm == _polymap.end()) // no match
  {
    int inewpi = (int)_orderedPolys.size();
    //////////////////////////////////////////////////
    // connect to vertices
    for (int i = 0; i < inumv; i++) {
      auto vtx = ply._vertices[i];
      // vtx->ConnectToPoly(inewpi);
    }
    //////////////////////////////////////////////////
    // add edges
    if (_mergeEdges) {
      for (int i = 0; i < inumv; i++) {
        int i0  = (i);
        int i1  = (i + 1) % inumv;
        int iv0 = ply.GetVertexID(i0);
        int iv1 = ply.GetVertexID(i1);
        auto v0 = _vtxpool._orderedVertices[iv0];
        auto v1 = _vtxpool._orderedVertices[iv1];

        edge Edge(v0, v1);
        nply.mEdges[i] = MergeEdge(Edge, ipolyindex);
      }
    }
    nply.SetAnnoMap(ply.GetAnnoMap());
    auto new_poly = std::make_shared<poly>(nply);
    _orderedPolys.push_back(new_poly);
    _polymap[ucrc] = new_poly;
    //////////////////////////////////////////////////
    // add n sided counters
    _polyTypeCounter[inumv]++;
    //////////////////////////////////////////////////
    float farea = ply.ComputeArea(_vtxpool, ork::fmtx4::Identity());
    _surfaceArea += farea;
  }
  _aaBoxDirty = true;
}
///////////////////////////////////////////////////////////////////////////////
edge_ptr_t submesh::MergeEdge(const edge& ed, int ipolyindex) {
  U64 crcA    = ed.GetHashKey();
  auto itfind = _edgemap.find(crcA);

  edge_ptr_t rval;

  if (_edgemap.end() != itfind) {
    rval     = itfind->second;
    U64 crcB = rval->GetHashKey();
    OrkAssert(ed.Matches(*rval));
  } else {
    rval           = std::make_shared<edge>(ed);
    _edgemap[crcA] = rval;
  }
  if (ipolyindex >= 0) {
    rval->ConnectToPoly(ipolyindex);
  }

  _aaBoxDirty = true;
  return rval;
}
///////////////////////////////////////////////////////////////////////////////
// addPoly helper methods
///////////////////////////////////////////////////////////////////////////////
void submesh::addQuad(fvec3 p0, fvec3 p1, fvec3 p2, fvec3 p3, fvec4 c) {
  vertex muvtx[4];
  muvtx[0].set(p0, fvec3(), fvec3(), fvec2(), c);
  muvtx[1].set(p1, fvec3(), fvec3(), fvec2(), c);
  muvtx[2].set(p2, fvec3(), fvec3(), fvec2(), c);
  muvtx[3].set(p3, fvec3(), fvec3(), fvec2(), c);
  auto v0 = newMergeVertex(muvtx[0]);
  auto v1 = newMergeVertex(muvtx[1]);
  auto v2 = newMergeVertex(muvtx[2]);
  auto v3 = newMergeVertex(muvtx[3]);
  MergePoly(poly(v0, v1, v2, v3));
}
void submesh::addQuad(fvec3 p0, fvec3 p1, fvec3 p2, fvec3 p3, fvec2 uv0, fvec2 uv1, fvec2 uv2, fvec2 uv3, fvec4 c) {
  vertex muvtx[4];
  fvec3 p0p1 = (p1 - p0).Normal();
  fvec3 p0p2 = (p2 - p0).Normal();
  fvec3 nrm  = p0p1.Cross(p0p2);
  // todo compute tangent space from uv gradients
  fvec3 bin = p0p1;
  muvtx[0].set(p0, nrm, bin, uv0, c);
  muvtx[1].set(p1, nrm, bin, uv1, c);
  muvtx[2].set(p2, nrm, bin, uv2, c);
  muvtx[3].set(p3, nrm, bin, uv3, c);

  auto v0 = newMergeVertex(muvtx[0]);
  auto v1 = newMergeVertex(muvtx[1]);
  auto v2 = newMergeVertex(muvtx[2]);
  auto v3 = newMergeVertex(muvtx[3]);
  MergePoly(poly(v0, v1, v2, v3));
}
void submesh::addQuad(
    fvec3 p0,
    fvec3 p1,
    fvec3 p2,
    fvec3 p3,
    fvec3 n0,
    fvec3 n1,
    fvec3 n2,
    fvec3 n3,
    fvec2 uv0,
    fvec2 uv1,
    fvec2 uv2,
    fvec2 uv3,
    fvec4 c) { /// add quad helper method
  vertex muvtx[4];
  fvec3 p0p1 = (p1 - p0).Normal();
  fvec3 bin  = p0p1;
  muvtx[0].set(p0, n0, bin, uv0, c);
  muvtx[1].set(p1, n1, bin, uv1, c);
  muvtx[2].set(p2, n2, bin, uv2, c);
  muvtx[3].set(p3, n3, bin, uv3, c);

  auto v0 = newMergeVertex(muvtx[0]);
  auto v1 = newMergeVertex(muvtx[1]);
  auto v2 = newMergeVertex(muvtx[2]);
  auto v3 = newMergeVertex(muvtx[3]);
  MergePoly(poly(v0, v1, v2, v3));
}
///////////////////////////////////////////////////////////////////////////////
/*
void SubMesh::GenIndexBuffers( void )
{
    int inumvtx = RefVertexPool().VertexPool.size();

    orkvector<int> TrianglePolyIndices;
    orkvector<int> QuadPolyIndices;

    FindNSidedPolys( TrianglePolyIndices, 3 );
    FindNSidedPolys( QuadPolyIndices, 4 );

    int inumtri( TrianglePolyIndices.size() );
    int inumquad( QuadPolyIndices.size() );

    mpBaseTriangleIndices = new U16[ inumtri*3 ];
    mpBaseQuadIndices = new U16[ inumquad*4 ];

    for( int itri=0; itri<inumtri; itri++ )
    {
        int iti = TrianglePolyIndices[itri];

        const poly & tri = RefPoly( iti );

        int i0 = tri.miVertices[0];
        int i1 = tri.miVertices[1];
        int i2 = tri.miVertices[2];

        OrkAssert( i0<inumvtx );
        OrkAssert( i1<inumvtx );
        OrkAssert( i2<inumvtx );

        mpBaseTriangleIndices[ (itri*3)+0 ] = U16(i0);
        mpBaseTriangleIndices[ (itri*3)+1 ] = U16(i1);
        mpBaseTriangleIndices[ (itri*3)+2 ] = U16(i2);
    }

    for( int iqua=0; iqua<inumquad; iqua++ )
    {
        int iqi = QuadPolyIndices[iqua];

        const poly & qu = RefPoly( iqi );

        int i0 = qu.miVertices[0];
        int i1 = qu.miVertices[1];
        int i2 = qu.miVertices[2];
        int i3 = qu.miVertices[3];

        OrkAssert( i0<inumvtx );
        OrkAssert( i1<inumvtx );
        OrkAssert( i2<inumvtx );
        OrkAssert( i3<inumvtx );

        mpBaseQuadIndices[ (iqua*4)+0 ] = U16(i0);
        mpBaseQuadIndices[ (iqua*4)+1 ] = U16(i1);
        mpBaseQuadIndices[ (iqua*4)+2 ] = U16(i2);
        mpBaseQuadIndices[ (iqua*4)+3 ] = U16(i3);
    }

}*/
} // namespace ork::meshutil


// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY 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.
//
// FlexibleSUSY 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 FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

#ifndef SLHA_IO_H
#define SLHA_IO_H

#include <string>
#include <iosfwd>
#include <vector>
#include <Eigen/Core>
#include <boost/format.hpp>
#include <boost/function.hpp>
#include "slhaea.h"
#include "config.h"
#include "logger.hpp"
#include "error.hpp"
#include "wrappers.hpp"
#include "numerics2.hpp"
#include "pmns.hpp"

namespace softsusy {
   class QedQcd;
   class DoubleMatrix;
   class ComplexMatrix;
}

namespace flexiblesusy {

   class Spectrum_generator_settings;
   class Physical_input;

   namespace {
      /// SLHA line formatter for the MASS block entries
      const boost::format mass_formatter(" %9d   %16.8E   # %s\n");
      /// SLHA line formatter for the mixing matrix entries (NMIX, UMIX, VMIX, ...)
      const boost::format mixing_matrix_formatter(" %2d %2d   %16.8E   # %s\n");
      /// SLHA line formatter for vector entries
      const boost::format vector_formatter(" %5d   %16.8E   # %s\n");
      /// SLHA number formatter
      const boost::format number_formatter("         %16.8E   # %s\n");
      /// SLHA line formatter for entries with three indices
      const boost::format tensor_formatter(" %8d %8d %8d   %16.8E   # %s\n");
      /// SLHA scale formatter
      const boost::format scale_formatter("%9.8E");
      /// SLHA line formatter for the one-element entries (HMIX, GAUGE, MSOFT, ...)
      const boost::format single_element_formatter(" %5d   %16.8E   # %s\n");
      /// SLHA line formatter for the SPINFO block entries
      const boost::format spinfo_formatter(" %5d   %s\n");
   }

#define FORMAT_MASS(pdg,mass,name)                                      \
   boost::format(mass_formatter) % (pdg) % (mass) % (name)
#define FORMAT_MIXING_MATRIX(i,k,entry,name)                            \
   boost::format(mixing_matrix_formatter) % (i) % (k) % (entry) % (name)
#define FORMAT_ELEMENT(pdg,value,name)                                  \
   boost::format(single_element_formatter) % (pdg) % (value) % (name)
#define FORMAT_SCALE(n)                                                 \
   boost::format(scale_formatter) % (n)
#define FORMAT_NUMBER(n,str)                                            \
   boost::format(number_formatter) % (n) % (str)
#define FORMAT_SPINFO(n,str)                                            \
   boost::format(spinfo_formatter) % (n) % (str)
#define FORMAT_RANK_THREE_TENSOR(i,j,k,entry,name)                      \
   boost::format(tensor_formatter) % (i) % (j) % (k) % (entry) % (name)

/**
 * @class SLHA_io
 * @brief Handles reading and writing of SLHA files
 *
 * Reading: There are two ways to read block entries from SLHA files:
 * a) using the read_block() function with a %SLHA_io::Tuple_processor
 * or b) using the read_entry() function for each entry.  Note, that
 * a) is much faster than b) (more than 1000 times) because b) needs
 * to search for the block each time read_entry() is called.
 *
 * Example how to use a tuple processor (fast!):
 * \code{.cpp}
void process_tuple(double* array, int key, double value) {
   array[key] = value;
}

void read_file() {
   double array[1000];

   SLHA_io reader;
   reader.read_from_file("file.slha");

   SLHA_io::Tuple_processor processor
      = boost::bind(&process_tuple, array, _1, _2);

   reader.read_block("MyBlock", processor);
}
 * \endcode
 *
 * Example how to use a for loop (slow!):
 * \code{.cpp}
void read_file() {
   double array[1000];

   SLHA_io reader;
   reader.read_from_file("file.slha");

   for (int i = 0; i < 1000; i++) {
      array[i] = reader.read_entry("MyBlock", i);
   }
}
 * \endcode
 */
class SLHA_io {
public:
   typedef boost::function<void(int, double)> Tuple_processor;
   enum Position { front, back };
   struct Modsel {
      bool quark_flavour_violated;   ///< key = 6
      bool lepton_flavour_violated;  ///< key = 6
      double parameter_output_scale; ///< key = 12
      Modsel()
         : quark_flavour_violated(false)
         , lepton_flavour_violated(false)
         , parameter_output_scale(0.)
         {}
      void clear() {
         quark_flavour_violated = false;
         lepton_flavour_violated = false;
         parameter_output_scale = 0.;
      }
   };

   struct CKM_wolfenstein {
      double lambdaW, aCkm, rhobar, etabar;
      CKM_wolfenstein() : lambdaW(0.), aCkm(0.), rhobar(0.), etabar(0.) {}
      void clear() {
         lambdaW = 0.;
         aCkm    = 0.;
         rhobar  = 0.;
         etabar  = 0.;
      }
   };

   class ReadError : public Error {
   public:
      ReadError(const std::string& message_) : message(message_) {}
      virtual ~ReadError() {}
      virtual std::string what() const { return message; }
   private:
      std::string message;
   };

   SLHA_io();
   ~SLHA_io() {}

   void clear();

   // reading functions
   bool block_exists(const std::string&) const;
   void fill(softsusy::QedQcd&) const;
   void fill(Spectrum_generator_settings&) const;
   void fill(Physical_input&) const;
   const Modsel& get_modsel() const { return modsel; }
   const SLHAea::Coll& get_data() const { return data; }
   void read_from_file(const std::string&);
   void read_from_source(const std::string&);
   void read_from_stream(std::istream&);
   double read_block(const std::string&, const Tuple_processor&) const;
   template <class Derived>
   double read_block(const std::string&, Eigen::MatrixBase<Derived>&) const;
   double read_block(const std::string&, double&) const;
   double read_entry(const std::string&, int) const;
   void read_modsel();
   double read_scale(const std::string&) const;

   // writing functions
   void set_data(const SLHAea::Coll& data_) { data = data_; }
   void set_block(const std::ostringstream&, Position position = back);
   void set_block(const std::string&, Position position = back);
   void set_blocks(const std::vector<std::string>&, Position position = back);
   void set_block(const std::string&, double, const std::string&, double scale = 0.);
   template<class Scalar, int M, int N>
   void set_block(const std::string&, const Eigen::Matrix<std::complex<Scalar>, M, N>&, const std::string&, double scale = 0.);
   template<class Scalar, int M>
   void set_block(const std::string&, const Eigen::Matrix<std::complex<Scalar>, M, 1>&, const std::string&, double scale = 0.);
   template<class Scalar, int M, int N>
   void set_block_imag(const std::string&, const Eigen::Matrix<std::complex<Scalar>, M, N>&, const std::string&, double scale = 0.);
   template<class Scalar, int M>
   void set_block_imag(const std::string&, const Eigen::Matrix<std::complex<Scalar>, M, 1>&, const std::string&, double scale = 0.);
   template <class Derived>
   void set_block(const std::string&, const Eigen::MatrixBase<Derived>&, const std::string&, double scale = 0.);
   template <class Derived>
   void set_block_imag(const std::string&, const Eigen::MatrixBase<Derived>&, const std::string&, double scale = 0.);
   void set_block(const std::string&, const softsusy::DoubleMatrix&, const std::string&, double scale = 0.);
   void set_block(const std::string&, const softsusy::ComplexMatrix&, const std::string&, double scale = 0.);
   void set_sminputs(const softsusy::QedQcd&);
   void write_to_file(const std::string&);
   void write_to_stream(std::ostream& = std::cout);

   template<int N>
   static void convert_symmetric_fermion_mixings_to_slha(Eigen::Array<double, N, 1>&,
                                                         Eigen::Matrix<double, N, N>&);

   static void convert_symmetric_fermion_mixings_to_slha(double&,
                                                         Eigen::Matrix<double, 1, 1>&);

   template<int N>
   static void convert_symmetric_fermion_mixings_to_slha(Eigen::Array<double, N, 1>&,
                                                         Eigen::Matrix<std::complex<double>, N, N>&);

   static void convert_symmetric_fermion_mixings_to_slha(double&,
                                                         Eigen::Matrix<std::complex<double>, 1, 1>&);

   template<int N>
   static void convert_symmetric_fermion_mixings_to_hk(Eigen::Array<double, N, 1>&,
                                                       Eigen::Matrix<double, N, N>&);

   static void convert_symmetric_fermion_mixings_to_hk(double&,
                                                       Eigen::Matrix<double, 1, 1>&);

   template<int N>
   static void convert_symmetric_fermion_mixings_to_hk(Eigen::Array<double, N, 1>&,
                                                       Eigen::Matrix<std::complex<double>, N, N>&);

   static void convert_symmetric_fermion_mixings_to_hk(double&,
                                                       Eigen::Matrix<std::complex<double>, 1, 1>&);

private:
   SLHAea::Coll data;          ///< SHLA data
   Modsel modsel;              ///< data from block MODSEL
   template <class Scalar>
   static Scalar convert_to(const std::string&); ///< convert string
   static std::string to_lower(const std::string&); ///< string to lower case
   static void process_sminputs_tuple(softsusy::QedQcd&, int, double);
   static void process_modsel_tuple(Modsel&, int, double);
   static void process_vckmin_tuple(CKM_wolfenstein&, int, double);
   static void process_upmnsin_tuple(PMNS_parameters&, int, double);
   static void process_flexiblesusy_tuple(Spectrum_generator_settings&, int, double);
   static void process_flexiblesusyinput_tuple(Physical_input&, int, double);
};

template <class Scalar>
Scalar SLHA_io::convert_to(const std::string& str)
{
   Scalar value;
   try {
      value = SLHAea::to<Scalar>(str);
   }  catch (const boost::bad_lexical_cast& error) {
      const std::string msg("cannot convert string \"" + str + "\" to "
                            + typeid(Scalar).name());
      throw ReadError(msg);
   }
   return value;
}

/**
 * Fills a matrix from a SLHA block
 *
 * @param block_name block name
 * @param matrix matrix to be filled
 *
 * @return scale (or 0 if no scale is defined)
 */
template <class Derived>
double SLHA_io::read_block(const std::string& block_name, Eigen::MatrixBase<Derived>& matrix) const
{
   SLHAea::Coll::const_iterator block =
      data.find(data.cbegin(), data.cend(), block_name);

   const int cols = matrix.cols(), rows = matrix.rows();
   double scale = 0.;

   while (block != data.cend()) {
      for (SLHAea::Block::const_iterator line = block->cbegin(),
              end = block->cend(); line != end; ++line) {
         if (!line->is_data_line()) {
            // read scale from block definition
            if (line->size() > 3 &&
                to_lower((*line)[0]) == "block" && (*line)[2] == "Q=")
               scale = convert_to<double>((*line)[3]);
            continue;
         }

         if (cols == 1) {
            // vector
            if (line->size() >= 2) {
               const int i = convert_to<int>((*line)[0]) - 1;
               if (0 <= i && i < rows) {
                  const double value = convert_to<double>((*line)[1]);
                  matrix(i,0) = value;
               }
            }
         } else {
            // martix
            if (line->size() >= 3) {
               const int i = convert_to<int>((*line)[0]) - 1;
               const int k = convert_to<int>((*line)[1]) - 1;
               if (0 <= i && i < rows && 0 <= k && k < cols) {
                  const double value = convert_to<double>((*line)[2]);
                  matrix(i,k) = value;
               }
            }
         }
      }

      ++block;
      block = data.find(block, data.cend(), block_name);
   }

   return scale;
}

template<class Scalar, int NRows>
void SLHA_io::set_block(const std::string& name,
                        const Eigen::Matrix<std::complex<Scalar>, NRows, 1>& matrix,
                        const std::string& symbol, double scale)
{
   std::ostringstream ss;
   ss << "Block " << name;
   if (scale != 0.)
      ss << " Q= " << FORMAT_SCALE(scale);
   ss << '\n';

   for (int i = 1; i <= NRows; ++i) {
      ss << boost::format(vector_formatter) % i % Re(matrix(i-1,0))
         % ("Re(" + symbol + "(" + ToString(i) + "))");
   }

   set_block(ss);
}

template<class Scalar, int NRows, int NCols>
void SLHA_io::set_block(const std::string& name,
                        const Eigen::Matrix<std::complex<Scalar>, NRows, NCols>& matrix,
                        const std::string& symbol, double scale)
{
   std::ostringstream ss;
   ss << "Block " << name;
   if (scale != 0.)
      ss << " Q= " << FORMAT_SCALE(scale);
   ss << '\n';

   for (int i = 1; i <= NRows; ++i) {
      for (int k = 1; k <= NCols; ++k) {
         ss << boost::format(mixing_matrix_formatter) % i % k
            % Re(matrix(i-1,k-1))
            % ("Re(" + symbol + "(" + ToString(i) + ","
               + ToString(k) + "))");
      }
   }

   set_block(ss);
}

template<class Scalar, int NRows>
void SLHA_io::set_block_imag(const std::string& name,
                             const Eigen::Matrix<std::complex<Scalar>, NRows, 1>& matrix,
                             const std::string& symbol, double scale)
{
   std::ostringstream ss;
   ss << "Block " << name;
   if (scale != 0.)
      ss << " Q= " << FORMAT_SCALE(scale);
   ss << '\n';

   for (int i = 1; i <= NRows; ++i) {
      ss << boost::format(vector_formatter) % i % Im(matrix(i-1,0))
         % ("Im(" + symbol + "(" + ToString(i) + "))");
   }

   set_block(ss);
}

template<class Scalar, int NRows, int NCols>
void SLHA_io::set_block_imag(const std::string& name,
                             const Eigen::Matrix<std::complex<Scalar>, NRows, NCols>& matrix,
                             const std::string& symbol, double scale)
{
   std::ostringstream ss;
   ss << "Block " << name;
   if (scale != 0.)
      ss << " Q= " << FORMAT_SCALE(scale);
   ss << '\n';

   for (int i = 1; i <= NRows; ++i) {
      for (int k = 1; k <= NCols; ++k) {
         ss << boost::format(mixing_matrix_formatter) % i % k
            % Im(matrix(i-1,k-1))
            % ("Im(" + symbol + "(" + ToString(i) + ","
               + ToString(k) + "))");
      }
   }

   set_block(ss);
}

template <class Derived>
void SLHA_io::set_block(const std::string& name,
                        const Eigen::MatrixBase<Derived>& matrix,
                        const std::string& symbol, double scale)
{
   std::ostringstream ss;
   ss << "Block " << name;
   if (scale != 0.)
      ss << " Q= " << FORMAT_SCALE(scale);
   ss << '\n';

   const int rows = matrix.rows();
   const int cols = matrix.cols();
   for (int i = 1; i <= rows; ++i) {
      if (cols == 1) {
         ss << boost::format(vector_formatter) % i % matrix(i-1,0)
            % (symbol + "(" + ToString(i) + ")");
      } else {
         for (int k = 1; k <= cols; ++k) {
            ss << boost::format(mixing_matrix_formatter) % i % k % matrix(i-1,k-1)
               % (symbol + "(" + ToString(i) + "," + ToString(k) + ")");
         }
      }
   }

   set_block(ss);
}

template <class Derived>
void SLHA_io::set_block_imag(const std::string& name,
                             const Eigen::MatrixBase<Derived>& matrix,
                             const std::string& symbol, double scale)
{
   std::ostringstream ss;
   ss << "Block " << name;
   if (scale != 0.)
      ss << " Q= " << FORMAT_SCALE(scale);
   ss << '\n';

   const int rows = matrix.rows();
   const int cols = matrix.cols();
   for (int i = 1; i <= rows; ++i) {
      if (cols == 1) {
         ss << boost::format(vector_formatter) % i % Im(matrix(i-1,0))
            % ("Im(" + symbol + "(" + ToString(i) + "))");
      } else {
         for (int k = 1; k <= cols; ++k) {
            ss << boost::format(mixing_matrix_formatter) % i % k % Im(matrix(i-1,k-1))
               % ("Im(" + symbol + "(" + ToString(i) + "," + ToString(k) + "))");
         }
      }
   }

   set_block(ss);
}

template<int N>
void SLHA_io::convert_symmetric_fermion_mixings_to_slha(Eigen::Array<double, N, 1>&,
                                                        Eigen::Matrix<double, N, N>&)
{
}

/**
 * Converts the given vector of masses and the corresponding (complex)
 * mixing matrix to SLHA convention: Matrix rows with non-zero
 * imaginary parts are multiplied by i and the corresponding mass
 * eigenvalue is multiplied by -1.  As a result the mixing matrix will
 * be real and the mass eigenvalues might be positive or negative.  It
 * is assumed that these mixings result from diagonalizing a symmetric
 * fermion mass matrix in the convention of Haber and Kane,
 * Phys. Rept. 117 (1985) 75-263.  This conversion makes sense only if
 * the original symmetric mass matrix is real-valued.
 *
 * @param m vector of masses
 * @param z mixing matrix
 */
template<int N>
void SLHA_io::convert_symmetric_fermion_mixings_to_slha(Eigen::Array<double, N, 1>& m,
                                                        Eigen::Matrix<std::complex<double>, N, N>& z)
{
   for (int i = 0; i < N; i++) {
      // check if i'th row contains non-zero imaginary parts
      if (!is_zero(z.row(i).imag().cwiseAbs().maxCoeff())) {
         z.row(i) *= std::complex<double>(0.0,1.0);
         m(i) *= -1;
#ifdef ENABLE_DEBUG
         if (!is_zero(z.row(i).imag().cwiseAbs().maxCoeff())) {
            WARNING("Row " << i << " of the following fermion mixing matrix"
                    " contains entries which have non-zero real and imaginary"
                    " parts:\nZ = " << z);
         }
#endif
      }
   }
}

template<int N>
void SLHA_io::convert_symmetric_fermion_mixings_to_hk(Eigen::Array<double, N, 1>&,
                                                      Eigen::Matrix<double, N, N>&)
{
}

/**
 * Converts the given vector of masses and the corresponding (real)
 * mixing matrix to Haber-Kane convention (Phys. Rept. 117 (1985)
 * 75-263): Masses are positive and mixing matrices can be complex.
 *
 * @param m vector of masses
 * @param z mixing matrix
 */
template<int N>
void SLHA_io::convert_symmetric_fermion_mixings_to_hk(Eigen::Array<double, N, 1>& m,
                                                      Eigen::Matrix<std::complex<double>, N, N>& z)
{
   for (int i = 0; i < N; i++) {
      if (m(i) < 0.) {
         z.row(i) *= std::complex<double>(0.0,1.0);
         m(i) *= -1;
      }
   }
}

} // namespace flexiblesusy

#endif


#pragma once
#include <boost/pending/disjoint_sets.hpp>
#include "xtensor/xtensor.hpp"
#include "affogato/util.hxx"
#include <queue>
#include <functional>

namespace affogato {
namespace segmentation {

    //
    // mutex helper functions:
    // check_mutex: check if mutex exists between two representatives
    // insert_mutex: insert mutex between two representatives
    // merge_mutexex: merge mutex constrained of two mutex stores
    //

    template<class MUTEX_STORAGE>
    inline bool check_mutex(const uint64_t ru, const uint64_t rv,
                            const MUTEX_STORAGE & mutexes) {
        // get iterators to the mutex vectors of rep u and rep v
        auto mutex_it_u = mutexes[ru].begin();
        auto mutex_it_v = mutexes[rv].begin();

        // check if the mutex vectors contain the same mutex edge
        while (mutex_it_u != mutexes[ru].end() && mutex_it_v != mutexes[rv].end()) {
            if (*mutex_it_u < *mutex_it_v) {
                ++mutex_it_u;
            } else  {
                if (!(*mutex_it_v < *mutex_it_u)) {
                    return true;
                }
                ++mutex_it_v;
            }
        }
        return false;
    }

    // insert 'mutex_edge_id' into the vectors containing mutexes of 'ru' and 'rv'
    template<class MUTEX_STORAGE>
    inline void insert_mutex(const uint64_t ru, const uint64_t rv,
                             const uint64_t mutex_edge_id, MUTEX_STORAGE & mutexes) {
        mutexes[ru].insert(std::upper_bound(mutexes[ru].begin(),
                                            mutexes[ru].end(),
                                            mutex_edge_id), mutex_edge_id);
        mutexes[rv].insert(std::upper_bound(mutexes[rv].begin(),
                                            mutexes[rv].end(),
                                            mutex_edge_id), mutex_edge_id);
    }

    // merge the mutex edges by merging from 'root_from' to 'root_to'
    template<class MUTEX_STORAGE>
    inline void merge_mutexes(const uint64_t root_from, const uint64_t root_to,
                              MUTEX_STORAGE & mutexes) {
        if (mutexes[root_from].size() == 0) {
            return;
        }

        if (mutexes[root_to].size() == 0){
            mutexes[root_to] = mutexes[root_from];
            return;
        }

        std::vector<uint64_t> merge_buffer;
        merge_buffer.reserve(std::max(mutexes[root_from].size(), mutexes[root_to].size()));

        std::merge(mutexes[root_from].begin(), mutexes[root_from].end(),
                   mutexes[root_to].begin(), mutexes[root_to].end(),
                   std::back_inserter(merge_buffer));

        mutexes[root_to] = merge_buffer;
        mutexes[root_from].clear();
    }

    // compute mutex clustering for a graph with attrative and mutex edges
    template<class EDGE_ARRAY, class WEIGHT_ARRAY, class NODE_ARRAY>
    void compute_mws_clustering(const size_t number_of_labels,
                                const xt::xexpression<EDGE_ARRAY> & uvs_exp,
                                const xt::xexpression<EDGE_ARRAY> & mutex_uvs_exp,
                                const xt::xexpression<WEIGHT_ARRAY> & weights_exp,
                                const xt::xexpression<WEIGHT_ARRAY> & mutex_weights_exp,
                                xt::xexpression<NODE_ARRAY> & node_labeling_exp) {

        // casts
        const auto & uvs = uvs_exp.derived_cast();
        const auto & mutex_uvs = mutex_uvs_exp.derived_cast();
        const auto & weights = weights_exp.derived_cast();
        const auto & mutex_weights = mutex_weights_exp.derived_cast();
        auto & node_labeling = node_labeling_exp.derived_cast();

        // make ufd
        std::vector<uint64_t> ranks(number_of_labels);
        std::vector<uint64_t> parents(number_of_labels);
        boost::disjoint_sets<uint64_t*, uint64_t*> ufd(&ranks[0], &parents[0]);
        for(uint64_t label = 0; label < number_of_labels; ++label) {
            ufd.make_set(label);
        }

        // determine number of edge types
        const size_t num_edges = uvs.shape()[0];
        const size_t num_mutex = mutex_uvs.shape()[0];

        // argsort ALL edges
        // we sort in ascending order
        std::vector<size_t> indices(num_edges + num_mutex);
        std::iota(indices.begin(), indices.end(), 0);
        std::sort(indices.begin(), indices.end(), [&](const size_t a, const size_t b){
            const double val_a = (a < num_edges) ? weights(a) : mutex_weights(a - num_edges);
            const double val_b = (b < num_edges) ? weights(b) : mutex_weights(b - num_edges);
            return val_a > val_b;
        });

        // data-structure storing mutex edges
        typedef std::vector<std::vector<uint64_t>> MutexStorage;
        MutexStorage mutexes(number_of_labels);

        // iterate over all edges
        for(const size_t edge_id : indices) {

            // check whether this edge is mutex via the edge offset
            const bool is_mutex_edge = edge_id >= num_edges;

            // find the edge-id or mutex id and the connected nodes
            const size_t id = is_mutex_edge ? edge_id - num_edges : edge_id;
            const uint64_t u = is_mutex_edge ? mutex_uvs(id, 0) : uvs(id, 0);
            const uint64_t v = is_mutex_edge ? mutex_uvs(id, 1) : uvs(id, 1);

            // find the current representatives
            uint64_t ru = ufd.find_set(u);
            uint64_t rv = ufd.find_set(v);

            // if the nodes are already connected, do nothing
            if(ru == rv) {
                continue;
            }

            // if we already have a mutex, we do not need to do anything
            // (if this is a regular edge, we do not link, if it is a mutex edge
            //  we do not need to insert the redundant mutex constraint)
            if(check_mutex(ru, rv, mutexes)) {
                continue;
            }

            if(is_mutex_edge) {

                // insert mutex constraint
                insert_mutex(ru, rv, id, mutexes);

            } else {

                // link the nodes and merge their mutex constraints
                ufd.link(u, v);
                // check  if we have to swap the roots
                if(ufd.find_set(ru) == rv) {
                    std::swap(ru, rv);
                }
                // merge mutexes from rv -> ru
                merge_mutexes(rv, ru, mutexes);
            }
        }

        // get node labeling into output
        for(size_t label = 0; label < number_of_labels; ++label) {
            node_labeling[label] = ufd.find_set(label);
        }

        util::export_consecutive_labels(ufd, number_of_labels, node_labeling);
    }

    // compute mutex segmentation via kruskal
    template<class WEIGHT_ARRAY, class NODE_ARRAY, class INDICATOR_ARRAY>
    void compute_mws_segmentation(const xt::xexpression<WEIGHT_ARRAY> & sorted_flat_indices_exp,
                                  const xt::xexpression<INDICATOR_ARRAY> & valid_edges_exp,
                                  const std::vector<std::vector<int>> & offsets,
                                  const size_t number_of_attractive_channels,
                                  const std::vector<int> & image_shape,
                                  xt::xexpression<NODE_ARRAY> & node_labeling_exp) {

        // casts
        const auto & sorted_flat_indices = sorted_flat_indices_exp.derived_cast();
        const auto & valid_edges = valid_edges_exp.derived_cast();
        auto & node_labeling = node_labeling_exp.derived_cast();

        // determine number of nodes and attractive edges
        const size_t number_of_nodes = node_labeling.size();
        const size_t number_of_attractive_edges = number_of_nodes * number_of_attractive_channels;
        const size_t number_of_offsets = offsets.size();
        const size_t ndims = offsets[0].size();

        std::vector<int64_t> array_stride(ndims);
        int64_t current_stride = 1;
        for (int i = ndims-1; i >= 0; --i){
            array_stride[i] = current_stride;
            current_stride *= image_shape[i];
        }

        std::vector<int64_t> offset_strides;
        for (const auto & offset: offsets){
            int64_t stride = 0;
            for (int i = 0; i < offset.size(); ++i){
                stride += offset[i] * array_stride[i];
            }
            offset_strides.push_back(stride);
        }

        // make ufd
        std::vector<uint64_t> ranks(number_of_nodes);
        std::vector<uint64_t> parents(number_of_nodes);
        boost::disjoint_sets<uint64_t*, uint64_t*> ufd(&ranks[0], &parents[0]);
        for(uint64_t label = 0; label < number_of_nodes; ++label) {
            ufd.make_set(label);
        }

        // data-structure storing mutex edges
        typedef std::vector<std::vector<uint64_t>> MutexStorage;
        MutexStorage mutexes(number_of_nodes);

        // iterate over all edges
        for(const size_t edge_id : sorted_flat_indices) {

            if(!valid_edges(edge_id)){
                continue;
            }

            // check whether this edge is mutex via the edge offset
            const bool is_mutex_edge = edge_id >= number_of_attractive_edges;

            // get nodes connected by edge of edge_id

            // const auto affCoord_ = xt::unravel_from_strides(edge_id, strides, layout);
            const uint64_t u = edge_id % number_of_nodes;
            const uint64_t v = u + offset_strides[edge_id / number_of_nodes];

            // find the current representatives
            uint64_t ru = ufd.find_set(u);
            uint64_t rv = ufd.find_set(v);

            // if the nodes are already connected, do nothing
            if(ru == rv) {
                continue;
            }

            // if we already have a mutex, we do not need to do anything
            // (if this is a regular edge, we do not link, if it is a mutex edge
            //  we do not need to insert the redundant mutex constraint)
            if(check_mutex(ru, rv, mutexes)) {
                continue;
            }

            if(is_mutex_edge) {

                // insert the mutex edge into both mutex edge storages
                insert_mutex(ru, rv, edge_id, mutexes);

            } else {

                ufd.link(u, v);
                // check  if we have to swap the roots
                if(ufd.find_set(ru) == rv) {
                    std::swap(ru, rv);
                }
                // merge mutexes from rv -> ru
                merge_mutexes(rv, ru, mutexes);
            }
        }

        // get node labeling into output (with consecutive relabeling)
        util::export_consecutive_labels(ufd, number_of_nodes, node_labeling);
    }

    // helper function for mws prim implementation:
    // add all neighbors of given node to the priority queue
    template <class WEIGHT_ARRAY, class VALID_ARRAY, class UFD, class PRIORITY_QUEUE>
    inline void add_neighbours(const uint64_t & position,
                               const std::vector<int64_t> & offset_strides,
                               const size_t & number_of_nodes,
                               const WEIGHT_ARRAY & edge_weights,
                               const VALID_ARRAY & valid_edges,
                               UFD & ufd,
                               const xt::xtensor<bool, 1> & visited,
                               PRIORITY_QUEUE & pq){

        const uint64_t ru = ufd.find_set(position);
        for(int i = 0; i < offset_strides.size(); ++i){
            // go in positive offset direction
            const uint64_t edge_id = position + i * number_of_nodes;
            if (valid_edges(edge_id) && !visited(edge_id)){
                const uint64_t neighbour = position + offset_strides[i];
                const uint64_t rv = ufd.find_set(neighbour);
                if (ru != rv){
                    pq.push(std::make_tuple(edge_weights(edge_id), edge_id, position, neighbour));
                }
            }

            // check that position - offset_stride lies within 0 and number_of_nodes
            const bool within_bounds = (offset_strides[i] > 0 || position < number_of_nodes + offset_strides[i])
                                    && (offset_strides[i] < 0 || offset_strides[i] <= position);

            // go in negative offset direction
            if (within_bounds){
                const uint64_t neg_neighbour = position - offset_strides[i];
                const uint64_t neg_edge_id = neg_neighbour + i * number_of_nodes;
                if (valid_edges(neg_edge_id) && !visited(neg_edge_id)){
                    const uint64_t rv = ufd.find_set(neg_neighbour);
                    if (ru != rv){
                        pq.push(std::make_tuple(edge_weights(neg_edge_id), neg_edge_id, position, neg_neighbour));
                    }
                }
            }
        }
    }

    // compute mutex segmentation via prim's algorithm
    template<class WEIGHT_ARRAY, class NODE_ARRAY, class INDICATOR_ARRAY>
    void compute_mws_prim_segmentation(const xt::xexpression<WEIGHT_ARRAY> & edge_weight_exp,
                                       const xt::xexpression<INDICATOR_ARRAY> & valid_edges_exp,
                                       const std::vector<std::vector<int>> & offsets,
                                       const size_t number_of_attractive_channels,
                                       const std::vector<int> & image_shape,
                                       xt::xexpression<NODE_ARRAY> & node_labeling_exp) {
        // typedef
        typedef std::tuple<float, uint64_t, uint64_t, uint64_t> PQElement;
        auto pq_compare = [](PQElement left, PQElement right) {return std::get<0>(left) < std::get<0>(right);};
        typedef std::priority_queue<PQElement, std::vector<PQElement>,
                                    decltype(pq_compare)> EdgePriorityQueue;
        typedef boost::disjoint_sets<uint64_t*, uint64_t*> NodeUnionFind;

        // casts
        const auto & edge_weights = edge_weight_exp.derived_cast();
        const auto & valid_edges = valid_edges_exp.derived_cast();
        auto & node_labeling = node_labeling_exp.derived_cast();

        const size_t number_of_nodes = node_labeling.size();
        const size_t number_of_attractive_edges = number_of_nodes * number_of_attractive_channels;
        const size_t number_of_offsets = offsets.size();
        const size_t ndims = offsets[0].size();
        xt::xtensor<bool, 1> visited = xt::zeros<bool>({edge_weights.size()});

        std::vector<int64_t> array_stride(ndims);
        int64_t current_stride = 1;
        for (int i = ndims-1; i >= 0; --i){
            array_stride[i] = current_stride;
            current_stride *= image_shape[i];
        }

        std::vector<int64_t> offset_strides;
        for (const auto & offset: offsets){
            int64_t stride = 0;
            for (int i = 0; i < offset.size(); ++i){
                stride += offset[i] * array_stride[i];
            }
            offset_strides.push_back(stride);
        }

        // make ufd
        std::vector<uint64_t> ranks(number_of_nodes);
        std::vector<uint64_t> parents(number_of_nodes);
        NodeUnionFind node_ufd(&ranks[0], &parents[0]);
        for(uint64_t label = 0; label < number_of_nodes; ++label) {
            node_ufd.make_set(label);
        }

        // data-structure storing mutex edges
        typedef std::vector<std::vector<uint64_t>> MutexStorage;
        MutexStorage mutexes(number_of_nodes);
        EdgePriorityQueue pq(pq_compare);

        // start prim from top left node
        add_neighbours(0,
                       offset_strides,
                       number_of_nodes,
                       edge_weights,
                       valid_edges,
                       node_ufd,
                       visited,
                       pq);

        // iterate over all edges
        while(!pq.empty()) {
            // extract next element from the queue
            const PQElement position_vector = pq.top();
            pq.pop();
            const uint64_t edge_id = std::get<1>(position_vector);
            const uint64_t u = std::get<2>(position_vector);
            const uint64_t v = std::get<3>(position_vector);

            if(visited(edge_id)) {
                continue;
            }
            visited(edge_id) = 1;

            // find the current representatives
            // and skip if roots are identical
            uint64_t ru = node_ufd.find_set(u);
            uint64_t rv = node_ufd.find_set(v);
            if(ru == rv) {
                continue;
            }

            // check whether this edge is mutex via the edge offset
            const bool is_mutex_edge = edge_id >= number_of_attractive_edges;

            // if we already have a mutex, we do not need to do anything
            // (if this is a regular edge, we do not link, if it is a mutex edge
            //  we do not need to insert the redundant mutex constraint)
            if(check_mutex(ru, rv, mutexes)) {
                continue;
            }

            if(is_mutex_edge) {
                insert_mutex(ru, rv, edge_id, mutexes);
            } else {

                node_ufd.link(u, v);
                // check  if we have to swap the roots
                if(node_ufd.find_set(ru) == rv) {
                    std::swap(ru, rv);
                }
                merge_mutexes(rv, ru, mutexes);
            }
            // add the next node to pq
            add_neighbours(v,
                           offset_strides,
                           number_of_nodes,
                           edge_weights,
                           valid_edges,
                           node_ufd,
                           visited,
                           pq);
        }

        // get node labeling into output (with consecutive relabeling)
        util::export_consecutive_labels(node_ufd, number_of_nodes, node_labeling);
    }

}
}