//////////////////////////////////////////////////////////////////////////
// SymmetricExtensionGraph.hh
// produced: 15/01/2020 jr
/////////////////////////////////////////////////////////////////////////
#ifndef SYMMETRICEXTENSIONGRAPH_HH
#define SYMMETRICEXTENSIONGRAPH_HH

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <sstream>

#include <assert.h>
#include <limits>
#include <deque>
#include <vector>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <stack>

#include <thread>
#include <atomic>
#include <condition_variable>

#include "ContainerIO.hh"

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

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

#include "ConfData.hh"
#include "PointConfiguration.hh"
#include "Chirotope.hh"
#include "Symmetry.hh"
#include "ClassifiedExtensionSymmetries.hh"
#include "SwitchTable.hh"
#include "Admissibles.hh"
#include "Incidences.hh"
#include "Volumes.hh"
#include "PartialTriang.hh"
#include "SymmetricExtensionGraphNode.hh"

#include "ScreenReport.hh"
#include "Message.hh"

#include "SymmetricFlipGraphAlgorithms.hh"

namespace topcom {
  typedef std::set<pt_complex_type>                   seg_orbit_type;
  typedef std::set<pt_complex_type>                   seg_doneset_type;
  typedef std::vector<int>                            seg_progress_vector_type;
  typedef std::vector<SymmetricExtensionGraphNode>    seg_nodes_container_type;
  typedef std::map<Simplex, SimplicialComplex>        seg_cover_cache_type;
  typedef std::map<Simplex, SimplicialComplex>        seg_coversimps_type;
  typedef std::map<Simplex, SimplicialComplex>        seg_coveradmissibles_type;
  typedef std::map<Simplex, bool>                     seg_coversimps_flag_type;
  typedef std::map<Simplex, bool>                     seg_coveradmissibles_flag_type;
  typedef std::vector<const Symmetry*>                seg_symmetryptr_iterdata;
  typedef std::map<Simplex, seg_symmetryptr_iterdata> seg_symmetryptr_table_type;
};

namespace topcom {

  class __seg_output_triang_base {
  public:
    inline virtual std::ostream& print_triang(std::ostream& ost,
					      const TriangResult& tr) const {
      return ost;
    }
  };

  class __seg_output_triang : public __seg_output_triang_base {
  public:
    inline virtual std::ostream& print_triang(std::ostream& ost,
					      const TriangResult& tr) const {
      ost << tr << '\n';
      return ost;
    }
  };
 

  class __seg_output_triang_debug : public __seg_output_triang_base {
  public:
    inline virtual std::ostream& print_triang(std::ostream& ost,
					      const TriangResult& tr) const {
      ost << tr << "\n";
      return ost;
    }
  };
 

  class SymmetricExtensionGraph {
  public:
    static constexpr int OUTPUT_FLUSH_FREQUENCY = 1000;

    // we provide a class to organize output of triangulations during enumeration:
    class TriangOutputter {
    public:
      
      // in order to switch on output of triangulations:
      typedef __seg_output_triang_base             output_triang_base_type;
      typedef __seg_output_triang                  output_triang_type;
      typedef __seg_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 std::ostream& print_triang(std::ostream& ost,
						const TriangResult& tr) const {
	return _action->print_triang(ost, tr);
      }
    };
  public:
    typedef SymmetricExtensionGraphNode node_type;
  private:
    class SymmetryWorker {
    private:
      const int                         _workerID;
      const SymmetricExtensionGraph*    _callerptr;
      pt_complex_type                   _partial_triang_to_check;
      Simplex                           _partial_triang_new_simplex;
      bool                              _work_received;
      bool                              _work_done;
      bool                              _worker_stopped;
    public:
      mutable std::condition_variable   _worker_condition;
    private:
      SymmetryWorker();
    public:
      SymmetryWorker(const int,
		     const SymmetricExtensionGraph&);
      SymmetryWorker(const SymmetryWorker&);
    private:
      const SymmetryWorker& operator=(const SymmetryWorker&);
    public:
      inline int  workerID() const;
      inline bool work_done() const;
      inline bool wake_up() const;
      inline void stop_worker();
      inline void pass_work(const pt_complex_type&, const Simplex&);
      void        check_lex_decreasing_symmetry();
      void operator()();
    };
  private:

    // sub-problem data:
    const int                             _ID;
    const size_type                       _runID;
    const size_type                       _current_symcount;
    const ConfData&                       _confdata;
    const Options&                        _options;
    const node_type*                      _rootptr;

    // parameters determined by calling master:
    const size_type                       _node_budget;
    const size_type*                      _current_workbuffersizeptr;
    const bool*                           _interruptptr;
    bool                                  _continue_work;
  private:

    // organizing output:
    TriangOutputter                 _triang_outputter;
    
    // variables holding results:
    mutable std::ostringstream      _output_stream;
    size_type                       _totalcount;
    size_type                       _symcount;
    size_type                       _nodecount;
    size_type                       _deadendcount;
    size_type                       _earlydeadendcount;
    Cache<SimplicialComplex>        _local_triangcache;
    const Cache<SimplicialComplex>& _global_triangcache; // read-only cache of Master
    size_type&                      _mincard;
    SimplicialComplex&              _mintriang;
    static std::mutex               _min_mutex;

    // statistics:
    size_type                       _seecount;

    // stack for iterative implementation _dfs_iterative of _dfs:
    std::stack<node_type>           _nodes_to_explore;
    
    // partial triangulations that could not be explored inside
    // the budget; reference set and owned by caller:
    std::deque<node_type>&          _open_nodes;

    // progress messaging:
    seg_progress_vector_type        _progress_vector;
  private:

    // variables holding intermediate results:
    size_type                       _orbitsize;
    seg_orbit_type                  _orbit;
    seg_orbit_type                  _regularity_dependent_orbit;
    mutable pt_complex_type         _equivalent_partial_triang;
    mutable int                     _iter_coversimptighten;
    mutable int                     _maxiter_coversimptighten;
  private:

    // checkpointing:
    ScreenReport                    _screenreport;

  private:
    
    // multi-threading for symmetry handling:
    int                             _no_of_threads;
    std::vector<std::thread>        _threads;

    // parallelize the check for minimality in orbit:
  
    // the following bool is shared among threads to signal
    // that the result in a thread has been obtained;
    // we can, however, get away without atomic<bool>
    // because all threads would only switch this
    // from false to true and never back;
    // so, race conditions are not an issue:
    mutable bool                            _is_old_symmetry_class;
    std::vector<SymmetryWorker>             _symmetry_workers;

    // we generate for each worker a symmetry table containing
    // for each simplex only those symmetries that
    // reduce the index of the simplex:
    // std::vector<seg_symmetryptr_table_type> _worker_symmetryptr_tables;
    mutable int                             _no_of_busy_threads;
    mutable std::mutex                      _main_mutex;
    mutable std::condition_variable         _main_condition;
    friend class SymmetryWorker;

  private:
    SymmetricExtensionGraph();
    SymmetricExtensionGraph(const SymmetricExtensionGraph&);
    SymmetricExtensionGraph& operator=(const SymmetricExtensionGraph&);
  public:
    // constructor:
    inline SymmetricExtensionGraph(const int,
				   const size_type,
				   const size_type,
				   const ConfData&,
				   const Options&,
				   const node_type*,
				   const Cache<SimplicialComplex>&,
				   size_type&,
				   SimplicialComplex&,
				   std::deque<node_type>&,
				   const size_type,
				   const size_type*,
				   const bool*);

    // destructor:
    inline ~SymmetricExtensionGraph();

    // accessors:
    inline int       ID()                                      const { return _ID; }
    inline size_type runID()                                   const { return _runID; }
    inline const std::deque<node_type>& open_nodes()           const { return _open_nodes; }
    inline const std::stack<node_type>& nodes_to_explore()     const { return _nodes_to_explore; }

    // results:
    inline size_type totalcount()                              const { return _totalcount; }
    inline size_type symcount()                                const { return _symcount; }
    inline size_type nodecount()                               const { return _nodecount; }
    inline size_type deadendcount()                            const { return _deadendcount; }
    inline size_type earlydeadendcount()                       const { return _earlydeadendcount; }
    inline const Cache<SimplicialComplex>& local_triangcache() const { return _local_triangcache; }
    inline size_type mincard()                                 const { return _mincard; }
    inline const SimplicialComplex& mintriang()                const { return _mintriang; }

    // statistics:
    inline size_type seecount()                            const { return _seecount; }
    inline const size_type maxiter_coversimptighten()      const { return _maxiter_coversimptighten; }

    // flush results buffer:
    inline Message& output_results(Message&, bool = false) const;

    // reporting:
    inline Message& report_progress(Message&);

    // internal methods:
  private:
    inline bool _threads_done() const;
    inline void _update_continue_work();
    bool _old_symmetry_class(const PartialTriang&,
			     const Simplex&);
    bool _update_coversimps(const PartialTriang&,
			    const Simplex&,
			    seg_coversimps_type&,
			    seg_coveradmissibles_type&,
			    seg_coversimps_flag_type&,
			    seg_coveradmissibles_flag_type&,
			    bool&) const;
    bool _update_coveradmissibles(const PartialTriang&,
				  const Simplex&,
				  seg_coversimps_type&,
				  seg_coveradmissibles_type&,
				  seg_coversimps_flag_type&,
				  seg_coveradmissibles_flag_type&) const;
    bool _some_possiblecoversimps_empty(const PartialTriang&,
					seg_coversimps_type&,
					seg_coveradmissibles_type&,
					seg_coversimps_flag_type&,
					seg_coveradmissibles_flag_type&) const;
    bool _some_possibleinitcoversimps_empty(const PartialTriang&,
					    seg_coversimps_type&,
					    seg_coveradmissibles_type&,
					    seg_coversimps_flag_type&,
					    seg_coveradmissibles_flag_type&) const;
    bool _noncoverable_freefacet_strong(const PartialTriang&) const;
    bool _noncoverable_freefacet_lex(const PartialTriang&) const;
    bool _noncoverable_freefacet_lean(const PartialTriang&) const;
    bool _noncoverable_freefacet(const PartialTriang&) const;
    bool _not_enough_volume(const PartialTriang&) const;
    bool _noncoverable_min_nonface(const PartialTriang&) const;
    size_type _min_no_of_missing_simplices(const PartialTriang&) const;
    void _symcount_unconditioned(const PartialTriang&, const size_type);
    bool _meets_regularity_criterion(const SimplicialComplex&, const bool);
    void _symcount_according_to_regularity_criterion(const PartialTriang&, const size_type);
    void _orbitcount_according_to_regularity_criterion(const PartialTriang&);
    void _init_symmetry_workers();
    void _init();
    void _process_node(const node_type&);
    void _dfs_iterative(const node_type&);
    void _dfs(const node_type&);
  };

  inline SymmetricExtensionGraph::SymmetricExtensionGraph(const int                                   ID,
							  const size_type                             runID,
							  const size_type                             current_symcount,
							  const ConfData&                             confdata,
							  const Options&                              options,
							  const node_type*                            root_nodeptr,
							  const Cache<SimplicialComplex>&             global_triangcache,
							  size_type&                                  mincard,
							  SimplicialComplex&                          mintriang,
							  std::deque<node_type>&                      open_nodes_collector,
							  const size_type                             node_budget,
							  const size_type*                            current_workbuffersizeptr,
							  const bool*                                 interruptptr) :
    _ID(ID),
    _runID(runID),
    _current_symcount(current_symcount),
    _confdata(confdata),
    _options(options),
    _rootptr(root_nodeptr),
    _node_budget(node_budget),
    _current_workbuffersizeptr(current_workbuffersizeptr),
    _interruptptr(interruptptr),
    _continue_work(true),
    _triang_outputter(),
    _output_stream(),
    _totalcount(0),
    _symcount(0),
    _nodecount(0),
    _deadendcount(0),
    _earlydeadendcount(0),
    _local_triangcache(),
    _global_triangcache(global_triangcache),
    _mincard(mincard),
    _mintriang(mintriang),
    _open_nodes(open_nodes_collector),
    _nodes_to_explore(),
    _progress_vector(),
    _orbitsize(0),
    _orbit(),
    _regularity_dependent_orbit(),
    _equivalent_partial_triang(),
    _iter_coversimptighten(0),
    _seecount(0),
    _maxiter_coversimptighten(0),
    _is_old_symmetry_class(false),
    _screenreport(),
    _no_of_threads(1) {
#ifdef CONSTRUCTOR_DEBUG
    MessageStreams::debug() << "SymmetricExtensionGraph::SymmetricExtensionGraph(...)" << std::endl;
#endif
    _init();
    size_type depth(_rootptr->partial_triang().card());
    if (depth > 0) {
      _progress_vector.resize(depth, -1); // set the nodes at fixed depths to -1
    }
    if (CommandlineOptions::iterative_dfs()) {
      _dfs_iterative(*_rootptr);
    }
    else {
      _dfs(*_rootptr);
    }
  }

  inline SymmetricExtensionGraph::~SymmetricExtensionGraph() {
#ifdef CONSTRUCTOR_DEBUG
    MessageStreams::debug() << "SymmetricExtensionGraph::~SymmetricExtensionGraph()" << std::endl;
#endif
    if (_options.output_triangs()) {
      output_results(MessageStreams::result(), true);
    }
    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();
	if (_threads[i].joinable()) {
	  _threads[i].join();
	}
      }
    }
  }

  // flush results buffer if requested:
  inline Message& SymmetricExtensionGraph::output_results(Message& msg, const bool force) const {
    if (force || _output_stream.tellp() > OUTPUT_FLUSH_FREQUENCY || _options.find_min()) {
      msg << message::lock
	  << _output_stream.str()
	  << message::unlock;
      _output_stream.clear();
      _output_stream.str("");
    }
    return msg;
  }
  
  // reporting:
  inline Message& SymmetricExtensionGraph::report_progress(Message& msg) {
    if (_screenreport.decide_report(this->_nodecount)) {
      _screenreport.execute_ext_worker_report(msg,
					      this->_ID,
					      this->_nodecount,
					      this->_symcount,
					      this->_totalcount,
					      this->_deadendcount,
					      this->_earlydeadendcount,
					      this->_progress_vector);
    }
    return msg;
  }

  inline bool SymmetricExtensionGraph::_threads_done() const {
    return (_no_of_busy_threads == 0);
  }

  inline void SymmetricExtensionGraph::_update_continue_work() {
    // interrupt when
    // * a checkpoint has been triggered
    // or, depending on the choice of the user, 
    // * there are not enough nodes in the workbuffer
    // * the node budget has been used up:
    
    if (*this->_interruptptr || Signal::signal_received()) {
      _continue_work = false;
      return;
    }
    else {
      if (CommandlineOptions::parallel_enumeration() && CommandlineOptions::workbuffercontrol()) {
	    
	// break if not enough work on hold or if a checkpoint is requested by the _node_budget:
	if ((*this->_current_workbuffersizeptr < CommandlineOptions::min_workbuffersize())
	    || (CommandlineOptions::dump_status() && (this->_nodecount > _node_budget))) {
	  MessageStreams::debug() << message::lock
				  << "work buffer drained down to size "
				  << *this->_current_workbuffersizeptr
				  << " - stopping enumeration with "
				  << _open_nodes.size()
				  << " unprocessed nodes" << std::endl
				  << message::unlock;
	  _continue_work = false;
	  return;
	}
      }
      else {
	    
	// enter the recursion only if there is still node budget left:
	if (this->_nodecount > _node_budget) {
	  MessageStreams::debug() << message::lock
				  << "node budget used up"
				  << " - stopping enumeration with "
				  << _open_nodes.size()
				  << " unprocessed nodes" << std::endl
				  << message::unlock;
	  _continue_work = false;
	  return;
	}
      }
    }
  }

  // inlines for subclass:
  inline int SymmetricExtensionGraph::SymmetryWorker::workerID() const {
    return _workerID;
  }

  inline bool SymmetricExtensionGraph::SymmetryWorker::wake_up() const {
    MessageStreams::debug() << message::lock
			    << message::tab
			    << "checking wake-up condition for thread "
			    << _workerID << " - value = "
			    << (_work_received || _worker_stopped) << std::endl
			    << message::unlock;
    return _work_received || _worker_stopped;
  }

  inline void SymmetricExtensionGraph::SymmetryWorker::pass_work(const pt_complex_type& pt, const Simplex& s) {
    MessageStreams::debug() << message::lock
			    << message::tab
			    << "worker " << _workerID << " is assigned to check " << pt
			    << " for old symmetry class ..." << std::endl
			    << message::unlock;
    _partial_triang_to_check    = pt;
    _partial_triang_new_simplex = s;
    _work_received              = true;
    _work_done                  = false;
    MessageStreams::debug() << message::lock
			    << message::tab
			    << "... done" << std::endl
			    << message::unlock;
  }

  inline void SymmetricExtensionGraph::SymmetryWorker::stop_worker() {
    _worker_stopped = true;
  }

}; // namespace topcom

#endif

// eof SymmetricExtensionGraph.hh
