//////////////////////////////////////////////////////////////////////////
// SymmetricFlipGraph.hh
// produced: 29/06/98 jr
/////////////////////////////////////////////////////////////////////////
#ifndef SYMMETRICFLIPGRAPH_HH
#define SYMMETRICFLIPGRAPH_HH

#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>

#include <assert.h>

#include "Signal.hh"
#include "ScreenReport.hh"
#include "CheckPoint.hh"
#include "Message.hh"
#include "ContainerIO.hh"

#include "CommandlineOptions.hh"
#include "Options.hh"
#include "Cache.hh"

#include "LabelSet.hh"
#include "SimplicialComplex.hh"

#include "ConfData.hh"
#include "Chirotope.hh"
#include "Incidences.hh"
#include "Volumes.hh"
#include "Flip.hh"
#include "MarkedFlips.hh"
#include "TriangNode.hh"
#include "TriangFlips.hh"
#include "Symmetry.hh"
#include "RegularityCheck.hh"

#include "TriangResult.hh"
#include "FlipResult.hh"

#ifdef TOPCOM_CONTAINERS
#include "HashKey.hh"
#include "HashSet.hh"
namespace topcom {
  typedef HashSet<SimplicialComplex>                triang_container_type;
  typedef HashSet<TriangNode>                       orbit_type;
  typedef HashMap<TriangNode, TriangFlips>          tnode_check_type; // Node->Flips
  typedef HashMap<TriangNode, TriangFlips>          tnode_container_type; // Node->Flips
  typedef HashSet<Vector>                           gkz_container_type;
  typedef HashMap<TriangNode, symmetryptr_datapair> stabilizer_container_type;
};
#else
#include <set>
#include <map>
namespace topcom {
  typedef std::set<SimplicialComplex>                triang_container_type;
  typedef std::set<TriangNode>                       orbit_type;
  typedef std::map<TriangNode, TriangFlips>          tnode_check_type;
  typedef std::map<TriangNode, TriangFlips>          tnode_container_type; // Node->Flips
  typedef std::set<Vector>                           gkz_container_type;
  typedef std::map<TriangNode, symmetryptr_datapair> stabilizer_container_type;
};
#endif // of #ifdef TOPCOM_CONTAINERS

namespace topcom {

  class __sfg_output_triang_base {
  public:
    inline virtual Message& print_triang(Message&            msg,
					 const TriangResult& tr) const {
      return msg;
    }
  };

  class __sfg_output_triang : public __sfg_output_triang_base {
  public:
    inline virtual Message& print_triang(Message&            msg,
					 const TriangResult& tr) const {
      msg << message::lock
	  << tr << std::endl
	  << message::unlock;
      return msg;
    }
  };

  class __sfg_output_triang_debug : public __sfg_output_triang_base {
  public:
    inline virtual Message& print_triang(Message&            msg,
					 const TriangResult& tr) const {
      msg << message::lock
	  << tr << std::endl
	  << message::unlock;
      return msg;
    }
  };

  class __sfg_output_flip_base {
  public:
    inline virtual Message& print_flip(Message&          msg,
				       const FlipResult& fr) const {
      return msg;
    }
  };

  class __sfg_output_flip : public __sfg_output_flip_base {
  public:
    inline virtual Message& print_flip(Message&          msg,
				       const FlipResult& fr) const {
      msg << message::lock
	  << fr << std::endl
	  << message::unlock;
      return msg;
    }
  };

  class __sfg_output_flip_debug : public __sfg_output_flip_base {
  public:
    inline virtual Message& print_flip(Message&          msg,
				       const FlipResult& fr) const {
      msg << message::lock
	  << fr << std::endl
	  << message::unlock;
      return msg;
    }
  };

  class __sfg_output_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      return true;
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_output_checkno : public __sfg_output_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.make_card();
      return (tr.card() == CommandlineOptions::no_of_simplices());
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_output_checkmaxno : public __sfg_output_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.make_card();
      return (tr.card() <= CommandlineOptions::max_no_of_simplices());
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_output_checkpoint : public __sfg_output_pred_base {
  private:
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      for (SimplicialComplex::const_iterator sciter = tr.triang().begin();
	   sciter != tr.triang().end();
	   ++sciter) {
	if (!sciter->contains(CommandlineOptions::required_point())) {
	  return false;
	}
      }
      return true;
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_output_checksymmetry : public __sfg_output_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.set_has_required_syms(confdata.required_symmetries().fixes(tr.triang()));
      return tr.has_required_syms();
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_output_checkflag : public __sfg_output_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.make_is_flag();
      return tr.is_flag();
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_output_checknonregularity : public __sfg_output_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.set_is_regular(RegularityCheck(confdata, tr.triang()).is_regular());
      return !tr.is_regular();
    }
    inline virtual bool is_invariant() const {
      return CommandlineOptions::symmetries_are_affine();
    }
  };

  class __sfg_output_checkunimodularity : public __sfg_output_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.set_is_unimodular(tr.triang().card() * confdata.voltable().min_volume() == confdata.volume());
      return tr.is_unimodular();
    }
    inline virtual bool is_invariant() const {
      return CommandlineOptions::symmetries_are_isometric();
    }
  };

  class __sfg_output_checknontargetequiv : public __sfg_output_pred_base {
  private:
    triang_container_type _target_orbit;
  public:
    inline __sfg_output_checknontargetequiv(const SimplicialComplex& target_triang,
					    const SymmetryGroup& symmetries) :
      _target_orbit() {
      MessageStreams::debug() << message::lock
			      << "__sfg_output_checknontargetequiv(const SimplicialComplex&, const SymmetryGroup&): constructing orbit of target ..."
				<< std::endl
			      << message::unlock;
      _target_orbit.insert(target_triang);
      if (!symmetries.complete()) {
	MessageStreams::debug() << message::lock
				<< "__sfg_output_checknontargetequiv(const SimplicialComplex&, const SymmetryGroup&): symmetries not complete - exiting."
				<< std::endl
				<< message::unlock;
	exit(1);
      }
      for (SymmetryGroup::const_iterator symiter = symmetries.begin();
	   symiter != symmetries.end();
	   ++symiter) {
	SimplicialComplex new_triang = symiter->map(target_triang);
	_target_orbit.insert(new_triang);
      }
      MessageStreams::debug() << message::lock
			      << "... done: " << _target_orbit << std::endl
			      << std::endl
			      << message::unlock;
    }
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {    
      MessageStreams::debug() << message::lock
			      << "checking non-equivalence of "
			      << tr
			      << " to target triangulation ... "
			      << _target_orbit
			      << std::endl
			      << message::unlock;
      if (_target_orbit.find(tr.triang()) == _target_orbit.end()) {
	MessageStreams::debug() << message::lock
				<< "... not equivalent: true. "
				<< std::endl
				<< message::unlock;
	tr.set_is_targetequiv(false);
	return true;
      }
      else {
	MessageStreams::debug() << message::lock
				<< "... equivalent: false. "
				<< std::endl
				<< message::unlock;
	tr.set_is_targetequiv(true);
	return false;
      }
      MessageStreams::debug() << message::lock
			      << "... done."
			      << std::endl
			      << message::unlock;
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_output_findregular : public __sfg_output_pred_base {
  private:
    mutable long _count;
  public:
    inline __sfg_output_findregular() : _count(0) {}
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {    
      MessageStreams::debug() << message::lock
			      << "checking regularity? "
			      << message::unlock;
      if (++_count == CommandlineOptions::findregular_frequency()) {
	_count = 0;
	MessageStreams::debug() << message::lock
				<< "yes." << std::endl
				<< message::unlock;
	MessageStreams::debug() << message::lock
				<< "checking regularity ..." << std::endl
				<< message::unlock;
	RegularityCheck regchecker = RegularityCheck(confdata, tr.triang());
	tr.set_is_regular(regchecker.is_regular());
	if (tr.is_regular()) {
	  MessageStreams::forced() << message::lock
				   << "regular triangulation found in component:" << '\n'
				   << "triangulation: " << '\n'
				   << tr.triang() << std::endl
				   << message::unlock;
	  if (CommandlineOptions::output_heights()) {
	    tr.set_heights(regchecker.heights());
	    MessageStreams::forced() << message::lock
				     << "heights:" << '\n'
				     << tr.heights() << std::endl
				     << message::unlock;
	  }
	  return false;
	}
	else {
	  MessageStreams::debug() << message::lock
				  << "not regular - done." << std::endl
				  << message::unlock;
	}
	return true;
      }
      else {
	MessageStreams::debug() << message::lock
				<< " no regular triangulation found so far." << std::endl
				<< message::unlock;
	return true;
      }
    }
    inline virtual bool is_invariant() const {
      return CommandlineOptions::symmetries_are_affine();
    }
  };


  class __sfg_search_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      return true;
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_search_checkreg : public __sfg_search_pred_base {
  public:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      RegularityCheck regchecker(confdata, tr.triang());
      tr.set_is_regular(regchecker.is_regular());
      if (tr.is_regular()) {
	if (CommandlineOptions::output_heights()) {
	  tr.set_heights(regchecker.heights());
	}
      }
      return tr.is_regular();
    }
    inline virtual bool is_invariant() const {
      return CommandlineOptions::symmetries_are_affine();
    }
  };

  class __sfg_search_no_insertion : public __sfg_search_pred_base {
  private:
    size_type _seed_card;
  public:
    inline __sfg_search_no_insertion(const size_type seed_card) : 
      _seed_card(seed_card) {}
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.make_supportcard();
      return (tr.supportcard() <= _seed_card);
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };

  class __sfg_search_min_no_of_verts : public __sfg_search_pred_base {
  private: 
    mutable size_type _min_no_of_verts;
  public:

    // constructor:
    inline __sfg_search_min_no_of_verts(const size_type min_no_of_verts) :
      _min_no_of_verts(min_no_of_verts) {}

    // function:
    inline virtual bool operator()(const ConfData& confdata,
				   TriangResult&   tr) const {
      tr.make_supportcard();
      if (tr.supportcard() < _min_no_of_verts) {
	_min_no_of_verts = tr.supportcard();
	MessageStreams::debug() << message::lock
				<< "worker " << tr.workerID()
				<< ": found triangulation with one vertex removed:" << '\n'
				<< tr.triang() << '\n'
				<< "support:" << '\n'
				<< tr.make_support() << '\n'
				<< "number of vertices:" << '\n'
				<< tr.supportcard() << std::endl
				<< message::unlock;
	return true;
      }
      else {
	if (tr.supportcard() > _min_no_of_verts) {
	  return false;
	}
	else {
	  return true;
	}
      }
    }
    inline virtual bool is_invariant() const {
      return true;
    }
  };
    

  class SymmetricFlipGraph {
    class TriangOutputter {
    public:
      
      // in order to switch-on output of triangulations:
      typedef __sfg_output_triang_base             output_triang_base_type;
      typedef __sfg_output_triang                  output_triang_type;
      typedef __sfg_output_triang_debug            output_triang_debug_type;
    private:
      output_triang_base_type* _action;
    public:
      inline TriangOutputter() : _action(new output_triang_base_type) {}
      inline ~TriangOutputter() {
	if (_action) {
	  delete _action;
	}
      }
      inline virtual void activate() {
	if (_action) {
	  delete _action;
	}
	_action = new output_triang_type();
      }
      inline virtual void deactivate() {
	if (_action) {
	  delete _action;
	}
	_action = new output_triang_base_type();
      }
      inline virtual void activate_debug() {
	if (_action) {
	  delete _action;
	}
	_action = new output_triang_debug_type();
      }
      inline virtual Message& print_triang(Message&      msg,
					   TriangResult& tr) const {
	return _action->print_triang(msg, tr);
      }
    };
    class FlipOutputter {
    public:
      
      // in order to switch-on output of flips:
      typedef __sfg_output_flip_base             output_flip_base_type;
      typedef __sfg_output_flip                  output_flip_type;
      typedef __sfg_output_flip_debug            output_flip_debug_type;
    private:
      output_flip_base_type* _action;
    public:
      inline FlipOutputter() : _action(new output_flip_base_type) {}
      inline ~FlipOutputter() {
	if (_action) {
	  delete _action;
	}
      }
      inline virtual void activate() {
	if (_action) {
	  delete _action;
	}
	_action = new output_flip_type();
      }
      inline virtual void deactivate() {
	if (_action) {
	  delete _action;
	}
	_action = new output_flip_base_type();
      }
      inline virtual void activate_debug() {
	if (_action) {
	  delete _action;
	}
	_action = new output_flip_debug_type();
      }
      inline virtual Message& print_flip(Message&          msg,
					 const FlipResult& fr) const {
	return _action->print_flip(msg, fr);
      }
    };
    class PredicatesChecker {
    public:
    
      // in order to switch on various checks on accepted triangulations
      // (further exploration does not depend on these):
      typedef __sfg_output_pred_base             output_pred_base_type;
      typedef __sfg_output_checkno               output_checkno_type;
      typedef __sfg_output_checkmaxno            output_checkmaxno_type;
      typedef __sfg_output_checkpoint            output_checkpoint_type;
      typedef __sfg_output_checksymmetry         output_checksymmetries_type;
      typedef __sfg_output_checkflag             output_checkflag_type;
      typedef __sfg_output_checknonregularity    output_checknonregularity_type;
      typedef __sfg_output_checkunimodularity    output_checkunimodularity_type;
      typedef __sfg_output_checknontargetequiv   output_checknontargetequiv_type;
      typedef __sfg_output_findregular           output_findregular_type;
    
      // collects all checks in a vector of checks:
      typedef std::vector<output_pred_base_type*> output_pred_container_type;

      // in order to switch on various checks on triangulations:
      // (further exploration depends on these):
      typedef __sfg_search_pred_base             search_pred_base_type;
      typedef __sfg_search_checkreg              search_checkreg_type;
      typedef __sfg_search_no_insertion          search_no_insertion_type;
      typedef __sfg_search_min_no_of_verts       search_min_no_of_verts_type;

      // collects all checks in a vector of checks:
      typedef std::vector<search_pred_base_type*> search_pred_container_type;
    private:
      output_pred_container_type                  _output_predicates;
      search_pred_container_type                  _search_predicates;
    private:
      bool                                        _all_output_predicates_invariant;
      bool                                        _all_search_predicates_invariant;
    public:
      inline PredicatesChecker() :
	_output_predicates(),
	_search_predicates(),
	_all_output_predicates_invariant(true),
	_all_search_predicates_invariant(true) {}
      inline ~PredicatesChecker() {
	for (auto predicate : _output_predicates) {
	  if (predicate) {
	    delete predicate;
	  }
	}
	for (auto predicate : _search_predicates) {
	  if (predicate) {
	    delete predicate;
	  }
	}
      }
      inline PredicatesChecker(const PredicatesChecker& checker) :
	_output_predicates(checker._output_predicates),
	_search_predicates(checker._search_predicates),
	_all_output_predicates_invariant(checker._all_output_predicates_invariant),
	_all_search_predicates_invariant(checker._all_search_predicates_invariant) {}
      inline PredicatesChecker(const PredicatesChecker&& checker) :
	_output_predicates(std::move(checker._output_predicates)),
	_search_predicates(std::move(checker._search_predicates)),
	_all_output_predicates_invariant(checker._all_output_predicates_invariant),
	_all_search_predicates_invariant(checker._all_search_predicates_invariant) {}
      inline PredicatesChecker& operator=(const PredicatesChecker& checker) {
	if (this == &checker) {
	  return *this;
	}
	_output_predicates = checker._output_predicates;
	_search_predicates = checker._search_predicates;
	_all_output_predicates_invariant = checker._all_output_predicates_invariant;
	_all_search_predicates_invariant = checker._all_search_predicates_invariant;
	return *this;
      }
      inline PredicatesChecker& operator=(const PredicatesChecker&& checker) {
	if (this == &checker) {
	  return *this;
	}
	_output_predicates = std::move(checker._output_predicates);
	_search_predicates = std::move(checker._search_predicates);
	_all_output_predicates_invariant = checker._all_output_predicates_invariant;
	_all_search_predicates_invariant = checker._all_search_predicates_invariant;
	return *this;
      }

      // add new predicates and take ownership over the pointers:
      inline virtual void add_output_predicate(output_pred_base_type* new_predicate_ptr) {
	_output_predicates.push_back(new_predicate_ptr);
	if (_all_output_predicates_invariant) {
	  _all_output_predicates_invariant = new_predicate_ptr->is_invariant();
	}
      }
      inline virtual void add_search_predicate(search_pred_base_type* new_predicate_ptr) {
	_search_predicates.push_back(new_predicate_ptr);
	if (_all_search_predicates_invariant) {
	  _all_search_predicates_invariant = new_predicate_ptr->is_invariant();
	}
      }

      // check all output predicates:
      inline virtual bool check_output_predicates(const ConfData&   confdata,
						  TriangResult&     tr) const {
	for (auto predicate : _output_predicates) {
	  if (!(*predicate)(confdata, tr)) {
	    return false;
	  }
	}
	return true;
      }

      // check whether all output predicates are symmetry-invariant:
      inline virtual bool all_output_predicates_invariant() const {
	return _all_output_predicates_invariant;
      }
    
      // check all search predicates:
      inline virtual bool check_search_predicates(const ConfData&   confdata,
						  TriangResult&     tr) const {
	for (auto predicate : _search_predicates) {
	  if (!(*predicate)(confdata, tr)) {
	    return false;
	  }
	}
	return true;
      }

      // check whether all search predicates are symmetry-invariant:
      inline virtual bool all_search_predicates_invariant() const {
	return _all_search_predicates_invariant;
      }
    };
  private:
  public:
    class SymmetryWorker {
    private:
      const int                        _workerID;
      SymmetricFlipGraph*              _callerptr;
      points_simpidx_symmetries_type*  _worker_symmetriesptr;  // need updated simpidx symmetries
      TriangNode                       _tn;
      TriangNode                       _equivalent_tnode;
      Vector                           _gkz;
      Vector                           _equivalent_gkz;
      enum State {
	idle,
	hired_for_find_old_symmetry_class,
	hired_for_build_orbit_with_searchpred,
	done,
	stopped
      };
      mutable State                    _state;
    public:
      mutable std::condition_variable  worker_condition;
    private:
      SymmetryWorker() = delete;
    public:
      SymmetryWorker(SymmetryWorker&& sw);
      SymmetryWorker(const int workerID,
		     SymmetricFlipGraph&,
		     points_simpidx_symmetries_type&);
      void find_old_symmetry_class();
      void build_orbit_with_searchpred() const;

      // interface with caller:
      void pass_work_for_find_old_symmetry_class(const TriangNode&, const Vector&);
      void pass_work_for_build_orbit_with_searchpred(const TriangNode&, const Vector&);
      void stop_worker();
    private:
      bool _wake_up() const;
    public:
      void operator()();
    };
    friend class SymmetricFlipGraph::SymmetryWorker;
#ifdef CHECK_NEW
  private:
    tnode_container_type                  _all_triangs;
#endif
  private:
    static const thread_local Cache<SimplicialComplex> _dummy_global_cache;
    static thread_local       Cache<SimplicialComplex> _dummy_local_cache;
    static const std::string                            _dump_startline;
    static const std::string                            _dump_endline;
  private:

    // global data for a run:
    const int                             _ID; // in case of several instances running in parallel
    const ConfData&                       _confdata; // the input data with preprocessed data
    const Options&                        _options; // the options
    const SimplicialComplex               _seed; // start exploration here
    const SimplicialComplex               _target; // one may choose to stop when this is found
    const SymmetryGroup&                  _active_symmetries; // can be _confdata.symmetries or _confdata.valid_symmetries or empty
    const points_simpidx_symmetries_type& _active_points_simpidx_symmetries; // dto. for symmetry pairs
    const Cache<SimplicialComplex>&       _global_triangcache; // contains triangs collected in master
    const Cache<SimplicialComplex>&       _local_triangcache; // contains triangs found by caller _ID
  private:

    // internal data:
    Cache<SimplicialComplex>              _cached_triangs;
    tnode_container_type                  _previous_triangs;
    tnode_container_type                  _new_triangs;
    Vector                                _current_gkz;
    Vector                                _new_gkz;
    Vector                                _equivalent_gkz;
    gkz_container_type                    _known_gkzs;
    stabilizer_container_type             _stabilizers;
    tnode_container_type::iterator        _find_iter;
    Permutation                           _randperm;
  private:

    // node data:
    symmetryptr_datapair                  _node_symmetryptrs;
    TriangNode                            _representative;
    Symmetry                              _transformation;
    size_type                             _search_orbitsize;
    orbit_type                            _search_orbit;
    bool                                  _rep_has_search_pred;
    size_type                             _output_orbitsize;
    orbit_type                            _output_orbit;
    bool                                  _rep_has_output_pred;
  private:

    // results data:
    size_type                             _nodecount;
    size_type                             _totalcount;
    size_type                             _symcount;
    size_type                             _storedcount;
    size_type                             _flipcount;
    size_type                             _flipdist;
    bool                                  _regular_triang_found;
    bool                                  _target_triang_found;
    Cache<SimplicialComplex>              _new_triangcache; // collects triangs found by this _ID
    SimplicialComplex                     _final_triang;
    SimplicialComplex                     _min_support_triang;
    parameter_type                        _min_support_card;
    SimplicialComplex                     _min_card_triang;
    size_type                             _min_card;
  private:

    // helper classes:
    TriangOutputter                       _triang_outputter;
    FlipOutputter                         _flip_outputter;
    PredicatesChecker                     _predicates_checker;
  private:

    // checkpointing:
    ScreenReport                          _screenreport;
    CheckPoint                            _checkpoint;
    int                                   _dump_no;
    std::fstream                          _dump_str;
    bool                                  _ready_for_new_checkpoint;
  private:
    
    // for symmetry multi-threading:
    int                                   _no_of_threads;
    std::vector<std::thread>              _threads;
    std::mutex                            _old_symmetry_class_pushresults_mutex;
    std::mutex                            _searchpred_pushresults_mutex;
    int                                   _no_of_busy_threads;
    int                                   _no_of_waiting_threads;
    mutable std::mutex                    _main_mutex;
    mutable std::condition_variable       _main_condition;
    int                                   _location_of_old_symmetry_class;
    std::vector<SymmetryWorker>           _symmetry_workers;
    worker_points_simpidx_symmetries_type _worker_symmetries;
    std::vector<orbit_type>               _worker_search_orbits;
    std::vector<orbit_type>               _worker_output_orbits;
  private:
    SymmetricFlipGraph();
    SymmetricFlipGraph(const SymmetricFlipGraph&);
    SymmetricFlipGraph& operator=(const SymmetricFlipGraph&);
  public:
    
    // constructor:
    inline SymmetricFlipGraph(const int,
			      const ConfData&,
			      const Options&,
			      const SimplicialComplex* = 0, // alternative seed
			      const SimplicialComplex* = 0, // alternative target
			      const Permutation* = 0, // random permutation for random monotone flipping
			      const Cache<SimplicialComplex>& = _dummy_global_cache,
			      const Cache<SimplicialComplex>& = _dummy_local_cache);
    
    // destructor:
    inline ~SymmetricFlipGraph();
    
    // results:
    inline const size_type                 nodecount()              const { return _nodecount; }
    inline const size_type                 totalcount()             const { return _totalcount; }
    inline const size_type                 symcount()               const { return _symcount; }
    inline const size_type                 storedcount()            const { return _storedcount; }
    inline const size_type                 flipcount()              const { return _nodecount; }
    inline const size_type                 flipdist()               const { return _flipdist; }
    inline const bool                      regular_triang_found()   const { return _regular_triang_found; }
    inline const bool                      target_triang_found()    const { return _target_triang_found; }
    inline const Cache<SimplicialComplex>& new_triangcache()        const { return _new_triangcache; }
    inline const SimplicialComplex         final_triang()           const { return _final_triang; }
    inline const SimplicialComplex         min_support_triang()     const { return _min_support_triang; }
    inline const SimplicialComplex         min_card_triang   ()     const { return _min_card_triang; }

    // reporting:
    inline Message& report_flip_node_progress(Message&, bool = false);
    inline Message& report_flip_distclass_progress(Message&, bool = false);
    inline void trigger_checkpoint(bool = false);


    // internal methods:
    void _mark_equivalent_flips(const TriangNode&, 
				const tnode_container_type::iterator,
				const FlipRep&);
    void _mark_equivalent_flips(const TriangNode&,
				TriangFlips&, 
				const FlipRep&);
  
    TriangNode           _map                         (const Vector&)                      const;
    TriangNode           _map                         (const std::pair<symmetryptr_type, Symmetry>&,
						       const TriangNode&)                  const;
    void                 _map_into                    (const std::pair<symmetryptr_type, Symmetry>&,
						       const TriangNode&,
						       TriangNode&)                        const;
    TriangNode           _map_via_indexset            (const Symmetry&,
						       const TriangNode&)                  const;
    void                 _map_into_via_indexset       (const Symmetry&,
						       const TriangNode&,
						       TriangNode&)                        const;
    symmetryptr_datapair _stabilizer_ptrs             (const TriangNode&,
						       const Vector&)                      const;
    symmetryptr_datapair _stabilizer_ptrs_via_indexset(const TriangNode&,
						       const Vector&)                      const;
    inline Vector        _gkz                         (const TriangNode&)                  const;
    bool _threads_done() const;
    inline bool _exploration_done() const;
    int  _old_symmetry_class(const TriangNode&, const Vector&);
    void _process_newtriang(const TriangNode&, 
			    const TriangFlips&, 
			    const TriangNode&, 
			    const FlipRep&);
    void _process_flips(const TriangNode&,
			const TriangFlips&);
    void _bfs();
    void _bfs_step();
    bool _is_lexgkz_smaller(const Vector&,
			    const Vector&,
			    const SimplicialComplex&,
			    const SimplicialComplex&,
			    const bool = false,
			    const bool = false);
    void _dfs_lexgkz(bool = false,
		     bool = false);
    void _dfs_lexgkz(const TriangNode&,
		     const Vector&,
		     const TriangFlips&,
		     const bool = false,
		     const bool = false);
    bool _find_lexgkz_max_neighbor(const TriangNode&,
				   const Vector&,
				   const TriangFlips&,
				   TriangNode&, // receives GKZ-lexmax neighbor
				   Vector&, // receives GKZ of GKZ-lexmax neighbor
				   Flip&, // receives flip to GKZ-lexmax neighbor
				   bool = false,
				   bool = false);
    void _dive_lexgkz(bool = false,
		      bool = false);
    void _dive_lexgkz(const TriangNode&,
		      const Vector&,
		      const TriangFlips&,
		      const bool = false,
		      const bool = false);
    void _dive_to_opt_lexgkz(bool = false,
			     bool = false);
    void _dive_to_opt_lexgkz(const TriangNode&,
			     const Vector&,
			     const TriangFlips&,
			     const bool = false,
			     const bool = false);
    bool _is_lex_smaller(const SimplicialComplex&,
			 const SimplicialComplex&,
			 const bool = false,
			 const bool = false);
    bool _find_lex_max_neighbor(const TriangNode&,
				const TriangFlips&,
				TriangNode&, // receives lexmax neighbor
				Flip&, // receives flip to lexmax neighbor
				bool = false,
				bool = false);
    void _dive_lexflip(const bool = false,
		       const bool = false);
    void _dive_lexflip(const TriangNode&,
		       const TriangFlips&,
		       const bool = false,
		       const bool = false);
    void _dive_to_opt_lexflip(bool = false,
			      bool = false);
    void _dive_to_opt_lexflip(const TriangNode&,
			      const TriangFlips&,
			      bool = false,
			      bool = false);

    void _init_symmetry_workers();
    void _init();

    // stream output/input:
    std::istream& read(std::istream&);
    inline std::ostream& write(std::ostream&) const;
    friend inline std::istream& operator>>(std::istream& ist, SymmetricFlipGraph& sbfs);
    friend inline std::ostream& operator<<(std::ostream& ost, const SymmetricFlipGraph& sbfs);
  };

  inline SymmetricFlipGraph::SymmetricFlipGraph(const int                             ID,
						const ConfData&                       confdata,
						const Options&                        options,  
						const SimplicialComplex*              alternative_seedptr,
						const SimplicialComplex*              alternative_targetptr,
						const Permutation*                    randpermptr,
						const Cache<SimplicialComplex>&       global_triangcache,
						const Cache<SimplicialComplex>&       local_triangcache) :
#ifdef CHECK_NEW
    _all_triangs(),
#endif
    _ID(ID),
    _confdata(confdata),
    _options(options),
    _seed(alternative_seedptr ? *alternative_seedptr : confdata.seed()),
    _target(alternative_targetptr ? *alternative_targetptr : SimplicialComplex()),
    _active_symmetries(options.no_symmetries() ? confdata.trivial_symmetries() :
		       options.all_symmetries() ? confdata.symmetries() :
		       confdata.valid_symmetries()),
    _active_points_simpidx_symmetries(options.no_symmetries() ? confdata.trivial_points_simpidx_symmetries() :
				      options.all_symmetries() ? confdata.points_simpidx_symmetries() :
				      confdata.valid_points_simpidx_symmetries()),
    _global_triangcache(global_triangcache),
    _local_triangcache(local_triangcache),
    _cached_triangs(),
    _previous_triangs(),
    _new_triangs(),
    _current_gkz(confdata.no()), _new_gkz(confdata.no()), _equivalent_gkz(confdata.no()), 
    _known_gkzs(),
    _stabilizers(),
    _find_iter(_previous_triangs.begin()),
    _randperm(randpermptr ? *randpermptr : Permutation(confdata.no(), confdata.no())),
    _node_symmetryptrs(),
    _representative(0, confdata.no(), confdata.rank(), alternative_seedptr ? *alternative_seedptr : _confdata.seed()),  // this changes over time
    _transformation(confdata.no()),
    _search_orbitsize(0),
    _search_orbit(),
    _rep_has_search_pred(false),
    _output_orbitsize(0),
    _output_orbit(),
    _rep_has_output_pred(false),
    _nodecount(0),
    _totalcount(0),
    _symcount(0),
    _storedcount(0),
    _flipcount(0),
    _flipdist(0),
    _regular_triang_found(false),
    _target_triang_found(false),
    _new_triangcache(),
    _triang_outputter(),
    _flip_outputter(),
    _predicates_checker(),
    _screenreport(),
    _dump_no(0),
    _ready_for_new_checkpoint(false),
    _location_of_old_symmetry_class(0),
    _no_of_threads(CommandlineOptions::no_of_threads()),
    _old_symmetry_class_pushresults_mutex(),
    _searchpred_pushresults_mutex(),
    _worker_search_orbits(),
    _worker_output_orbits(),
    _worker_symmetries(),
    _no_of_busy_threads(0),
    _no_of_waiting_threads(0) {
    if (_no_of_threads > _active_symmetries.size()) {
      _no_of_threads = _active_symmetries.size();
    }
    _worker_search_orbits.resize(_no_of_threads);
    _worker_output_orbits.resize(_no_of_threads);
    _worker_symmetries.resize(_no_of_threads);
    if (!_options.is_downstream_call()) {
      Signal::init();
    }
    _init();  
  }

  inline SymmetricFlipGraph::~SymmetricFlipGraph() {
    if (CommandlineOptions::parallel_symmetries()) {
      for (int i = 0; i < _no_of_threads; ++i) {
	_symmetry_workers[i].stop_worker();
	_symmetry_workers[i].worker_condition.notify_one();
      }
      for (int i = 0; i < _no_of_threads; ++i) {
	if (_threads[i].joinable()) {
	  _threads[i].join();
	}
      }
    }
  }

  // reporting:
  inline Message& SymmetricFlipGraph::report_flip_node_progress(Message& msg, bool force) {
    if (force || _screenreport.decide_report(this->_nodecount)) {
      _screenreport.execute_flip_node_report(msg,
					     this->_ID,
					     this->_nodecount,
					     this->_symcount,
					     this->_totalcount,
					     this->_storedcount,
					     this->_flipcount,
					     this->_flipdist);
    }
    return msg;
  }
  
  inline Message& SymmetricFlipGraph::report_flip_distclass_progress(Message& msg, bool force) {
    if (force || _screenreport.decide_report(this->_nodecount)) {
      _screenreport.execute_flip_distclass_report(msg,
						  this->_ID,
						  this->_nodecount,
						  this->_symcount,
						  this->_totalcount,
						  this->_storedcount,
						  this->_flipcount,
						  this->_flipdist);
    }
    return msg;
  }

  // checkpointing:
  inline void SymmetricFlipGraph::trigger_checkpoint(bool force) {
    if (CommandlineOptions::dump_status()) {
      if (force || _checkpoint.decide_dump(this->_nodecount)) {
	MessageStreams::debug() << message::lock
				<< "dumping data:" << '\n'
				<< *this
				<< std::endl
				<< message::unlock;
	_checkpoint.open_dump();
	{
	  std::unique_lock<std::mutex> main_lock(_main_mutex);
	  _checkpoint.dump_stream() << _dump_startline << std::endl;
	  Message(_checkpoint.dump_stream()).print_timestamp();
	  write(_checkpoint.dump_stream());
	  _checkpoint.dump_stream() << _dump_endline << std::endl;
	}
	_checkpoint.close_dump();
      }
    }
  }


  // function shortcut picking up internal data:
  inline Vector SymmetricFlipGraph::_gkz(const TriangNode& tn) const {
    return _confdata.gkz(tn);
  }

  inline bool SymmetricFlipGraph::_exploration_done() const {
    return (_options.stop_at_regular() && _regular_triang_found)
      ||
      (_options.stop_at_target() && _target_triang_found);
  }

  // stream output/input:

  inline std::ostream& SymmetricFlipGraph::write(std::ostream& ost) const {
    ost << "_dump_no                " << _checkpoint.dump_no() << std::endl;
    ost << "_no                     " << _confdata.no() << std::endl;
    ost << "_rank                   " << _confdata.rank() << std::endl;
    if (_confdata.has_points()) {
      ost << "_points                 " << _confdata.points() << std::endl;
    }
    if (_confdata.has_chiro()) {
      ost << "_chiro                  " << _confdata.chiro() << std::endl;
    }
    if (_confdata.has_symmetries() && _confdata.has_valid_symmetries()) {
      ost << "_symmetries             " << _active_symmetries << std::endl;
    }
    if (_confdata.has_required_symmetries()) {
      ost << "_required_symmetries    " << _confdata.required_symmetries() << std::endl;
    }
    ost << "_options                " << _options << std::endl;
    ost << "_previous_triangs       " << _previous_triangs << std::endl;
    ost << "_new_triangs            " << _new_triangs << std::endl;
    ost << "_nodecount              " << _nodecount << std::endl;
    ost << "_totalcount             " << _totalcount << std::endl;
    ost << "_symcount               " << _symcount << std::endl;
    ost << "_storedcount            " << _storedcount << std::endl;
    ost << "_flipcount              " << _flipcount << std::endl;
    ost << "_flipdist               " << _flipdist << std::endl;
    return ost;
  }

  inline std::istream& operator>>(std::istream& ist, SymmetricFlipGraph& sfg) {
    return sfg.read(ist);
  }

  inline std::ostream& operator<<(std::ostream& ost, const SymmetricFlipGraph& sfg) {
    return sfg.write(ost);
  }
  
}; // namespace topcom

#endif

// eof SymmetricFlipGraph.hh
