////////////////////////////////////////////////////////////////////////////////
// 
// SymmetricExtensionGraphMaster.cc
//
//    produced: 22/03/2020 jr
// 
////////////////////////////////////////////////////////////////////////////////

#include <limits>
#include <sstream>
#include <thread>
#include <functional>
#include <atomic>

#include "Message.hh"

#include "SymmetricExtensionGraphMaster.hh"

namespace topcom {

  const std::string SymmetricExtensionGraphMaster::_dump_startline = "TOPCOM SymmetricExtensionGraphMaster dump start:";
  const std::string SymmetricExtensionGraphMaster::_dump_endline   = "SymmetricExtensionGraphMaster dump end.";

  SymmetricExtensionGraphMaster::Worker::Worker(const int                            workerID,
						const SymmetricExtensionGraphMaster& segm) :
    _workerID(workerID),
    _callerptr(&segm),
    _root_nodeptr(0),
    _symcount(0UL),
    _totalcount(0UL),
    _nodecount(0UL),
    _deadendcount(0UL),
    _earlydeadendcount(0UL),
    _mincard(segm._mincard),
    _mintriang(segm._mintriang),
    _maxiter_coversimptighten(0),
    _no_of_runs(0),
    _open_nodes(),
    _state(idle) {
  }

  SymmetricExtensionGraphMaster::Worker::Worker(const Worker& sw) :
    _workerID(sw._workerID),
    _callerptr(sw._callerptr),
    _root_nodeptr(sw._root_nodeptr),
    _symcount(sw._symcount),
    _totalcount(sw._totalcount),
    _nodecount(sw._nodecount),
    _deadendcount(sw._deadendcount),
    _earlydeadendcount(sw._earlydeadendcount),
    _mincard(sw._mincard),
    _mintriang(sw._mintriang),
    _maxiter_coversimptighten(0),
    _no_of_runs(sw._no_of_runs),
    _open_nodes(sw._open_nodes),
    _state(idle) {
  }

  void SymmetricExtensionGraphMaster::Worker::operator()() {
    
    // main loop of worker thread:
    MessageStreams::debug() << message::tab << "worker " << _workerID << " is spawned" << std::endl;

    // the thread_local caches must be initialized in each thread:
    SymmetricExtensionGraphNode::init_simpidx_cache(_callerptr->_symmetriesptr);
    bool master_notified = false;
    while (!is_stopped()) {

      // debug information in each loop:
      MessageStreams::debug() << message::lock
			      << message::tab << "worker " << _workerID << " entering main loop with:" << std::endl;
      MessageStreams::debug() << message::tab << "\t_callerptr->_interrupt = " << _callerptr->_interrupt << std::endl;
      MessageStreams::debug() << message::tab << "\t_callerptr->_no_of_threads = " << _callerptr->_no_of_threads << std::endl;
      MessageStreams::debug() << message::tab << "\t_callerptr->_no_of_idle_threads = " << _callerptr->_no_of_idle_threads << std::endl;
      MessageStreams::debug() << message::tab << "\t_callerptr->_no_of_pending_results = " << _callerptr->_no_of_pending_results << std::endl;
      MessageStreams::debug() << message::tab << "\t_state = " << _state << std::endl;
      MessageStreams::debug() << message::tab << "\tmaster_notified = " << master_notified << std::endl
			      << message::unlock;

      // signal handling:
      if (Signal::signal_received()) {

	// give the master the opportunity to handle the signal:
	_callerptr->_main_condition.notify_one();
      }
      
      // the actual work is done here:
      if (is_hired()) {
	MessageStreams::debug() << message::lock
				<< message::tab << "worker " << _workerID << " doing work ..." << std::endl
				<< message::unlock;
	run();
	master_notified = false;
	MessageStreams::debug() << message::lock
				<< message::tab << "... worker " << _workerID << " done" << std::endl
				<< message::unlock;
      }
      else if (is_done() && !master_notified) {

	// actual work done:
	MessageStreams::debug() << message::lock
				<< message::tab << "... worker " << _workerID << " done - notifying master" << std::endl
				<< message::unlock;
	_callerptr->_main_condition.notify_one();
	master_notified = true;
      }

      // no work - wait:
      else if (is_idle()) {
	MessageStreams::debug() << message::lock
				<< message::tab << "worker " << _workerID << " idle - wait" << std::endl
				<< message::unlock;
	std::unique_lock<std::mutex> main_lock(_callerptr->_main_waitmutex);
	worker_condition.wait(main_lock, [this] { return wake_up(); });
	MessageStreams::debug() << message::lock
				<< message::tab << "worker " << _workerID << " waking up ..." << std::endl
				<< message::unlock;
      }
    }
    MessageStreams::debug() << message::lock
			    << message::tab << "worker " << _workerID << " terminating" << std::endl
			    << message::unlock;
  }

  void SymmetricExtensionGraphMaster::_collect_results() {
    if (!_exists_pending_result()) {
      return;
    }
    for (int i = 0; i < _no_of_threads; ++i) {
      if (_workers[i].is_done()) {
	_nodecount                += _workers[i].nodecount();
	_totalcount               += _workers[i].totalcount();
	_symcount                 += _workers[i].symcount();
	_deadendcount             += _workers[i].deadendcount();
	_earlydeadendcount        += _workers[i].earlydeadendcount();
	if (_workers[i].maxiter_coversimptighten() > _maxiter_coversimptighten) {
	  _maxiter_coversimptighten = _workers[i].maxiter_coversimptighten();
	}
#ifdef COMPUTATIONS_DEBUG
	MessageStreams::debug() << message::lock
				<< "... moving remaning work into queue ..." << std::endl
				<< message::unlock;
#endif
	std::move(_workers[i].open_nodes().begin(),
		  _workers[i].open_nodes().end(),
		  std::back_inserter<std::deque<node_type> >(_open_nodes));
#ifdef COMPUTATIONS_DEBUG
	MessageStreams::debug() << message::lock
				<< "... done" << std::endl
				<< message::unlock;
#endif
	_workers[i].clear_results();
      }
    }
    _current_workbuffersize = _open_nodes.size();
    update_node_budget();
    report_progress(MessageStreams::verbose());
  }

  void SymmetricExtensionGraphMaster::_run() {
    if (CommandlineOptions::parallel_enumeration()) {
      
      // main loop of the master thread:
      SimplicialComplex::start_multithreading();
      while (
	     !_open_nodes.empty() // need to distribute work
	     || !_all_threads_idle() // need to wait for results
	     || _exists_pending_result() // need to collect results
	     ) {
	  
	// collect pending results of all done workers first:
	_collect_results();

	// check for termination by signal:
	if (Signal::signal_received()) {
	  trigger_checkpoint(true);
	  break;
	}
	
	// check for possible checkpoint if requested:
	if (CommandlineOptions::dump_status()) {
	  trigger_checkpoint(false);
	}
	  
	// check for more work and idle threads:
	if (_exists_idle_thread() && !_open_nodes.empty()) {
	    
	  // distribute work:
	  for (int i = 0; i < _no_of_threads; ++i) {
	    if (_workers[i].is_idle()) {
	      const node_type* next_nodeptr = new node_type(std::move(_open_nodes.front()));
	      _open_nodes.pop_front();
	      _workers[i].pass_work(next_nodeptr);
	    }
	    if (_open_nodes.empty()) {
	      break;
	    }
	  }
	    
	  // notify all workers with assigned work:
	  for (int i = 0; i < _no_of_threads; ++i) {
	    if (_workers[i].is_hired()) {
	      _workers[i].worker_condition.notify_one();
	    }
	  }
	}
	  
	// finally, wait if there is nothing to do:
	else {
	  MessageStreams::debug() << message::lock
				  << "master is waiting for results || work & idle_thread || termination ..." << std::endl
				  << message::unlock;
	  std::unique_lock<std::mutex> main_lock(_main_waitmutex);
	  _main_condition.wait(main_lock, [this] { return wake_up(); } );
	}
      }
      MessageStreams::debug() << message::lock
			      << "work completed - master is terminating itself" << std::endl
			      << message::unlock;
      SimplicialComplex::stop_multithreading();
    }
    else {

      if (CommandlineOptions::dump_status()) {

	// here, the node budget controls the interrupt for checkpointing:
	_node_budget = CommandlineOptions::dump_frequency();
      }
      else {
	
	// infinite node budget - one instance of SymmetricExtensionGraph
	// does the whole enumeration in one go:
	_node_budget = std::numeric_limits<size_type>::max();
      }

      size_type cnt = 0;
      while (!_open_nodes.empty()) {
	const node_type next_node(std::move(_open_nodes.front()));
	SymmetricExtensionGraph seg(0,
				    cnt,
				    _symcount,
				    _no,
				    _rank,
				    _pointsptr,
				    _chiroptr,
				    _symmetriesptr,
				    _required_symmetriesptr,
				    _simpidx_symmetriesptr,
				    _classified_symmetriesptr,
				    _switch_tableptr,
				    _admtableptr,
				    _inctableptr,
				    _voltableptr,
				    _volume,
				    &next_node,
				    _mincard,
				    _mintriang,
				    _open_nodes,
				    _print_triangs,
				    _only_fine_triangs,
				    _find_minimal_triang,
				    _node_budget,
				    &_current_workbuffersize,
				    &_interrupt);
	_nodecount                += seg.nodecount();
	_totalcount               += seg.totalcount();
	_symcount                 += seg.symcount();
	_deadendcount             += seg.deadendcount();
	_earlydeadendcount        += seg.earlydeadendcount();
	if (seg.maxiter_coversimptighten() > _maxiter_coversimptighten) {
	  _maxiter_coversimptighten = seg.maxiter_coversimptighten();
	}
	_open_nodes.pop_front();
	_current_workbuffersize = _open_nodes.size();
	
	// check for termination by signal:
	if (Signal::signal_received()) {
	  trigger_checkpoint(true);
	  break;
	}
	report_progress(MessageStreams::verbose());
	if (CommandlineOptions::dump_status()) {
	  trigger_checkpoint(false);
	}
	++cnt;
      }
    }
  }

  void SymmetricExtensionGraphMaster::_init() {
    if (_pointsptr && CommandlineOptions::use_volumes()) {
      
      // only in these cases, the volume table can be computed:
      MessageStreams::verbose() << "computing volumes table ..." << std::endl;
      _voltableptr = new Volumes(*_pointsptr, _only_fine_triangs);
      _volume = _pointsptr->volume();
      MessageStreams::verbose() << "... done." << std::endl;
    }
    if (_voltableptr) {
      MessageStreams::verbose() << "preprocessing simplex table sorted lexicographically for all full-dimensional simplices up to rank "
				<< std::min<parameter_type>(_no, _rank + 1) << " ..." << std::endl;
      SimplicialComplex::reset_index_table();
      SimplicialComplex::preprocess_index_table(_no, 0, std::min<parameter_type>(_no, _rank + 1), *_voltableptr, true);
    }
    else {
      if (CommandlineOptions::use_random_order()) {
	MessageStreams::verbose() << "preprocessing simplex table sorted randomly for all simplices up to rank "
				  << _rank + 1 << " ..." << std::endl;
      }
      else {
	MessageStreams::verbose() << "preprocessing simplex table sorted lexicographically for all full-dimensional simplices up to rank "
				  << std::min<parameter_type>(_no, _rank + 1) << " ..." << std::endl;
      }
      SimplicialComplex::reset_index_table();
      SimplicialComplex::preprocess_index_table(_no, 0, std::min<parameter_type>(_no, _rank + 1), *_chiroptr, true);
    }
    if (!CommandlineOptions::use_volumes()) {

      // volumes were only used to sort preprocessing simplex table; dump the volumes now:
      if (_voltableptr) {
	delete _voltableptr;
	_voltableptr = 0;
      }
    }
    MessageStreams::verbose() << "... done." << std::endl;
    MessageStreams::verbose() << "computing circuits ..." << std::endl;
    Circuits* circuitsptr = new Circuits(*_chiroptr);
    MessageStreams::verbose() << "... done." << std::endl;
    MessageStreams::verbose() << "computing admissibles ..." << std::endl;
    Admissibles* _admissiblesptr = new Admissibles(*circuitsptr, *_pointsptr, *_chiroptr, _only_fine_triangs);
    if (CommandlineOptions::observe_required_symmetries()) {
      _admissiblesptr->require_symmetries(*_required_symmetriesptr, *_chiroptr);
    }
    _admtableptr = _admissiblesptr;
    MessageStreams::verbose() << "... done." << std::endl;
    Vertices* verticesptr = 0;
    if (CommandlineOptions::output_asy() && (_rank == 3)) {
      MessageStreams::verbose() << "computing vertices for graphics output ..." << std::endl;
      verticesptr = new Vertices(*circuitsptr);
      MessageStreams::verbose() << "... done." << std::endl;
    }
    delete circuitsptr;
    if (CommandlineOptions::simpidx_symmetries()) {
      
      // preprocess the permutations describing the action of the symmetry group
      // on rank-subsets indexed in SimplicialComplex:
      MessageStreams::verbose() << "computing simplex-index symmetries ..." << std::endl;
      if (CommandlineOptions::use_switch_tables()) {
	MessageStreams::verbose() << "computing symmetry generators in terms of simplex indices:" << std::endl;
	symmetry_collectordata simpidx_generators;
	for (symmetry_collectordata::const_iterator geniter = _symmetriesptr->generators().begin();
	     geniter != _symmetriesptr->generators().end();
	     ++geniter) {
	  simpidx_generators.insert(geniter->simpidx_symmetry(_rank));
	}
	MessageStreams::verbose() << "switch tables directly from generators ..." << std::endl;
	_switch_tableptr = new SwitchTable<IntegerSet, lexmin_mode>(SimplicialComplex::no_of_simplices(_rank), simpidx_generators);
	MessageStreams::verbose() << "... done." << std::endl;
      }
      else {
	_simpidx_symmetriesptr = new SymmetryGroup(_symmetriesptr->simpidx_symmetries(_rank));
	if (CommandlineOptions::debug()) {
	  MessageStreams::debug() << "symmetries in terms of simplex indices:" << std::endl;
	  MessageStreams::debug() << *_simpidx_symmetriesptr << std::endl;
	}
	MessageStreams::verbose() << "... done." << std::endl;
	if (CommandlineOptions::use_classified_symmetries()) {
	  MessageStreams::verbose() << "computing classified symmetries ..." << std::endl;
	  if (CommandlineOptions::parallel_symmetries()) {
	    _classified_symmetriesptr = new ClassifiedExtensionSymmetries(ClassifiedExtensionSymmetries::simpidx_mode,
									  CommandlineOptions::no_of_threads(),
									  _admtableptr->simplices(),
									  *_simpidx_symmetriesptr);
	  }
	  else {
	    _classified_symmetriesptr = new ClassifiedExtensionSymmetries(ClassifiedExtensionSymmetries::simpidx_mode,
									  1,
									  _admtableptr->simplices(),
									  *_simpidx_symmetriesptr);
	  }
	  MessageStreams::verbose() << "... done." << std::endl;
	}
      }
    }
    else {
      MessageStreams::verbose() << "no preprocessing of simplex-index symmetries and switch tables ..." << std::endl;
      if (CommandlineOptions::use_classified_symmetries()) {
	MessageStreams::verbose() << "computing classified symmetries ..." << std::endl;
	if (CommandlineOptions::parallel_symmetries()) {
	  _classified_symmetriesptr = new ClassifiedExtensionSymmetries(ClassifiedExtensionSymmetries::pointidx_mode,
									CommandlineOptions::no_of_threads(),
									_admtableptr->simplices(),
									*_symmetriesptr);
	}
	else {
	  _classified_symmetriesptr = new ClassifiedExtensionSymmetries(ClassifiedExtensionSymmetries::pointidx_mode,
									1,
									_admtableptr->simplices(),
									*_symmetriesptr);
	}
	MessageStreams::verbose() << "... done." << std::endl;
      }
    }
    MessageStreams::verbose() << "computing positive cocircuits ..." << std::endl;
    Cocircuits* cocircuitsptr = new Cocircuits(*_chiroptr, true);
    MessageStreams::verbose() << "... done." << std::endl;
    MessageStreams::verbose() << "computing facets ..." << std::endl;
    Facets* facetsptr = new Facets(*cocircuitsptr);
    MessageStreams::verbose() << "... done." << std::endl;
    delete cocircuitsptr;
    MessageStreams::verbose() << "computing incidences ..." << std::endl;
    _inctableptr = new Incidences(*_chiroptr, *facetsptr, _admtableptr->simplices());
    MessageStreams::verbose() << "... done." << std::endl;

    // initialize the statistics file, if requested:
    if (CommandlineOptions::output_stats()) {
      Statistics::init();
    }
    
    // initialize the asymptote file, if requested:
    if (CommandlineOptions::output_asy()) {
      Graphics::init_asy(CommandlineOptions::asy_file());
      Graphics::typedef_for_partialtriang_to_asy();
      Graphics::header_to_asy(_no, _rank);
      if (_rank == 3) {
	Graphics::vertices_to_asy(*verticesptr);
	Graphics::points_to_asy(*_pointsptr);
	Graphics::nodesize_for_pointsgraphics_to_asy();
      }
      delete verticesptr;
    }

    // by now, we have seen all simplices that might be occurring during the exploration:
    // SimplicialComplex::set_preprocessed(true);
    if (CommandlineOptions::parallel_enumeration()) {
      _no_of_threads = CommandlineOptions::no_of_threads();
      _init_workers();
    }
    else {

      // we need to generate a single worker for asymptote:
      if (CommandlineOptions::output_asy()) {
	Graphics::worker_to_asy();
      }
    }

    // from here on we need to block IO:
    MessageStreams::verbose() << message::lock
			      << "generating root node with some preprocessing of symmetries ..." << std::endl
			      << message::unlock;

    // the empty root triang is the starting point of the enumeration
    // but also the ground structure into which we may easily read
    // non-trivial partial triangulations; the advantage is that
    // the pointers to the global auxiliary data are already in place:
    const PartialTriang root_partial_triang(_no,
					    _rank,
					    facetsptr,
					    _admtableptr,
					    _inctableptr,
					    _voltableptr);
    delete facetsptr;
    MessageStreams::verbose() << message::lock
			      << "exploring all symmetry classes of triangulations by extension ..." << std::endl
			      << message::unlock;
    
    // generate the root node
    if (!_simpidx_symmetriesptr) {
      
      // here, we use a representation of the symmetry group as a
      // permutation group on the vertices of simplicial complexes:
      _rootptr = new node_type(_symmetriesptr, std::move(root_partial_triang));
      
      // initialize the simplex index tables according to command line options:
      SymmetricExtensionGraphNode::init_simpidx_table(_symmetriesptr, SimplicialComplex::no_of_simplices(_rank));
    }
    else {
      
      // here, we use a representation of the symmetry group as a
      // permutation group on simplex index sets representing simplicial complexes:
      _rootptr = new node_type(_simpidx_symmetriesptr, std::move(root_partial_triang));
      
      // initialize the simplex index tables according to command line options:
      SymmetricExtensionGraphNode::init_simpidx_table(_simpidx_symmetriesptr, SimplicialComplex::no_of_simplices(_rank));
    }
    MessageStreams::debug() << message::lock;
    MessageStreams::debug() << "SymmetricExtensionGraphMaster:" << std::endl;
    MessageStreams::debug() << "root triangulation: " << *_rootptr << std::endl;
    MessageStreams::debug() << "no                : " << _rootptr->partial_triang().no() << std::endl;
    MessageStreams::debug() << "rank              : " << _rootptr->partial_triang().rank() << std::endl;
    MessageStreams::debug() << "freeintfacets     : " << _rootptr->partial_triang().freeintfacets() << std::endl;
    MessageStreams::debug() << "admissibles       : " << _rootptr->partial_triang().admissibles() << std::endl;
    MessageStreams::debug() << "admissibles table : " << *(_rootptr->partial_triang().admtableptr()) << std::endl;
    MessageStreams::debug() << "incidences        : " << *(_rootptr->partial_triang().inctableptr()) << std::endl;
    if (_rootptr->partial_triang().voltableptr()) {
      MessageStreams::debug() << "volumes           : " << *(_rootptr->partial_triang().voltableptr()) << std::endl;
    }
    MessageStreams::debug() << message::unlock;
    set_no_of_idle_threads(_no_of_threads);
    set_no_of_pending_results(0);
    if (CommandlineOptions::read_status()) {
      
      // here, we read an intermediate state from a TOPCOM dump and initialize
      // _open_node from there using the _rootptr as a template containing
      // all the necessary global information in the nodes:
      std::ifstream read_str(CommandlineOptions::read_file());
      read(read_str);
      
      MessageStreams::verbose() << "SymmetricExtensionGraphMaster initialized from file " << CommandlineOptions::read_file() << std::endl;
      if (CommandlineOptions::debug()) {
	MessageStreams::debug() << "data:" 
				<< std::endl
				<< *this
				<< std::endl;
      }
      MessageStreams::verbose() << "checkpoint status:" << std::endl;
      MessageStreams::verbose().print_hseparator();
      report_progress(MessageStreams::verbose(), true);
      MessageStreams::verbose().print_hseparator();
    }
    else {
      _open_nodes.push_back(*_rootptr);
    }
    if (CommandlineOptions::output_stats()) {
      Statistics::report();
      Statistics::reset();
    }
  }

  void SymmetricExtensionGraphMaster::_init_workers() {
    MessageStreams::verbose() << message::lock
			      << "init distributed workers ..." << std::endl
			      << message::unlock;
    for (int i = 0; i < _no_of_threads; ++i) {
      MessageStreams::verbose() << message::lock
				<< message::tab
				<< "initializing worker " << i << " ..." << std::endl
				<< message::unlock;
      _workers.emplace_back(i, *this);
      if (CommandlineOptions::output_asy()) {
	Graphics::worker_to_asy();
      }
      MessageStreams::verbose() << message::lock
				<< message::tab
				<< "... done" << std::endl
				<< message::unlock;
    }
    for (int i = 0; i < _no_of_threads; ++i) {
      MessageStreams::verbose() << message::lock
				<< message::tab
				<< "starting worker thread " << i 
				<< " ..." << std::endl
				<< message::unlock;
      _threads.push_back(std::thread(&SymmetricExtensionGraphMaster::Worker::operator(),
				     std::ref(_workers[i])));
      MessageStreams::verbose() << message::lock
				<< message::tab
				<< "... done" << std::endl
				<< message::unlock;
    }

    // from now on we need to block IO:
    MessageStreams::verbose() << message::lock
			      << "... done" << std::endl
			      << message::unlock;
  }

  void SymmetricExtensionGraphMaster::_term() {
#ifdef CONSTRUCTOR_DEBUG
    MessageStreams::debug() << message::lock
			    << "SymmetricExtensionGraphMaster::~SymmetricExtensionGraphMaster():" << '\n'
			    << "destructing owned pointers ..." << std::endl
			    << message::unlock;
#endif
    if (CommandlineOptions::parallel_enumeration()) {
      _term_workers();
    }

    // finish the asymptote file, if necessary:
    if (CommandlineOptions::output_asy()) {
      Graphics::nodepos_to_asy();
      Graphics::define_draw_node_for_partialtriang_to_asy();
      Graphics::drawings_to_asy();
      Graphics::term_asy();
    }

    // finish the statistics file, if necessary:
    if (CommandlineOptions::output_stats()) {
      Statistics::term();
    }

    // destroy all objects that have been generated by this class:
    if (_rootptr) {
      delete _rootptr;
    }
    if (_inctableptr) {
      delete _inctableptr;
    }
    if (_voltableptr) {
      delete _voltableptr;
    }
    if (_classified_symmetriesptr) {
      delete _classified_symmetriesptr;
    }
    if (_admtableptr) {
      delete _admtableptr;
    }
    if (_simpidx_symmetriesptr) {
      delete _simpidx_symmetriesptr;
    }
#ifdef CONSTRUCTOR_DEBUG
    MessageStreams::debug() << "... done" << std::endl;
#endif
  }

  void SymmetricExtensionGraphMaster::_term_workers() {
    MessageStreams::debug() << "work completed - master is stopping all workers ..." << std::endl;
    for (int i = 0; i < _no_of_threads; ++i) {
      _workers[i].stop_worker();
    }
    MessageStreams::debug() << "work completed - master is waking up all workers for termination ..." << std::endl;
    for (int i = 0; i < _no_of_threads; ++i) {
      _workers[i].worker_condition.notify_one();
    }
    MessageStreams::debug() << "... joining threads" << std::endl;
    for (int i = 0; i < _no_of_threads; ++i) {
      if (_threads[i].joinable()) {
	_threads[i].join();
      }
    }
  }

  // stream output/input:
  std::ostream& SymmetricExtensionGraphMaster::write(std::ostream& ost) const {
    ost << message::lock;
    ost << _dump_startline << std::endl;
    ost << "_dump_no                  " << _checkpoint.dump_no() << std::endl;
    ost << "_totalcount               " << _totalcount << std::endl;
    ost << "_symcount                 " << _symcount << std::endl;
    ost << "_nodecount                " << _nodecount << std::endl;
    ost << "_deadendcount             " << _deadendcount << std::endl;
    ost << "_earlydeadendcount        " << _earlydeadendcount << std::endl;
    ost << "_mincard                  " << _mincard << std::endl;
    ost << "_mintriang                " << _mintriang << std::endl;
    ost << "_maxiter_coversimptighten " << _maxiter_coversimptighten << std::endl;
    ost << "_no                       " << _no << std::endl;
    ost << "_rank                     " << _rank << std::endl;
    if (_pointsptr) {
      ost << "_points                   " << *_pointsptr << std::endl;
    }
    if (_chiroptr) {
      ost << "_chiro                    " << *_chiroptr << std::endl;
    }
    if (_symmetriesptr) {
      ost << "_symmetries               " << *_symmetriesptr << std::endl;
    }
    if (_required_symmetriesptr) {
      ost << "_required_symmetries      " << *_required_symmetriesptr << std::endl;
    }
    ost << "_current_workbuffersize   " << _current_workbuffersize << std::endl;
    ost << "_open_nodes               " << _open_nodes << std::endl;
    ost << _dump_endline << std::endl;
    ost << message::unlock;
    return ost;
  }

  std::istream& SymmetricExtensionGraphMaster::read(std::istream& ist) {
    std::string dump_line;
    std::string last_dump_line;

    // first, set the flag if the standard-first-line was found to make sure the file is a TOPCOM dump:
    std::getline(ist, dump_line);
    if (_dump_startline.compare(0, _dump_startline.length(), dump_line) != 0) {
      MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): not a TOPCOM dump file - exiting" << std::endl;
      exit(1);
    }

    // first, check whether input file is a TOPCOM dump file:
    bool is_complete(false);
    
    while ((std::getline(ist, dump_line))) {
      std::string::size_type lastPos = dump_line.find_first_not_of(" ", 0);
      std::string::size_type pos     = dump_line.find_first_of(" ", lastPos);
      std::string keyword            = dump_line.substr(lastPos, pos - lastPos);
	  
      // next, parse the partial computational result from the dump file:
      if (keyword == "_totalcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());      
	std::istringstream istrst (value, std::ios::in);      
	if (!(istrst >> _totalcount)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _totalcount - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_totalcount initialized with " << _totalcount << std::endl;
      }
      if (keyword == "_symcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _symcount)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _symcount - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_symcount initialized with " << _symcount << std::endl;
      }
      if (keyword == "_nodecount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _nodecount)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _nodecount - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_nodecount initialized with " << _nodecount << std::endl;
      }
      if (keyword == "_deadendcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _deadendcount)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _deadendcount - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_deadendcount initialized with " << _deadendcount << std::endl;
      }
      if (keyword == "_earlydeadendcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _earlydeadendcount)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _earlydeadendcount - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_earlydeadendcount initialized with " << _earlydeadendcount << std::endl;
      }
      if (keyword == "_mincard") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _mincard)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _mincard - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_mincard initialized with " << _mincard << std::endl;
      }
      if (keyword == "_mintriang") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _mintriang)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _mintriang - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_mintriang initialized with " << _mintriang << std::endl;
      }
      if (keyword == "_maxiter_coversimptighten") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _maxiter_coversimptighten)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _maxiter_coversimptighten - exiting" << std::endl;
	  exit(1);
	}
	if (CommandlineOptions::debug()) {
	  MessageStreams::debug() << "_maxiter_coversimptighten initialized with " << _maxiter_coversimptighten << std::endl;
	}
      }

      // next, some data is parsed that makes sure that the dumped computational results were
      // obtained with the "right" data:
      if (keyword == "_no") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	parameter_type no_check;
      
	if (!(istrst >> no_check)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _no - exiting" << std::endl;
	  exit(1);
	}
	if (_no != no_check) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): no of points in input differs from no of points in dump file - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "no of points in input coincides with no of points in dump file: okay" << std::endl;
      }
      if (keyword == "_rank") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	parameter_type rank_check;

	if (!(istrst >> rank_check)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _rank - exiting" << std::endl;
	  exit(1);
	}
	if (_rank != rank_check) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): rank of input differs from rank in dump file - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "rank of input coincides with rank of dump file: okay" << std::endl;
      }
      if (_pointsptr) {
	if (keyword == "_points") {
	  lastPos = dump_line.find_first_not_of(" ", pos);
	  std::string value = dump_line.substr(lastPos, dump_line.length());
	  std::istringstream istrst (value, std::ios::in);
	  PointConfiguration points_check;
	
	  if (!(istrst >> points_check)) {
	    MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _points - exiting" << std::endl;
	    exit(1);
	  }
	  if (*_pointsptr != points_check) {
	    MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): points of input differ from points in dump file - exiting" << std::endl;
	    exit(1);
	  }
	  MessageStreams::debug() << "points of input coincide with points in dump file: okay" << std::endl;
	}
      }
      if (_chiroptr) {
	if (keyword == "_chiro") {
	  lastPos = dump_line.find_first_not_of(" ", pos);
	  std::string value = dump_line.substr(lastPos, dump_line.length());
	  std::istringstream istrst (value, std::ios::in);
	  Chirotope chiro_check(*_pointsptr, false);
	
	  if (!(istrst >> chiro_check)) {
	    MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _chiro - exiting" << std::endl;
	    exit(1);
	  }
	  if ((*_chiroptr) != chiro_check) {
	    MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): chirotope of input differs from chirotope in dump file - exiting" << std::endl;
	    exit(1);
	  }
	  MessageStreams::debug() << "chirotope of input coincides with chirotope of dump file: okay" << std::endl;
	}
      }
      if (keyword == "_symmetries") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	SymmetryGroup symmetries_check(_no);

	// we expext the full symmetry group; thus, read as generators without computing closure:
	if (!(symmetries_check.read_complete_group(istrst))) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _symmetries - exiting" << std::endl;
	  exit(1);
	}
	if (*_symmetriesptr != symmetries_check) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): symmetries of input differ from symmetries in dump file - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "symmetries of input coincide with _symmetries in dump file: okay" << std::endl;
      }
      if (keyword == "_requiredsymmetries") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	SymmetryGroup required_symmetries_check(_no);
	if (!(istrst >> required_symmetries_check)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _required_symmetries - exiting" << std::endl;
	  exit(1);
	}
	if (*_required_symmetriesptr != required_symmetries_check) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): required symmetries of input differ from required symmetries in dump file - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "required symmetries of input coincide with _required_symmetries in dump file: okay" << std::endl;
      }

      // finally, read in the work buffer:
      if (keyword == "_current_workbuffersize") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _current_workbuffersize)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _current_workbuffersize - exiting" << std::endl;
	  exit(1);
	}
	MessageStreams::debug() << "_current_workbuffersize initialized with " << _current_workbuffersize << std::endl;
      }
      if (keyword == "_open_nodes") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);

	char c;

	// we read into an auxiliary structure because several pointers to
	// global data cannot be read but have to be constructed:
	std::deque<SymmetricExtensionGraphNodeReader> aux_reader;
	if (!(istrst >> aux_reader)) {
	  MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): error while reading _open_nodes data into aux_reader - exiting"
				   << std::endl;
	  exit(1);
	}

	// next, we construct the actual nodes into _open_nodes from aux_reader
	// utilizing that this class knows the global data:
	_open_nodes.clear();
	for (std::deque<SymmetricExtensionGraphNodeReader>::iterator iter = aux_reader.begin();
	     iter != aux_reader.end();
	     ++iter) {
	  _open_nodes.emplace_back(this->_symmetriesptr, // for the node
				   this->_no,            // for the partial triangulation
				   this->_rank,          // for the partial triangulation
				   this->_admtableptr,   // for the partial triangulation
				   this->_inctableptr,   // for the partial triangulation
				   this->_voltableptr,   // for the partial triangulation
				   std::move(*iter));    // the data read into the aux reader
	}
      }
      last_dump_line = dump_line;
    }

    // finally, check last line for the standard end line in order
    // to make sure that the file is complete:
    if (_dump_endline.compare(0, _dump_endline.length(), last_dump_line) != 0) {
      MessageStreams::forced() << "last dump_line: " << last_dump_line << std::endl;
      MessageStreams::forced() << "SymmetricExtensionGraphMaster::read(std::istream& ist): TOPCOM dump file is incomplete - exiting" << std::endl;
      exit(1);
    }
    return ist;
  }
  
}; // namespace topcom

// eof SymmetricExtensionGraphMaster.cc
  
