////////////////////////////////////////////////////////////////////////////////
// 
// RegularityCheck.cc
//
//    produced: 2001/10/28 jr
//
////////////////////////////////////////////////////////////////////////////////

#include <set>
#include <unordered_map>

#include "RegularityCheck.hh"

namespace topcom {

#ifdef REGULARITY_CACHE
  ConstraintCache  RegularityCheck::_cache;
#endif

  std::mutex RegularityCheck::_reg_mutex;
  size_type  RegularityCheck::cnt_calls = 0;


  // constructors:

  RegularityCheck::RegularityCheck(const PointConfiguration& points,
				   const Chirotope& chiro,
				   const Incidences& incidences, 
				   const SimplicialComplex& t) :
    _heights(points.no()),
    _coeffs(),
    _pointsptr(&points),
    _chiroptr (&chiro),
    _triangptr(&t),
    _incidencesptr(&incidences) {

    static const Vector zerovec(_chiroptr->no());
  
    // // keep a vector of matrices:
    // std::vector<StairCaseMatrix> matrices(_pointsptr->rank());

    parameter_type no   = _chiroptr->no();
    parameter_type rank = _chiroptr->rank();
    
    // we build the coefficient matrix from the chirotope and the triangulation t:
    SimplicialComplex intfacets;
    std::unordered_set<Simplex, Hash<Simplex> > unionsimps;
    
    // we ignore duplicates (disabled):
    size_type cnt_duplicates(0UL);
    
    // for all simplices in the triangulation t collect interior facets:
    for (SimplicialComplex::const_iterator iter = t.begin();
	 iter != t.end();
	 ++iter) {
      intfacets += _incidencesptr->intfacets(*iter);
    }
    size_type intfacets_cnt = intfacets.card();  
    MessageStreams::debug() << message::lock
			     << intfacets_cnt << " interior facets in triangulation " << t << std::endl
			     << message::unlock;
    
    // prepare a matrix with a column for each interior facet
    // (some may be irrelevant, but at this point we do not care):
    // _coeffs.resize(intfacets_cnt, zerovec);
    _coeffs.reserve(intfacets_cnt);
    
    parameter_type col_cnt = 0;
    
    // for all interior facets ...
    for (SimplicialComplex::const_iterator iter = intfacets.begin();
	 iter != intfacets.end();
	 ++iter) {
      
      // generate a new column with the right dimensions:
      // _coeffs.push_back(zerovec);
      
      const Simplex intfacet(*iter);
      MessageStreams::debug() << message::lock
			       << "processing interior facet " << intfacet << " ..." << std::endl
			       << message::unlock;
      
      // find the two simplices in t (or fewer, if t is partial) containing intfacet:
      const SimplicialComplex simppair(t * _incidencesptr->intcofacets(intfacet));
      MessageStreams::debug() << message::lock
			       << "simplices in partial triangulation containing it are " << simppair << std::endl
			       << message::unlock;
      if (simppair.card() < 2) {
      
	// intfacet is not interior in t, thus no constraint:
	continue;
      }
      SimplicialComplex::const_iterator pairiter(simppair.begin());
      const Simplex simp1(*pairiter);
      MessageStreams::debug() << message::lock
			       << "simp1 = " << simp1 << std::endl
			       << message::unlock;
      ++pairiter;
      const Simplex simp2(*pairiter);
      MessageStreams::debug() << message::lock
			       << "simp2 = " << simp2 << std::endl
			       << message::unlock;
      const Simplex unionsimp(simp1 + simp2);
      MessageStreams::debug() << message::lock
			       << "simp1 union simp2 = " << unionsimp << std::endl
			       << message::unlock;
      if (unionsimps.find(unionsimp) != unionsimps.end()) {
	
	// constraints on identical rank + 1 subsets will be identical:
	continue;
      }
      unionsimps.insert(unionsimp);
#ifdef REGULARITY_CACHE
      const ConstraintCacheEntry constraint_config(unionsimp, intfacet);
#endif
      
      // grab reference to the right column in resulting matrix:
      // Vector& new_col = _coeffs[col_cnt];
      
      // generate a new zero-column:
      Vector new_col(zerovec);
      ++col_cnt;
      
#ifdef REGULARITY_CACHE
      // check for cached values:
      {
	std::lock_guard<std::mutex> lock(_reg_mutex);     
	ConstraintCache::const_iterator find_iter = _cache.find(constraint_config);
	if (find_iter != _cache.end()) {
	  new_col = find_iter->second;
	  continue;
	}
      }
#endif
      
      // compute the index not in simp1:
      const Simplex simpdiff = simp2 - intfacet;
      MessageStreams::debug() << message::lock
			       << "vertex not in interior facet: " << simpdiff << std::endl
			       << message::unlock;
      if (simpdiff.empty()) {
	continue;
      }
      const parameter_type new_index = *simpdiff.begin();
      
      // simp1 is the calibration simplex --
      // if point new_index receives infinite height, then new_index is folded above aff(simp1):
      int calibrate = (*_chiroptr)(simp1);
      
      // determinants can be taken from the chirotope, since
      // RegularityCheck is only called when there are points available:
      new_col[new_index] = calibrate * _chiroptr->det(simp1);
      calibrate = -calibrate;
      
      // build a permutation out of simp1, i.e., at first, new_index is missing:
      Permutation simp1_perm(no, rank, simp1);
      
      // the remaining coefficients now receive alternating signs,
      // when the points i missing in new_index union simp1 minus i are traversed in increasing order;
      // the first missing element is the min of simp1:
      Simplex::const_iterator simp1_iter = simp1.begin();
      
      
      // count iterations:
      int cnt = 0;
      
      // overwrite first element of simp1_perm with new_index,
      // then the min of simp1 is missing:
      simp1_perm[cnt] = new_index;
      
      // iterate over further missing elements:
      while (true) {
	
	// fetch determinant from chirotope structure:
	new_col[*simp1_iter] = calibrate * _chiroptr->det(simp1_perm);
	calibrate = -calibrate;
	
	// check if we are done already:
	if (++cnt == rank) {
	  break;
	}
	
	// exchange the permutation element at cnt with the currently missing element:
	simp1_perm[cnt] = *simp1_iter;
	
	// increment iterator:
	++simp1_iter;
      }
#ifdef REGULARITY_CACHE
      {
	// cache the computational result:
	std::lock_guard<std::mutex> lock(_reg_mutex);     
	_cache[constraint_config] = new_col;
      }
#endif
      if (CommandlineOptions::debug()) {
	
	// check the result:
	Matrix simp1_matrix(*_pointsptr, simp1);
	Field det_simp1 = simp1_matrix.det();
	
	Vector check_col(_chiroptr->no());
	int calibrate = sign(det_simp1);
	const parameter_type new_index = *(simp2 - intfacet).begin();
	check_col[new_index] = new_col[new_index];
	calibrate = -calibrate;
	
	basis_type basis(simp1);
	for (Simplex::const_iterator simp_iter = simp1.begin();
	     simp_iter != simp1.end();
	     ++simp_iter) {
	  const parameter_type idx(*simp_iter);
	  basis -= idx;
	  StairCaseMatrix basis_matrix;
	  basis_matrix.push_back((*_pointsptr)[new_index]);	  
	  for (basis_type::const_iterator basis_iter = basis.begin();
	       basis_iter !=  basis.end();
	       ++basis_iter) {
	    basis_matrix.augment((*_pointsptr)[*basis_iter]);
	  }
	  check_col[idx] = calibrate * basis_matrix.det();
	  calibrate = -calibrate;
	  basis += idx;
	}
	
	if (new_col != check_col) {
	  MessageStreams::forced() << message::lock
				   << "RegularityCheck::RegularityCheck(...):"
				   << " constraint coefficient vector inconsistent with check:"
				   << "simp1    : " << simp1 << '\n'
				   << "new_index: " << new_index << '\n'
				   << "fast     : " << new_col << '\n'
				   << "old      : " << check_col << '\n'
				   << "exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
      }
      
      // bring the new column into the coefficient matrix with low memory overhead:
      _coeffs.emplace_back(new_col);
    }
    
    // remove trailing zero-columns that were not needed:
    // _coeffs.resize(col_cnt);
    
    MessageStreams::debug() << message::lock
			     << _coeffs.size() << " columns found" << std::endl;
    MessageStreams::debug() << "the coefficient matrix for regularity check:" << std::endl;
    if (_coeffs.coldim() == 0) {
      MessageStreams::debug() << "no constraints." << std::endl;
    }
    else {
      _coeffs.transpose().pretty_print(MessageStreams::debug());
    }    
    MessageStreams::debug() << message::unlock;
  }

}; // namespace topcom

// eof RegularityCheck.cc
