////////////////////////////////////////////////////////////////////////////////
// 
// Admissibles.cc
//
//    produced: 19/04/98 jr
// 
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <sstream>
#include <iomanip>
#include <ctype.h>
#include <string.h>
#include <thread>

#include "VertexFacetTable.hh"

#include "Permutation.hh"
#include "Admissibles.hh"

namespace topcom {
  void Admissibles::Worker::operator()(const SimplicialComplex& universe,
				       const VertexFacetTable&  vertexfacets,
				       const Circuits&          circuits) {

    // chained message stream will not be locked altogether - thus generated the output beforehand:
    MessageStreams::verbose() << message::lock
			      << message::tab << "thread " << std::setw(2) << _ID << ": generating admissibles ..."
			      << std::endl
			      << message::unlock;
    size_type count(0);
    for (SimplicialComplex::iterator sciter = _input.begin(); sciter != _input.end(); ++sciter) {
      const Simplex simp(*sciter);
      SimplicialComplex simp_admissibles(universe);
      for (Circuits::const_iterator iter = circuits.begin();
	   iter != circuits.end();
	   ++iter) {
	const Circuit& circuit(iter->second);
	if (circuit.first.subset(simp)) {
	  vertexfacets.remove_star(circuit.second, simp_admissibles);
	}
	if (circuit.second.subset(simp)) {
	  vertexfacets.remove_star(circuit.first, simp_admissibles);
	}
      }
      simp_admissibles -= simp;
      SimplicialComplex simp_intersecting_admissibles;
      if (CommandlineOptions::check_flag()) {
	for (SimplicialComplex::const_iterator admiter = simp_admissibles.begin();
	     admiter != simp_admissibles.end();
	     ++admiter) {
	  const Simplex& adm_simp(*admiter);
	  if (adm_simp.intersection_nonempty(simp) && simp.lexsmaller(adm_simp)) {
	    simp_intersecting_admissibles += adm_simp;
	  }
	}
      }
      _output[simp] = std::pair<SimplicialComplex, SimplicialComplex>(simp_admissibles, simp_intersecting_admissibles);
      if (++count % CommandlineOptions::report_frequency() == 0) {
	MessageStreams::verbose() << message::lock
				  << message::tab << "thread " << std::setw(2) << _ID << ": "
				  << count << " simplices processed so far" << std::endl
				  << message::unlock;
      }
    }
    MessageStreams::verbose() << message::lock
			      << message::tab << "... thread " << std::setw(2) << _ID
			      << " done." << std::endl
			      << message::unlock;
  }
  
  Admissibles::Admissibles(const Circuits& circuits,
			   const PointConfiguration* pointsptr,
			   const Chirotope& chiro,
			   const SymmetryGroup& symmetries,
			   const SymmetryGroup& required_symmetries,
			   const bool only_empty_simplices) : 
    admissibles_data(),
    _valid_simplices() {

    // allow for switching on debug locally:
    bool local_debug = true;
    if (CommandlineOptions::check_unimodular() && !pointsptr) {
      MessageStreams::forced() << message::lock
			       << "Admissibles::Admissibles("
			       << "const Circuits&, "
			       << "const PointConfiguration*, "
			       << "const Chirotope&, "
			       << "const SymmetryGroup&, "
			       << "const SymmetryGroup&, "
			       << "const bool):\n"
			       << " unimodularity only possible with points as input - exiting." << std::endl
			       << message::unlock;
      exit(1);
    }
    const parameter_type no(circuits.no());
    const parameter_type rank(circuits.rank());
    Field min_vol;
    bool have_first_vol = false;
    Permutation perm(no, rank);
    if (CommandlineOptions::check_unimodular()) {

      // if unimodularity is requested, then determine the minimal positive volume of a simplex first:
      do {
	Simplex simp(perm);
	if (chiro(simp) != FieldConstants::ZERO) {
	  Field simp_vol(pointsptr->volume(simp));
	  if (!have_first_vol) {
	    min_vol = simp_vol;
	    have_first_vol = true;
	  }
	  else {
	    if (simp_vol < min_vol) {
	      min_vol = simp_vol;
	    }
	  }
	}
      } while (perm.lexnext());
      MessageStreams::verbose() << message::lock
				<< "unimodularity check done with simplex volume " << min_vol << std::endl
				<< message::unlock;
    }

    // next, check conditions for all simplices to determine all valid simplices:
    perm = Permutation(no, rank);
    SimplicialComplex known_invalid_simplices; // some computations yield violations for other simplices as well
    MessageStreams::verbose() << message::lock
			      << global::binomial(no, rank) << " "
			      << rank << "-subsets of {0,...," << no - 1 << "} to be checked for validity ..." << std::endl
			      << message::unlock;
    size_type count(0);
    do {
      
      if ((count > 0) && (count % CommandlineOptions::report_frequency() == 0)) {
	MessageStreams::verbose() << message::lock
				  << count << " "
				  << rank << "-subsets of {0,...," << no - 1 << "} checked for validity so far" << std::endl
				  << message::unlock;
      }
      ++count;
      
      // simplex to be checked for validity:
      Simplex simp(perm);

      // check whether it is in the set of known invalid simplices:
      if (known_invalid_simplices.contains(simp)) {
	continue;
      }

      // check independence:
      if (chiro(simp) == FieldConstants::ZERO) {
	MessageStreams::debug() << message::lock
				<< "simplex " << simp << " not independent - ignored" << std::endl
				<< message::unlock;
	continue;
      }

      // check unimodularity:
      if (CommandlineOptions::check_unimodular() && (pointsptr->volume(simp) != min_vol)) {
	MessageStreams::debug() << message::lock
				<< "simplex " << simp << " is not unimodular: volume(simp) = " << pointsptr->volume(simp) << " - ignored" << std::endl
				<< message::unlock;
	continue;
      }

      // check required points:
      if (CommandlineOptions::require_point() && !simp.contains(CommandlineOptions::required_point())) {
	MessageStreams::debug() << message::lock
				<< "simplex " << simp << " does not contain point " << CommandlineOptions::required_point() << " - ignored" << std::endl
				<< message::unlock;
	continue;	
      }

      // check for contained non-vertices (if not already checked for minimal volume):
      if (!CommandlineOptions::check_unimodular() && only_empty_simplices) {
	bool is_empty(true);

	// checking all points for containment is faster for large problems:
	for (parameter_type idx = 0;
	     idx < no;
	     ++idx) {
	  if (!simp.contains(idx)) {
	    const Circuit circuit(chiro, simp + idx);
	    if ( (simp.superset(circuit.first) && (circuit.second.card() == 1))
		 || (simp.superset(circuit.second) && (circuit.first.card() == 1)) ) {
 	      MessageStreams::debug() << message::lock
				      << "simplex " << simp << " contains non-vertex "
				      << idx << " with circuit certificate "
				      << circuit << " - ignored" << std::endl
				      << message::unlock;
	      is_empty = false;
	      break;
	    }
	  }
	}
	if (!is_empty) {
	  continue;
	}
      }
      _valid_simplices += simp;
    } while (perm.lexnext());
    MessageStreams::verbose() << message::lock
			      << "... "
			      << _valid_simplices.card() << " valid simplices" << std::endl
			      << message::unlock;
    VertexFacetTable vertexfacets(no, _valid_simplices);

    // finally, check for pairwise symmetry-proper intersection:
    if (CommandlineOptions::parallel_enumeration()) {
      int local_no_of_threads = std::min<int>(CommandlineOptions::no_of_threads(), _valid_simplices.card());
      std::vector<std::thread> local_threads(local_no_of_threads);
      std::vector<SimplicialComplex> thread_simplices(local_no_of_threads, SimplicialComplex());
      std::vector<admissibles_data> thread_results(local_no_of_threads, admissibles_data());
      std::vector<Worker> thread_workers;
    
      int thread_no = 0;

      // distribute simplices from the valid simplices:
      for (SimplicialComplex::const_iterator iter = _valid_simplices.begin();
	   iter != _valid_simplices.end();
	   ++iter) {
      
	// round-robin distribution:
	thread_simplices[thread_no] += *iter;
	thread_no = (thread_no + 1) % local_no_of_threads;
      }

      // generate workers:
      for (int i = 0; i < local_no_of_threads; ++i) {
	thread_workers.emplace_back(i, thread_simplices[i]);
      }

      // generate threads:
      for (int i = 0; i < local_no_of_threads; ++i) {
	local_threads[i] = std::thread(&Admissibles::Worker::operator(),
				       std::ref(thread_workers[i]),
				       std::ref(_valid_simplices),
				       std::ref(vertexfacets),
				       std::ref(circuits));
      }
    
      // wait for threads to finish:
      for (int i = 0; i < local_no_of_threads; ++i) {
	if (local_threads[i].joinable()) {
	  local_threads[i].join();
	}
      }

      // collect the results (not in the same order as the input!):
      for (int i = 0; i < local_no_of_threads; ++i) {
	(*this).insert(thread_workers[i].output().begin(), thread_workers[i].output().end());
	thread_workers[i].clear_output();
      }
    }
    else {
      MessageStreams::verbose() << message::lock
				<< message::tab << "main thread: generating admissibles ..."
				<< std::endl
				<< message::unlock;
      size_type count(0);
      for (SimplicialComplex::iterator sciter = _valid_simplices.begin();
	   sciter != _valid_simplices.end();
	   ++sciter) {
	const Simplex simp(*sciter);
	SimplicialComplex simp_admissibles(_valid_simplices);
	for (Circuits::const_iterator iter = circuits.begin();
	     iter != circuits.end();
	     ++iter) {
	  const Circuit& circuit(iter->second);
	  if (circuit.first.subset(simp)) {
	    vertexfacets.remove_star(circuit.second, simp_admissibles);
	  }
	  if (circuit.second.subset(simp)) {
	    vertexfacets.remove_star(circuit.first, simp_admissibles);
	  }
	}
	simp_admissibles -= simp;
	SimplicialComplex simp_intersecting_admissibles;
	if (CommandlineOptions::check_flag()) {
	  for (SimplicialComplex::const_iterator admiter = simp_admissibles.begin();
	       admiter != simp_admissibles.end();
	       ++admiter) {
	    const Simplex& adm_simp(*admiter);
	    if (adm_simp.intersection_nonempty(simp) && simp.lexsmaller(adm_simp)) {
	      simp_intersecting_admissibles += adm_simp;
	    }
	  }
	}
	(*this)[simp] = std::pair<SimplicialComplex, SimplicialComplex>(simp_admissibles, simp_intersecting_admissibles);
	if (++count % CommandlineOptions::report_frequency() == 0) {
	  MessageStreams::verbose() << message::lock
				    << count << " simplices processed so far." << std::endl
				    << message::unlock;
	}
      }
      MessageStreams::verbose() << message::lock
				<< message::tab << "... main thread done." << std::endl
				<< message::unlock;
    }
    if (CommandlineOptions::observe_required_symmetries()) {
      MessageStreams::verbose() << message::lock
				<< "... "
				<< this->valid_simplices().card() << " valid simplices before processing required symmetries ..." << std::endl
				<< message::unlock;
      require_symmetries(chiro, required_symmetries);
      MessageStreams::verbose() << message::lock
				<< "... "
				<< this->valid_simplices().card() << " valid simplices after processing required symmetries" << std::endl
				<< message::unlock;
    }
  }

  // functions:
  void Admissibles::require_symmetries(const Chirotope& chiro, const SymmetryGroup& required_symmetries) {

    // if symmetries are required for a triangulation,
    // then simplices can only coexist in a triangulation
    // if their orbits intersect properly for all required symmetries:
    Admissibles::iterator admiter = this->begin();
    while (admiter != this->end()) {
      const Simplex simp = admiter->first;
      SimplicialComplex& simp_admissibles = admiter->second.first;
      SimplicialComplex& simp_intersecting_admissibles = admiter->second.second;
      simp_admissibles += simp;
      bool simp_feasible(true);
      SimplicialComplex simp_orbit;
      for (SymmetryGroup::const_iterator symiter = required_symmetries.begin();
    	   symiter != required_symmetries.end();
    	   ++symiter) {
    	const Simplex simpimg(symiter->map(simp));
    	simp_orbit += simpimg;
    	if (simp == simpimg) {
    	  continue;
    	}
    	if (!simp_admissibles.contains(simpimg)) {

    	  // the required symmetry maps simp to non-properly intersecting image:
    	  simp_feasible = false;
    	  break;
    	}
    	else {

    	  // admissible simplices for simp must intersect properly with or be identical to simpimg:
    	  simp_admissibles *= ((*this)[simpimg].first + simpimg);
    	  simp_intersecting_admissibles *= simp_admissibles;
    	}
      }
      if (simp_feasible) {
	simp_admissibles -= simp;
	simp_intersecting_admissibles -= simp;
	++admiter;
      }
      else {
	_valid_simplices -= simp;
	admiter = this->erase(admiter);
	// ++admiter;
      }
    }
  }
  
  // stream input/output:
  std::istream& Admissibles::read(std::istream& ist) {
    ist >> static_cast<admissibles_data&>(*this);
    return ist;
  }
  
  std::ostream& Admissibles::write(std::ostream& ost) const {
    ost << static_cast<const admissibles_data&>(*this);

    //    size_type count = 0;

    //     ost << ContainerChars::list_start_char;
    //     for (typename Admissibles::const_iterator iter = this->begin();
    // 	 iter != this->end();
    // 	 ++iter) {
    //       ost << iter->first << ContainerChars::map_chars << iter->second;
    //       count++;
    
    //       if (count < size()) {
    // 	ost << ContainerChars::delim_char;
    //       }
    //     }
    //     ost << ContainerChars::delim_char;
    //     ost << _valid_simplices;
    //     ost << ContainerChars::list_end_char;
    return ost;
  }

  bool Admissibles::_have_proper_intersection(const Simplex& simp1, const Simplex& simp2, const Circuits& circuits) const {
    if (simp1 == simp2) {
      return true;
    }
    for (Circuits::const_iterator circiter = circuits.begin();
	 circiter != circuits.end();
	 ++circiter) {
      if ((circiter->second.first.subset(simp1) && circiter->second.second.subset(simp2))
	  || (circiter->second.first.subset(simp2) && circiter->second.second.subset(simp1))) {
	MessageStreams::verbose() << message::lock
				  << "improper intersection of "
				  << simp1
				  << " and "
				  << simp2
				  << " detected by circuit "
				  << *circiter
				  << std::endl
				  << message::unlock;
	return false;
      }
    }
    return true;
  }

}; // namespace topcom

// eof Admissibles.cc
