//////////////////////////////////////////////////////////////////////////
// SymmetricFlipGraphAlgorithms.hh
// produced: 2026-03-03 jr
/////////////////////////////////////////////////////////////////////////
#ifndef SYMMETRICFLIPGRAPHALGORITHMS_HH
#define SYMMETRICFLIPGRAPHALGORITHMS_HH

#include <iostream>

#include "Global.hh"
#include "ConfData.hh"
#include "Options.hh"
#include "Cache.hh"
#include "SimplicialComplex.hh"
#include "SymmetricFlipGraph.hh"


namespace topcom {

  // an application to showcase how the options for SymmetricFlipGraph
  // can be used for a more complicated task:

  class SymmetricFlipGraphAlgorithms {
  private:

    // data members holding input:
    const int                       _ID;
    const ConfData&                 _confdata;
    const Options&                  _options;
    
    // data members for sharing information:
    const Cache<SimplicialComplex>& _global_triangcache;
    Cache<SimplicialComplex>&       _local_triangcache;
    RandomPermutationGenerator      _randperm_generator;
    Permutation                     _randperm;


    // data members holding results:
    size_type                       _symcount;
    size_type                       _totalcount;
    size_type                       _flipdist;
    SimplicialComplex               _final_triang; // last triangulation visited by flip-graph exploration
    SimplicialComplex               _min_support_triang; // triang of min support found during previous flip-graph exploration
  public:
    
    // constructors:
    SymmetricFlipGraphAlgorithms() = delete;
    inline SymmetricFlipGraphAlgorithms(const int ID,
					const ConfData& confdata,
					const Options& options,
					const Cache<SimplicialComplex>& global_triangcache,
					Cache<SimplicialComplex>& local_triangcache) :
      _ID(ID),
      _confdata(confdata),
      _options(options),
      _global_triangcache(global_triangcache),
      _local_triangcache(local_triangcache),
      _randperm_generator(confdata.no(), confdata.no()),
      _randperm(confdata.no(), confdata.no()),
      _symcount(0),
      _totalcount(0),
      _flipdist(0),
      _final_triang(),
      _min_support_triang() {}

    // destructor:
    inline ~SymmetricFlipGraphAlgorithms() {}

    // accessors:
    inline size_type         symcount()           const { return _symcount; }
    inline size_type         totalcount()         const { return _totalcount; }
    inline size_type         flipdist()           const { return _flipdist; }
    inline SimplicialComplex min_support_triang() const { return _min_support_triang; }

    // actual algorithms:
    inline bool find_regular_by_lex_increasing_flips(const SimplicialComplex& triang,
						     direction_type dir = DIR_UP,
						     bool use_regularity_checks = false) {
      if (_options.is_downstream_call()) {
	MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
				  << "lex-flip diving ... "
				  << std::endl
				  << message::unlock;
      }
      else {
	MessageStreams::verbose() << message::lock << "worker "<< _ID << ": "
				  << "lex-flip diving ... "
				  << std::endl
				  << message::unlock;
      }
      if (_global_triangcache.is_cached(triang) || _local_triangcache.is_cached(triang)) {
	
	// in this case, triang was flipped to a regular triangulation earlier,
	// and no more work has to be done:
	MessageStreams::debug() << message::lock
				<< "worker " << _ID << ": top-level cache hit."
				<< std::endl
				<< message::unlock;
	return true;
      }
      bool found_regular_triang = false;
      MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
			      << "exploring flip graph component of non-regular triangulation "
			      << triang
			      << " ..."
			      << std::endl
			      << message::unlock;
    
      // start from the given triangulation with a very fast lex-increasing dive
      // (follow a lex-flip monotone path in the flip-graph);
      // thereby, never flip-in vertices and try to flip-out vertices to reduce the support of the triangulations:    
    
      // options are determined by the needs of this exploration:
      Options find_regular_options(_options);
      if (use_regularity_checks) {
	find_regular_options.remove_options(Options::STOP_AT_TARGET);
	find_regular_options.add_options(Options::STOP_AT_REGULAR);
      }
      else {
	find_regular_options.remove_options(Options::STOP_AT_REGULAR);
	find_regular_options.add_options(Options::STOP_AT_TARGET);
      }	
      find_regular_options.add_options(Options::NO_SYMMETRIES);
    
      // start with heuristics that do not use much memory:
      // first, dive with only lex-increasing flips that do not add points to the support of new_seed (no symmetries used):
      find_regular_options.remove_options(Options::FULL_ONLY
					  | Options::UNIMODULAR_ONLY
					  | Options::OUTPUT_TRIANGS
					  | Options::STOP_AT_REGULARCOMP
					  | Options::ALL_SYMMETRIES);
      const Permutation* randpermptr = 0;
      if (dir == DIR_RANDOM) {
	_randperm = _randperm_generator();
	randpermptr = &_randperm;
      }
      switch (dir) {
      case DIR_UP:
	find_regular_options.add_options(Options::LEXFLIP_DIVE_UP_ONLY);
	break;
      case DIR_DOWN:
	find_regular_options.add_options(Options::LEXFLIP_DIVE_DOWN_ONLY);
	break;
      case DIR_BOTH:
	find_regular_options.add_options(Options::LEXFLIP_DIVE_UP_ONLY
					 | Options::LEXFLIP_DIVE_DOWN_ONLY);
	break;
      case DIR_RANDOM:
	find_regular_options.add_options(Options::LEXFLIP_DIVE_RANDDIR_ONLY);
	break;
      }

      // copy options for finding the locally optimal triangulation:
      Options find_opt_options(find_regular_options);

      // first, use monotone flipping to a local opt starting from a placing triangulation:
      find_opt_options.remove_options(Options::STOP_AT_TARGET
				      | Options::STOP_AT_REGULAR);
      find_opt_options.add_options(Options::LEXFLIP_DIVE_OPT_ONLY);
      SimplicialComplex regular_triang(_confdata.placingtriang());
      SymmetricFlipGraph find_opt_sfg(_ID,
				      _confdata,
				      find_opt_options, // options to use gkz-dfs only for monotone flipping
				      &regular_triang, // a regular seed
				      0, // no target
				      randpermptr, // pointer to the random permutation for DIR_RANDOM and 0 otherwise
				      _global_triangcache, // passes triangulations already resolved (read-only)
				      _local_triangcache // passes triangulations already resolved (may be changed)
				      );
      _final_triang = find_opt_sfg.final_triang();
      SymmetricFlipGraph lex_flip_dive_sfg(_ID,
					   _confdata,
					   find_regular_options,  // overwrites options in confdata
					   &triang, // overwrites seed in confdata
					   &_final_triang, // target is local opt of monotone flipping from a regular triangulation
					   randpermptr, // no random permutation
					   _global_triangcache, // passes triangulations already resolved (read-only)
					   _local_triangcache // passes triangulations already resolved (may be changed)
					   ); 
      found_regular_triang = lex_flip_dive_sfg.regular_triang_found();

      // get a triangulation with minimal support from the previous exploration:
      // if a triangulation found in one exploration is flip-connected to the
      // component of the regular triangulations, then so is the original triangulation:
      _min_support_triang = lex_flip_dive_sfg.min_support_triang();
      if (found_regular_triang) {
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID
				<< " done: lex-flip diving found a regular-component triangulation."
				<< std::endl
				<< message::unlock;
	
	// in this case, all traversed triangulations can be flipped to a regular triangulation:
	_local_triangcache.add(lex_flip_dive_sfg.new_triangcache());
      }
      else {

	// in this case, no regular triangulation could be reached by lex-monotone flips:
	MessageStreams::debug() << message::lock << "... worker " << _ID 
				<< " done: lexflip diving found a non-regular triangulation"
				<< '\n' << triang << '\n';
	switch(dir) {     
	case DIR_UP:
	  MessageStreams::debug() << "that cannot greedily be upflipped " << '\n';
	  break;
	case DIR_DOWN:
	  MessageStreams::debug() << "that cannot greedily be downflipped " << '\n';
	  break;
	case DIR_BOTH:
	  if (find_regular_options.stop_at_regularcomp()) {

	    // difficult to tell what exactly this is because for this option
	    // it is enough to flip to a triangulation that could be flipped
	    // to a regular triangulation before (any direction):
	    MessageStreams::debug() << "that could not greedily be up-/downflipped " << '\n';
	  }
	  else {

	    // otherwise, the cache has not been used:
	    MessageStreams::debug() << "can greedily neither be upflipped nor downflipped " << '\n';
	  }	    
	  break;
	case DIR_RANDOM:
	  MessageStreams::debug() << "that cannot greedily be upflipped w.r.t. random permutation " << _randperm << '\n';
	  break;
	}
	if (use_regularity_checks) {
	  MessageStreams::debug() << "to the regular component"
				  << std::endl
				  << message::unlock;
	}
	else {
	  MessageStreams::debug() << "to the target triangulation" << '\n'
				    << _final_triang
				    << std::endl
				    << message::unlock;
	}
      }
      return found_regular_triang;
    }

    inline bool find_regular_by_lex_repeated_randdir_flips(const SimplicialComplex& triang,
							   int how_often = 1,
							   bool use_regularity_checks = false) {
      bool found_regular_triang = false;
      for (int i = 0; i < how_often; ++i) {
	found_regular_triang = find_regular_by_lex_increasing_flips(triang, DIR_RANDOM, use_regularity_checks);
	if (found_regular_triang) {
	  break;
	}
      }
      return found_regular_triang;
    }

    inline bool find_regular_by_gkz_increasing_flips(const SimplicialComplex& triang,
						     direction_type dir = DIR_UP,
						     bool use_regularity_checks = false) {
      if (_options.is_downstream_call()) {
	MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
				<< "lex-gkz diving ... "
				<< std::endl
				<< message::unlock;
      }
      else {
	MessageStreams::verbose() << message::lock << "worker "<< _ID << ": "
				  << "lex-gkz diving ... "
				  << std::endl
				  << message::unlock;
      }
      if (_global_triangcache.is_cached(triang) || _local_triangcache.is_cached(triang)) {
      
	// in this case, triang was flipped to a regular triangulation earlier,
	// and no more work has to be done:
	MessageStreams::debug() << message::lock
				<< "worker " << _ID << ": top-level cache hit."
				<< std::endl
				<< message::unlock;
	return true;
      }

      // add options that are determined by the needs of this exploration;
      // if options.full_only() was selected in the command line,
      // then also triangulations that have no upflip-path
      // through full triangulations are counted as non-subregular;
      // if options.full_only() is not selected,
      // then only triangulations that have no upflip-path through
      // arbitrary triangulations are counted as non-subregular;
      // in the first case there maybe more non-subregulars
      // than in the second case;
      // the direction can be flipped to "downflips" by "dir":
      bool found_regular_triang = false;
      Options find_regular_options(_options);
      if (use_regularity_checks) {
	find_regular_options.remove_options(Options::STOP_AT_TARGET);
	find_regular_options.add_options(Options::STOP_AT_REGULAR);
      }
      else {
	find_regular_options.remove_options(Options::STOP_AT_REGULAR);
	find_regular_options.add_options(Options::STOP_AT_TARGET);
      }	
      find_regular_options.add_options(Options::NO_SYMMETRIES);
      find_regular_options.remove_options(Options::UNIMODULAR_ONLY
					  | Options::OUTPUT_TRIANGS
					  | Options::ALL_SYMMETRIES);
      const Permutation* randpermptr = 0;
      if (dir == DIR_RANDOM) {
	_randperm = _randperm_generator();
	randpermptr = &_randperm;
      }
      switch(dir) {
      case DIR_UP:
	find_regular_options.add_options(Options::LEXGKZ_DIVE_UP_ONLY);
	break;
      case DIR_DOWN:
	find_regular_options.add_options(Options::LEXGKZ_DIVE_DOWN_ONLY);
	break;
      case DIR_BOTH:
	find_regular_options.add_options(Options::LEXGKZ_DIVE_UP_ONLY
					 | Options::LEXGKZ_DIVE_DOWN_ONLY);
	break;
      case DIR_RANDOM:
	find_regular_options.add_options(Options::LEXGKZ_DIVE_RANDDIR_ONLY);
	break;
      }

      // copy options for finding the locally optimal triangulation:
      Options find_opt_options(find_regular_options);

      // first, use monotone flipping to a local opt starting from a placing triangulation:
      find_opt_options.remove_options(Options::STOP_AT_TARGET
				      | Options::STOP_AT_REGULAR);
      find_opt_options.add_options(Options::LEXGKZ_DIVE_OPT_ONLY);
      SimplicialComplex regular_triang(_confdata.placingtriang());
      SymmetricFlipGraph find_opt_sfg(_ID,
				      _confdata,
				      find_opt_options, // options to use gkz-dfs only for monotone flipping
				      &regular_triang, // a regular seed
				      0, // no target
				      randpermptr, // pointer to the random permutation for DIR_RANDOM and 0 otherwise
				      _global_triangcache, // passes triangulations already resolved (read-only)
				      _local_triangcache // passes triangulations already resolved (may be changed)
				      );
      _final_triang = find_opt_sfg.final_triang();
      if (!_options.stop_at_regularcomp() && !use_regularity_checks) {

	// here, we are really interested in checking for subregularity and friends;
	// we do not use regularity checks throughout the exploration;
	// however, the target needs to be regular to be able to
	// conclude subregularity from reaching the target:
	RegularityCheck regchecker(_confdata, _final_triang);
	if (!regchecker.is_regular()) {
	  
	  // in this case, the regularity-check free method cannot find a *monotone* flip-path to the regulars:
	  return false;
	}
      }
      SymmetricFlipGraph lex_gkz_dive_sfg(_ID,
					  _confdata,
					  find_regular_options, // options to use gkz-dfs only for exploration
					  &triang, // overwrites seed in confdata
					  &_final_triang, // target is local opt of monotone flipping from a regular triangulation
					  randpermptr, // pointer to the random permutation for DIR_RANDOM and 0 otherwise
					  _global_triangcache, // passes triangulations already resolved (read-only)
					  _local_triangcache // passes triangulations already resolved (may be changed)
					  );
      found_regular_triang = lex_gkz_dive_sfg.regular_triang_found() || lex_gkz_dive_sfg.target_triang_found();

      // get a triangulation with minimal support from the previous exploration:
      // if a triangulation found in one exploration is flip-connected to the
      // component of the regular triangulations, then so is the original triangulation:
      _min_support_triang = lex_gkz_dive_sfg.min_support_triang();
      if (found_regular_triang) {

	// in this case, all triangulations on a monotone path to a leaf
	// can be gkz-monotonically flipped to a regular triangulation:
	MessageStreams::debug() << message::lock << "... worker " << _ID << " done: lex-gkz diving found a regular-component triangulation."
				<< std::endl
				<< message::unlock;	      
	_local_triangcache.add(lex_gkz_dive_sfg.new_triangcache());
      }
      else {

	// in this case, no regular triangulation could be reached by gkz-monotone flips:
	MessageStreams::debug() << message::lock << "... worker " << _ID 
				<< " done: lex-gkz diving found a non-regular triangulation"
				<< '\n' << triang << '\n';
	switch(dir) {     
	case DIR_UP:
	  MessageStreams::debug() << "that cannot greedily be upflipped " << '\n';
	  break;
	case DIR_DOWN:
	  MessageStreams::debug() << "that cannot greedily be downflipped " << '\n';
	  break;
	case DIR_BOTH:
	  if (find_regular_options.stop_at_regularcomp()) {

	    // difficult to tell what exactly this is because for this option
	    // it is enough to flip to a triangulation that could be flipped
	    // to a regular triangulation before (any direction):
	    MessageStreams::debug() << "that could not greedily be up-/downflipped " << '\n';
	  }
	  else {

	    // otherwise, the cache has not been used:
	    MessageStreams::debug() << "can greedily neither be upflipped nor downflipped " << '\n';
	  }	    
	  break;
	case DIR_RANDOM:
	  MessageStreams::debug() << "that cannot greedily be upflipped w.r.t. random permutation " << _randperm << '\n';
	  break;
	}
	if (use_regularity_checks) {
	  MessageStreams::debug() << "to the regular component"
				  << std::endl
				  << message::unlock;
	}
	else {
	  MessageStreams::debug() << "to the target triangulation" << '\n'
				    << _final_triang
				    << std::endl
				    << message::unlock;
	}
      }
      return found_regular_triang;
    }

    inline bool find_regularcomp_by_gkz_increasing_flips(const SimplicialComplex& triang,
							 direction_type dir = DIR_UP) {

      // mostly used for the check of greedy sub-/supregularity:

      // start with heuristig without regularity checks:
      bool found_regular_triang = find_regular_by_gkz_increasing_flips(triang, dir, false);
      if (!found_regular_triang && _confdata.has_points()) {

	// if not conclusive, use regularity checks:
	found_regular_triang = find_regular_by_gkz_increasing_flips(triang, dir, true);
      }
      return found_regular_triang;
    }

    inline bool find_regular_by_gkz_repeated_randdir_flips(const SimplicialComplex& triang,
							   int how_often = 1,
							   bool use_regularity_checks = false) {
      bool found_regular_triang = false;
      for (int i = 0; i < how_often; ++i) {
	found_regular_triang = find_regular_by_gkz_increasing_flips(triang, DIR_RANDOM, use_regularity_checks);
	if (found_regular_triang) {
	  break;
	}
      }
      return found_regular_triang;
    }
  
    inline bool find_regular_by_bfs(const SimplicialComplex& triang) {
      MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
			      << "breadth-first search ... "
			      << std::endl
			      << message::unlock;
      if (_global_triangcache.is_cached(triang) || _local_triangcache.is_cached(triang)) {
      
	// in this case, triang was flipped to a regular triangulation earlier,
	// and no more work has to be done:
	MessageStreams::debug() << message::lock
				<< "worker " << _ID << ": top-level cache hit."
				<< std::endl
				<< message::unlock;
	return true;
      }
      bool found_regular_triang = false;

      // use a complete bfs flip-graph exploration:
      Options find_regular_options(_options);
      find_regular_options.add_options(Options::STOP_AT_REGULAR
				       | Options::STOP_AT_REGULARCOMP
				       | Options::ALL_SYMMETRIES);
      find_regular_options.remove_options(Options::FULL_ONLY
					  | Options::UNIMODULAR_ONLY
					  | Options::OUTPUT_TRIANGS
					  | Options::NO_SYMMETRIES);
      find_regular_options.add_options(Options::REDUCE_POINTS
				       | Options::DONT_ADD_POINTS);
      SymmetricFlipGraph bfs_sfg(_ID,
				 _confdata,
				 find_regular_options,
				 &triang,
				 0, // no target
				 0, // no random permutation
				 _global_triangcache, // passes triangulations already resolved (read-only)
				 _local_triangcache // passes triangulations already resolved (may be changed)
				 );
      found_regular_triang = bfs_sfg.regular_triang_found();
      _flipdist = bfs_sfg.flipdist();

      // get a triangulation with minimal support from the previous exploration:
      // if a triangulation found in one exploration is flip-connected to the
      // component of the regular triangulations, then so is the original triangulation:
      _min_support_triang = bfs_sfg.min_support_triang(); // the support-min triangulation that is non-regular (interesting for ex-post check)
      if (found_regular_triang) {
	MessageStreams::debug() << message::lock << "... worker "
				<< _ID << " done: bfs found a regular-component triangulation at flip-distance "
				<< _flipdist
				<< std::endl
				<< message::unlock;
	
	// in this case, all traversed triangulations can be flipped to a regular triangulation:
	_local_triangcache.add(bfs_sfg.new_triangcache());
      }
      else {
	_symcount = bfs_sfg.symcount();
	_totalcount = bfs_sfg.totalcount();
      }
    
      // report conclusive result after full bfs exploration:
      if (!found_regular_triang) {
	MessageStreams::forced() << message::lock << "... worker "<< _ID 
				 << " done:" << '\n'
				 << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << '\n'
				 << "%%% found a triangulation in a purely non-regular flip-graph component: %%%" << '\n'
				 << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << '\n'
				 << triang << '\n'
				 << "---       support-min triangulation in that non-regular component:      ---" << '\n'
				 << _min_support_triang << '\n'
				 << "---       number of symmetry classes in that non-regular component:     ---" << '\n'
				 << _symcount << '\n'
				 << "---       total number of triangulations in that non-regular component: ---" << '\n'
				 << _totalcount << '\n'
				 << "---       maximal flip-distance up to symmetry from seed:               ---" << '\n'
				 << _flipdist << '\n'
				 << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << '\n'
				 << std::endl
				 << message::unlock;
      }  
      return found_regular_triang;
    }
  
    inline bool find_target_by_bfs(const SimplicialComplex& triang,
				   const SimplicialComplex& target) {
      MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
			      << "breadth-first search ... "
			      << std::endl
			      << message::unlock;
      bool found_target_triang = false;

      // use a complete bfs flip-graph exploration:
      Options find_target_options(_options);
      find_target_options.add_options(Options::STOP_AT_TARGET
				      | Options::NO_REGULARITY_CHECKS
				      | Options::STOP_AT_REGULARCOMP
				      | Options::ALL_SYMMETRIES);
      find_target_options.remove_options(Options::FULL_ONLY
					 | Options::UNIMODULAR_ONLY
					 | Options::OUTPUT_TRIANGS
					 | Options::STOP_AT_REGULAR
					 | Options::NO_SYMMETRIES);
      SymmetricFlipGraph bfs_sfg(_ID,
				 _confdata,
				 find_target_options,
				 &triang, // start here
				 &target // stop if arrived here (up to symmetry)
				 );
      found_target_triang = bfs_sfg.target_triang_found();
      _flipdist = bfs_sfg.flipdist();

      // get a triangulation with minimal support from the previous exploration:
      // if a triangulation found in one exploration is flip-connected to the
      // component of the regular triangulations, then so is the original triangulation:
      _final_triang = bfs_sfg.final_triang(); // the final triangulation that is equivalent to target (if exists)
      if (found_target_triang) {
	MessageStreams::debug() << message::lock << "... worker "
				<< _ID << " done: bfs found a triangulation "
				<< _final_triang
				<< " equivalent to target "
				<< target
				<< " at flip-distance "
				<< _flipdist
				<< " from seed triang " << triang
				<< std::endl
				<< message::unlock;

	// in this case, all traversed triangulations can be flipped to a regular triangulation:
	_local_triangcache.add(bfs_sfg.new_triangcache());
      }
      else {
	_symcount = bfs_sfg.symcount();
	_totalcount = bfs_sfg.totalcount();
      }
    
      // report conclusive result after full bfs exploration:
      if (!found_target_triang) {
	MessageStreams::forced() << message::lock << "... worker "<< _ID 
				 << " done:" << '\n'
				 << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << '\n'
				 << "%%% found a triangulation not in the flip-graph component of target:    %%%" << '\n'
				 << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << '\n'
				 << "---                            triang:                                  ---" << '\n'
				 << triang << '\n'
				 << "---                            target:                                  ---" << '\n'
				 << target << '\n'
				 << "---       support-min triangulation in the component of triang:         ---" << '\n'
				 << _min_support_triang << '\n'
				 << "---       number of symmetry classes in the component of triang:        ---" << '\n'
				 << _symcount << '\n'
				 << "---       total number of triangulations in the component of triang:    ---" << '\n'
				 << _totalcount << '\n'
				 << "---       maximal flip-distance up to symmetry from seed:               ---" << '\n'
				 << _flipdist << '\n'
				 << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << '\n'
				 << std::endl
				 << message::unlock;
      }  
      return found_target_triang;
    }
  
    inline size_type enumerate_component_by_bfs(const SimplicialComplex& triang) {
      MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
			      << "breadth-first search enumeration ... "
			      << std::endl
			      << message::unlock;

      // use a complete bfs flip-graph exploration:
      Options find_regular_options(_options);
      find_regular_options.remove_options(Options::FULL_ONLY
					  | Options::UNIMODULAR_ONLY
					  | Options::OUTPUT_TRIANGS
					  | Options::NO_SYMMETRIES);
      find_regular_options.add_options(Options::REDUCE_POINTS
				       | Options::DONT_ADD_POINTS
				       | Options::ALL_SYMMETRIES);
      find_regular_options.add_options(Options::NO_REGULARITY_CHECKS);

      // enumerate component of triang up to symmetry with no regularity checks:
      SymmetricFlipGraph bfs_sfg(_ID,
				 _confdata,
				 find_regular_options,
				 &triang,
				 0 // no target
				 );
      _symcount = bfs_sfg.symcount();
      _totalcount = bfs_sfg.totalcount();
      _flipdist = bfs_sfg.flipdist();
      return _symcount;
    }

    inline bool find_regular_by_heuristic_flips(const SimplicialComplex& triang) {
      if (_options.is_downstream_call()) {
	MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
				<< "find regular heuristically by various monotone flips ... "
				<< std::endl
				<< message::unlock;
      }
      else {
	MessageStreams::verbose() << message::lock << "worker "<< _ID << ": "
				  << "find regular heuristically by various monotone flips ... "
				  << std::endl
				  << message::unlock;
      }
      
      // check cache:
      MessageStreams::debug() << message::lock
			      << "worker " << _ID << ": "
			      << "global cache size: " << _global_triangcache.size() << ", "
			      << "local cache size: " << _local_triangcache.size()
			      << std::endl
			      << message::unlock;
      if (_global_triangcache.is_cached(triang) || _local_triangcache.is_cached(triang)) {

	// in this case, triang was flipped to a regular triangulation earlier,
	// and no more work has to be done:
	MessageStreams::debug() << message::lock
				<< "worker " << _ID << ": top-level cache hit."
				<< std::endl
				<< message::unlock;
	return true;
      }

      // explore flip graph:
      bool found_regular_triang = false;

      // first, dive with only lex-increasing flips that do not add points to the support of new_seed (no symmetries used):
      MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
			      << "heuristically exploring flip graph component of non-regular triangulation "
			      << triang
			      << " ..."
			      << std::endl
			      << message::unlock;

      if (!_confdata.has_points()) {

	// since in this case we neither have regularity checks nor gkz coordinates,
	// onle diving with lex-monotone flips without regularity checks is possible:
	found_regular_triang = find_regular_by_lex_increasing_flips(triang, DIR_BOTH, false);
	if (!found_regular_triang) {
	  
	  // dive with lex-monoton random-dir flips only without regularity checks, if possible:
	  found_regular_triang = find_regular_by_lex_repeated_randdir_flips(triang, _confdata.no(), false);
	}
      }
      else {
	if (!found_regular_triang) {
	  
	  // here, more effective checks are executed ordered by increasing effort:
	  
	  // dive with gkz-monotone flips only without regularity checks:
	  found_regular_triang = find_regular_by_gkz_increasing_flips(triang, DIR_BOTH, false);
	  if (!found_regular_triang) {
	    
	    // dive with gkz-monotone random-dir flips only without regularity checks:
	    found_regular_triang = find_regular_by_gkz_repeated_randdir_flips(triang, _confdata.no(), false);
	    if (!found_regular_triang) {
	      
	      // dive with gkz-monotone flips only with regularity checks:
	      found_regular_triang = find_regular_by_gkz_increasing_flips(triang, DIR_BOTH, true);	
	      if (!found_regular_triang) {
		
		// dive with gkz-monotone random-dir flips only with regularity checks:
		found_regular_triang = find_regular_by_gkz_repeated_randdir_flips(triang, _confdata.no(), true);
	      }
	    }
	  }
	}
      }
      return found_regular_triang;
    }
  
    inline bool find_regular_by_flips(const SimplicialComplex& triang) {
      if (_options.is_downstream_call()) {
	MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
				<< "find regular by flips ... "
				<< std::endl
				<< message::unlock;
      }
      else {
	MessageStreams::verbose() << message::lock << "worker "<< _ID << ": "
				  << "find regular by flips ... "
				  << std::endl
				  << message::unlock;
      }
      
      // check cache:
      MessageStreams::debug() << message::lock
			      << "worker " << _ID << ": "
			      << "global cache size: " << _global_triangcache.size() << ", "
			      << "local cache size: " << _local_triangcache.size()
			      << std::endl
			      << message::unlock;
      if (_global_triangcache.is_cached(triang) || _local_triangcache.is_cached(triang)) {

	// in this case, triang was flipped to a regular triangulation earlier,
	// and no more work has to be done:
	MessageStreams::debug() << message::lock
				<< "worker " << _ID << ": top-level cache hit."
				<< std::endl
				<< message::unlock;
	return true;
      }      
      bool found_regular_triang = false;
      MessageStreams::debug() << message::lock << "worker "<< _ID << ": "
			      << "exploring flip graph component of non-regular triangulation "
			      << triang
			      << " ..."
			      << std::endl
			      << message::unlock;

      if (_confdata.has_points() && !CommandlineOptions::skip_heuristics()) {
	
	// start with heuristics:
	found_regular_triang = find_regular_by_heuristic_flips(triang);
      }
      if (!found_regular_triang) {
	
	// now, a complete bfs up to symmetry must give the answer;
	// try to find a placing triangulation without any regularity check:
	found_regular_triang = find_target_by_bfs(triang, _confdata.placingtriang());
      }
      return found_regular_triang;
    }
  };
  
}; // namespace topcom

#endif

// eof SymmetricFlipGraphAlgorithms.hh
