//////////////////////////////////////////////////////////////////////////
// SymmetricExtensionGraphNode.hh
// produced: 27/11/2020 jr
/////////////////////////////////////////////////////////////////////////
#ifndef SYMMETRICEXTENSIONGRAPHNODE_HH
#define SYMMETRICEXTENSIONGRAPHNODE_HH

#include <iostream>
#include <iomanip>
#include <vector>
#include <queue>
#include <unordered_map>
#include <mutex>

#include "ContainerIO.hh"
#include "HashKey.hh"
#include "LabelSet.hh"

#include "ConfData.hh"
#include "Symmetry.hh"
#include "SimplicialComplex.hh"
#include "Admissibles.hh"
#include "Incidences.hh"
#include "Volumes.hh"
#include "PartialTriang.hh"

namespace topcom {

  
  // the following type can store for each symmetry g
  // the minimal index of the symmetric difference of a partial triangulation T and its image g(T)
  // where the convention is that the critical simplex index is std::numeric_limits<size_type>::max() if S = g(S)
  // (the simplex with this index decides the lexicographic comparison);
  // in order to save memory, we store a vector of critical indices
  // where critical_element[idx] is the index of the critical simplex for symmetries[idx]:
  typedef std::vector<size_type> critical_simpidx_table_type;

  // the following type can store an ordered set
  // containing the cliques of size at most rank
  // in the edge graph of a partial triangulation
  // that are unions of pairwise intersections
  // of three maximal simplices and are not yet faces
  // but might become faces during extension;
  // currently, a SimplicialComplex is taken as data structure
  // for fast extraction of the minimal element;
  // as a consequence, only cliques with cardinality
  // at most rank are allowed:
  typedef std::vector<Simplex> uncovered_cliques_type;

  // for the case of scarce memory we offer a cache for the symmetries represented by images of
  // simplex indices; to this end, a pair of indices is defined with the necessary features
  // for an unordered map:
  class IndexPair : public std::pair<size_type, size_type> {
  public:
    inline IndexPair(const size_type simpidx, const size_type symidx) : std::pair<size_type, size_type>(simpidx, symidx) {}
    inline size_type keysize() const { return 2; }
    inline size_type key(const size_type n) const { return n == 0 ? first : second; }
    inline bool operator==(const IndexPair& ip) { return (second == ip.second) && (first == ip.first); }
  };

  // use if memory is not an issue:
  typedef std::vector<std::vector<size_type> >                            symmetry_table_type;

  // use if memory is scarce:
  typedef std::pair<IndexPair, size_type>                                 symmetry_cache_entry_type;
  typedef std::vector<symmetry_cache_entry_type>                          symmetry_cache_type;
  typedef std::queue<IndexPair>                                           symmetry_keyvec_type;

  // the following class supports reading the object-specific data of a
  // SymmetricExtensionGraphNode; the node cannot be read directly because
  // of pointers to global data (the same holds for a PartialTriang so that
  // a PartialTriangReader has to be read instead):
  class SymmetricExtensionGraphNodeReader {
  public:
    PartialTriangReader         partial_triang_reader; // cannot be a PartialTriang because of missing pointers to global data
    size_type                   card;                  // no. of simplices in partial triangulation
    critical_simpidx_table_type critsimpidx_table;     // critical-element table
    uncovered_cliques_type      uncovered_cliques;     // special cliques in edge-graph that are not yet faces
  public:
    inline std::istream& read(std::istream& ist) {
      char c;
      
      // the expected syntax should be idential to the write syntax of PartialTriang:
      if (!(ist >> std::ws >> c >> std::ws) || (c != ContainerChars::list_start_char)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading - expected "
				 << ContainerChars::list_start_char
				 << " - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> partial_triang_reader)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading partial_triang_reader - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> std::ws >> c >> std::ws) || (c != ContainerChars::delim_char)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading - expected "
				 << ContainerChars::delim_char
				 << "after partial_triang_reader - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> card)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading card - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> std::ws >> c >> std::ws) || (c != ContainerChars::delim_char)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading - expected "
				 << ContainerChars::delim_char
				 << "after card - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> critsimpidx_table)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading critsimpidx_table - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> std::ws >> c >> std::ws) || (c != ContainerChars::delim_char)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading - expected "
				 << ContainerChars::delim_char
				 << "after critsimpidx_table - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> uncovered_cliques)) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading uncovered_cliques - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      if (!(ist >> std::ws >> c >> std::ws) || (c != ContainerChars::list_end_char)) {
	MessageStreams::forced() << "SymmetricExtensionGraphNodeReader::read(std::istream& ist): error while reading - expected "
				 << ContainerChars::list_end_char
				 << "at end of data record - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      return ist;
    }
    inline friend std::istream& operator>>(std::istream& ist, SymmetricExtensionGraphNodeReader& segnr) {
      return segnr.read(ist);
    }
  };
  
  class SymmetricExtensionGraphNode {
  public:
    static symmetry_table_type               _symmetry_images_by_element;
    static thread_local symmetry_cache_type  _symmetry_images_by_element_cache;
    // static thread_local symmetry_keyvec_type _symmetry_images_by_element_keyvec;
  private:
    PartialTriang               _partial_triang;     // partial triangulation
    size_type                   _card;               // no. of simplices in partial triangulation
    critical_simpidx_table_type _critsimpidx_table;  // critical-element table "permutation |-> critical simpidx"
    uncovered_cliques_type      _uncovered_cliques;  // complex of cliques that are not yet faces

    // only for single-element evaluation statistics - can therefore be mutable:
    mutable size_type           _seecount;
  private:
    SymmetricExtensionGraphNode() = delete;
  public:
    
    // initializer for static data:
    inline static void init_simpidx_table(const ConfData&, const size_type);
    inline static void init_simpidx_cache();
    
    // constructors:
    inline SymmetricExtensionGraphNode(const SymmetricExtensionGraphNode&);
    inline SymmetricExtensionGraphNode(SymmetricExtensionGraphNode&&);
    inline SymmetricExtensionGraphNode(const SymmetricExtensionGraphNode&,
				       const PartialTriang&,
				       const size_type,
				       const critical_simpidx_table_type&,
				       const uncovered_cliques_type& = uncovered_cliques_type());
    inline SymmetricExtensionGraphNode(const SymmetricExtensionGraphNode&,
				       PartialTriang&&,
				       const size_type,
				       critical_simpidx_table_type&&,
				       uncovered_cliques_type&& = uncovered_cliques_type());

    // compute a node from scratch for double-check purposes
    // (the root node is computed for pt = empty partial triangulation):
    SymmetricExtensionGraphNode(const ConfData&,
				const PartialTriang&,
				const size_type);
    SymmetricExtensionGraphNode(const ConfData&,
				PartialTriang&&,
				const size_type);

    // construct a node from global data and local reader data
    // whose members are moved into the node:
    inline SymmetricExtensionGraphNode(const ConfData&,
				       SymmetricExtensionGraphNodeReader&&);
    
    // destructor:
    inline ~SymmetricExtensionGraphNode();

    // assignment:
    inline SymmetricExtensionGraphNode& operator=(const SymmetricExtensionGraphNode&);
    inline SymmetricExtensionGraphNode& operator=(SymmetricExtensionGraphNode&&);

    // functions:
 
    // the following is the core function:
    // it checks whether the child node of this node by extending partial triangulation by a new simplex
    // is lex-minimal; it is assumed that the new simplex has an index that is larger than all the existing
    // simplices in the partial triangulation; the critical simplex table is updated during the checking process
    // in the second non-const reference argument, and the stabilizer count is stored in the third non-const
    // pointer argument; the final non-const pointer argument receives the number of single-element evaluations
    // that were needed to obtain the result:
    bool child_is_lexmin(const ConfData&,
			 const PartialTriang&,
			 const Simplex&,
			 critical_simpidx_table_type*,
			 size_type*) const;
    
    // the same, but based on the symmetry group representation on points:
    bool child_is_lexmin_lean(const ConfData&,
			      const PartialTriang&,
			      const Simplex&,
			      critical_simpidx_table_type*,
			      size_type*) const;

    // auxiliary function to compute critical simplex from scratch for a symmetry:
    size_type critical_simpidx(const SimplicialComplex&,
			       const Symmetry&,
			       const size_type) const;
    
    // the same, but based on the symmetry group representation on points:
    size_type critical_simpidx_lean(const SimplicialComplex&,
				    const Symmetry&,
				    const size_type) const;

    // the following is used for an incremental check whether or not a partial triangulation
    // is potentially extendable to a flag triangulation:
    bool child_is_flagfeasible(const ConfData&,
			       const PartialTriang&,
			       const Simplex&,
			       uncovered_cliques_type*) const;

    // accessors:
    inline const PartialTriang&               partial_triang()          const { return _partial_triang; }
    inline const size_type                    card()                    const { return _card; }
    inline const critical_simpidx_table_type& critsimpidx_table()       const { return _critsimpidx_table; }
    inline const uncovered_cliques_type&      uncovered_cliques()       const { return _uncovered_cliques; }

    // single-element evaluation statistics:
    inline const size_type                    seecount()                const { return _seecount; }
    inline void                               reset_seecount()          const { _seecount = 0; }

    // internal functions:
    void _populate_critsimpidx_table(const ConfData&, const PartialTriang&, const size_type);
 
    // stream input/output (plain input is not supported because of the pointers to additional global data):
    inline std::istream& read(std::istream&);
    inline friend std::istream& operator>>(std::istream&, SymmetricExtensionGraphNode&);
    inline std::ostream& write(std::ostream&) const;
    inline friend std::ostream& operator<<(std::ostream&, const SymmetricExtensionGraphNode&);
  };

  // initializer for static data:
  inline void SymmetricExtensionGraphNode::init_simpidx_table(const ConfData& confdata,
							      const size_type n) {
    if (CommandlineOptions::simpidx_symmetries()) {

      // here, we simply reorganize the symmetries in a more cache coherent way in a new table:
      try {
	_symmetry_images_by_element.resize(confdata.simpidx_symmetries().n(), std::vector<size_type>(confdata.simpidx_symmetries().size(), std::numeric_limits<size_type>::max()));
      }
      catch (...) {
	MessageStreams::forced() << message::lock
				 << "SymmetricExtensionGraphNode::SymmetricExtensionGraphNode(const SymmetryGroup&, const PartialTriang&): "
				 << "allocation of " << confdata.simpidx_symmetries().size() * confdata.simpidx_symmetries().n() << " int elements failed - exiting"
				 << std::endl
				 << message::unlock;
	exit(1);
      }
      for (size_type symidx = 0; symidx < confdata.simpidx_symmetries().size(); ++symidx) {
	for (parameter_type elm = 0; elm < confdata.simpidx_symmetries().n(); ++elm) {
	  _symmetry_images_by_element[elm][symidx] = confdata.simpidx_symmetries()[symidx][elm];
	}
      }
    }
    else {
      if (!CommandlineOptions::memopt()) {

	// here, we prepare a complete table that is populated on the fly when needed:
	try {
	  _symmetry_images_by_element.resize(n, std::vector<size_type>(confdata.valid_symmetries().size(), std::numeric_limits<size_type>::max()));
	}
	catch (...) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricExtensionGraphNode::SymmetricExtensionGraphNode(const SymmetryGroup&, const PartialTriang&): "
				   << "allocation of " << confdata.valid_symmetries().size() * n << " int elements failed - exiting"
				   << std::endl
				   << message::unlock;
	  exit(1);
	}
      }
      else if (!CommandlineOptions::parallel_enumeration()) {

	// in this case, the main thread needs to initialize the thread_local cache:
	init_simpidx_cache();
      }
    }      
  }

  inline void SymmetricExtensionGraphNode::init_simpidx_cache() {
    if (!CommandlineOptions::simpidx_symmetries() && CommandlineOptions::memopt()) {
      if (CommandlineOptions::localcache() > 0) {
	MessageStreams::forced() << message::lock
				 << "init cache:" << std::endl
				 << message::unlock;
	_symmetry_images_by_element_cache.resize(CommandlineOptions::localcache() / CommandlineOptions::no_of_threads() + 1,
						 std::pair<IndexPair, size_type>(IndexPair(std::numeric_limits<size_type>::max(),
											   std::numeric_limits<size_type>::max()),
										 std::numeric_limits<size_type>::max()));
      }
    }
  }
  
  // constructors:
  inline SymmetricExtensionGraphNode::SymmetricExtensionGraphNode(const SymmetricExtensionGraphNode& segn) :
    _partial_triang(segn._partial_triang),
    _card(segn._card),
    _critsimpidx_table(segn._critsimpidx_table),
    _uncovered_cliques(segn._uncovered_cliques),
    _seecount(segn._seecount) {
  }

  inline SymmetricExtensionGraphNode::SymmetricExtensionGraphNode(SymmetricExtensionGraphNode&& segn) :
    _partial_triang(std::move(segn._partial_triang)),
    _card(segn._card),
    _critsimpidx_table(std::move(segn._critsimpidx_table)),
    _uncovered_cliques(std::move(segn._uncovered_cliques)),
    _seecount(segn._seecount) {
  }

  inline SymmetricExtensionGraphNode::SymmetricExtensionGraphNode(const SymmetricExtensionGraphNode&  node,
								  const PartialTriang&                partial_triang,
								  const size_type                     card,
								  const critical_simpidx_table_type&  critsimpidx_table,
								  const uncovered_cliques_type&       uncovered_cliques) :
    _partial_triang(partial_triang),
    _card(card),
    _critsimpidx_table(critsimpidx_table),
    _uncovered_cliques(uncovered_cliques),
    _seecount(0) {
  }
  
  inline SymmetricExtensionGraphNode::SymmetricExtensionGraphNode(const SymmetricExtensionGraphNode&  node,
								  PartialTriang&&                     partial_triang,
								  const size_type                     card,
								  critical_simpidx_table_type&&       critsimpidx_table,
								  uncovered_cliques_type&&            uncovered_cliques) :
    _partial_triang(std::move(partial_triang)),
    _card(card),
    _critsimpidx_table(std::move(critsimpidx_table)),
    _uncovered_cliques(std::move(uncovered_cliques)),
    _seecount(0) {
  }
  
  inline SymmetricExtensionGraphNode::SymmetricExtensionGraphNode(const ConfData&                     confdata,
								  SymmetricExtensionGraphNodeReader&& segnr) :
    _partial_triang(confdata, std::move(segnr.partial_triang_reader)),
    _card(_partial_triang.card()), // declaration of _partial_triang must precede the one of _card for this to work
    _critsimpidx_table(std::move(segnr.critsimpidx_table)),
    _uncovered_cliques(std::move(segnr.uncovered_cliques)),
    _seecount(0) {
  }
  
  // destructor:
  inline SymmetricExtensionGraphNode::~SymmetricExtensionGraphNode() {
#ifdef DEBUG
    MessageStreams::debug() << message::lock;
    size_type max_bucket_size = 0;
    for (size_type n = 0; n < _symmetry_images_by_element_cache.bucket_count(); ++n) {
      if (max_bucket_size < _symmetry_images_by_element_cache.bucket_size(n)) {
	max_bucket_size = _symmetry_images_by_element_cache.bucket_size(n);
      }
    }
    MessageStreams::debug() << "max bucket size of cache is " << max_bucket_size << std::endl
			    << message::unlock;
#endif
  }

  // assignment:
  inline SymmetricExtensionGraphNode& SymmetricExtensionGraphNode::operator=(const SymmetricExtensionGraphNode& segn) {
    if (this == &segn) {
      return *this;
    }
    _partial_triang          = segn._partial_triang;
    _card                    = segn._card,
    _critsimpidx_table       = segn._critsimpidx_table;
    _uncovered_cliques       = segn._uncovered_cliques;
    _seecount                = segn._seecount;
    return *this;
  }

  inline SymmetricExtensionGraphNode& SymmetricExtensionGraphNode::operator=(SymmetricExtensionGraphNode&& segn) {
    if (this == &segn) {
      return *this;
    }
    _partial_triang          = std::move(segn._partial_triang);
    _card                    = segn._card,
    _critsimpidx_table       = std::move(segn._critsimpidx_table);
    _uncovered_cliques       = std::move(segn._uncovered_cliques);
    _seecount                = segn._seecount;
    return *this;
  }

  // stream input/output:
  inline std::istream& SymmetricExtensionGraphNode::read(std::istream& ist) {
    MessageStreams::forced() << message::lock
			     << "std::istream& SymmetricExtensionGraphNode::read(std::ostream& ist) const: "
			     << "reading from stream not supported because pointers to global data are required - exiting"
			     << std::endl
			     << message::unlock;
    exit(1);
    return ist;
  }

  inline std::istream& operator>>(std::istream& ist, SymmetricExtensionGraphNode& ssgn) {
    return ssgn.read(ist);
  }
    
  inline std::ostream& SymmetricExtensionGraphNode::write(std::ostream& ost) const {
    ost << ContainerChars::list_start_char
	<< _partial_triang
	<< ContainerChars::delim_char
	<< _card
	<< ContainerChars::delim_char
	<< _critsimpidx_table
	<< ContainerChars::delim_char
	<< _uncovered_cliques
	<< ContainerChars::list_end_char;
    return ost;
  }

  inline std::ostream& operator<<(std::ostream& ost, const SymmetricExtensionGraphNode& ssgn) {
    return ssgn.write(ost);
  }

}; // namespace topcom

#endif

// eof SymmetricExtensionGraphNode.hh
