////////////////////////////////////////////////////////////////////////////////
// 
// 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 ConfData& confdata, 
				   const SimplicialComplex& triang,
				   const SimplicialComplex& coplanar) :
    _confdata(confdata),
    _triang(triang),
    _heights(confdata.no()),
    _coeffs() {

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

    const parameter_type no   = _confdata.no();
    const parameter_type rank = _confdata.rank();
    
    // we build the coefficient matrix from the chirotope and the triangulation t:
    SimplicialComplex intfacets(triang.pseudointerior());
    std::unordered_set<Simplex, Hash<Simplex> > unionsimps;
    
    // we ignore duplicates (disabled):
    size_type cnt_duplicates(0UL);
    size_type intfacets_cnt = intfacets.card();  
    MessageStreams::debug() << message::lock
			     << intfacets_cnt << " interior facets in triangulation " << _triang << std::endl
			     << message::unlock;
    
    // prepare a matrix with one or two columns for each interior facet
    // (some may be irrelevant, but at this point we do not care):
    // _coeffs.resize(intfacets_cnt, zerovec);
    _coeffs.reserve(2 * 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(_triang * _confdata.inctable().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;
      }
      bool coplanarity_condition(false);
      if (coplanar.superset(simppair)) {

	// here, instead of a strict folding condition with right-hand side _rhs = 1
	// a coplanarity condition is generated in the matrix _coeffs
	// by a vector and its negative and right-hand side _rhs = 0:
	coplanarity_condition = true;
      }

      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 and a right-hand side:
      Vector new_col(zerovec);
      Field rhsval(FieldConstants::ONE);
      if (coplanarity_condition) {
	rhsval = FieldConstants::ZERO;
      }

      ++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 = _confdata.chiro()(simp1);
      
      // determinants can be taken from the chirotope, since
      // RegularityCheck is only called when there are points available:
      new_col[new_index] = calibrate * _confdata.chiro().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 * _confdata.chiro().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(_confdata.points(), simp1);
	Field det_simp1 = simp1_matrix.det();
	
	Vector check_col(_confdata.chiro().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(_confdata.points()[new_index]);
	  for (basis_type::const_iterator basis_iter = basis.begin();
	       basis_iter !=  basis.end();
	       ++basis_iter) {
	    basis_matrix.augment(_confdata.points()[*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:
      if (coplanarity_condition) {

	// an equation "lifting volume = 0" is simulated by two inequalities:
	_coeffs.push_back(new_col);
	_rhs.push_back(rhsval);
	_coeffs.emplace_back(new_col.scale(FieldConstants::MINUSONE));
	_rhs.push_back(rhsval);
      }
      else {

	// an inequality "lifting volume >= 1" is generated:
	_coeffs.push_back(new_col);
	_rhs.push_back(rhsval);
      }
    }

    // remove trailing zero-columns that were not needed:
    // _coeffs.resize(col_cnt);
    if (CommandlineOptions::debug()) {
      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 {
	Matrix printmatrix(_coeffs.transpose());
	printmatrix.augment(_rhs);
	printmatrix.pretty_print(MessageStreams::debug());
      }    
      MessageStreams::debug() << message::unlock;
    }
  }

}; // namespace topcom

// eof RegularityCheck.cc
