////////////////////////////////////////////////////////////////////////////////
// 
// ComputeTriangs.cc
//
//    produced: 25 Nov 1999 jr
// 
////////////////////////////////////////////////////////////////////////////////

#include "Signal.hh"
#include "CommandlineOptions.hh"

#include "Permutation.hh"
#include "Volumes.hh"
#include "Circuits.hh"
#include "Facets.hh"
#include "Admissibles.hh"
#include "InteriorFacets.hh"
#include "PartialTriang.hh"
#include "CheckTriang.hh"
#include "Flip.hh"
#include "MarkedFlips.hh"
#include "TriangNode.hh"
#include "TriangFlips.hh"
#include "FineTriang.hh"
#include "SymmetricFlipGraph.hh"
#include "SymmetricExtensionGraphMaster.hh"

#include "ComputeTriangs.hh"

namespace topcom {
  
  Message& ComputeTriangs::write_header(Message& msg) const {
    msg << std::endl;
    msg << "------------------------------------------------------------------\n";
    msg << " computing triangulations of a point configuration up to symmetry \n";
    msg << " TOPCOM client: " << CommandlineOptions::client() << '\n';
    msg << "------------------------------------------------------------------\n";
    msg << std::endl;
    return msg;
  }

  std::istream& ComputeTriangs::read_input(std::istream& ist) {
    if (input_chiro) {
      _chiroptr = new Chirotope();
      if (CommandlineOptions::output_asy()) {
	MessageStreams::forced() << "graphics output cancelled because of chirotope input." << std::endl;
      }
      if (!_chiroptr->read_string(ist)) {
	MessageStreams::forced() << "error while reading chirotope - exiting" << std::endl;
	exit(1);
      }
      MessageStreams::verbose() << "read chirotope with " << _chiroptr->no()
				<< " elements in rank " << _chiroptr->rank() << std::endl;
    }
    else {
      _pointsptr = new PointConfiguration();
      if (!_pointsptr->read(ist)) {
	MessageStreams::forced() << "error while reading point configuration - exiting" << std::endl;
	exit(1);
      }
      if (_pointsptr->rank() < _pointsptr->rowdim()) {
	MessageStreams::forced() << "point configuration has " << _pointsptr->rowdim() << " rows of rank " << _pointsptr->rank() << std::endl;
	_pointsptr->transform_to_full_rank();
	MessageStreams::forced() << "resulting no of rows after transformation: " << _pointsptr->rank() << std::endl;
	_pointsptr->pretty_print(MessageStreams::forced());
      }
      if ((_pointsptr->no() < 1) || (_pointsptr->rank() < 1)) {
	std::cerr << "no of points and rank must be at least one - exiting" << std::endl;
	exit(1);
      }
      if (_pointsptr->rank() > _pointsptr->no()) {
	std::cerr << "rank must not be larger than no of points - exiting" << std::endl;
	exit(1);
      }
      MessageStreams::verbose()  << "read point configuration with " << _pointsptr->no()
				 << " points in rank " << _pointsptr->rank() << std::endl;
      _chiroptr = new Chirotope(*_pointsptr, preprocess);
    }
    _no = _chiroptr->no();
    _rank = _chiroptr->rank();
    MessageStreams::verbose() << "checking for symmetries ..." << std::endl;
    _symmetriesptr = new SymmetryGroup(_no); // all configurations have a symmetry group
    if (!CommandlineOptions::ignore_symmetries()) {
      if (CommandlineOptions::use_switch_tables()
	  && !CommandlineOptions::require_point()
	  && (CommandlineOptions::required_point() < _no)
	  && (CommandlineOptions::required_point() >= 0)) {
	if (_symmetriesptr->read_generators(ist)) {
	  std::cerr << "read " << _symmetriesptr->generators().size()
		    << " generators" << std::endl;
	  if (CommandlineOptions::debug()) {
	    std::cerr << "_symmetriesptr->generators():" << std::endl;
	    std::cerr << _symmetriesptr->generators() << std::endl;
	  }
	}
      }
      else {
	if (_symmetriesptr->read(ist)) {
	  MessageStreams::verbose() << "read symmetry group with " << _symmetriesptr->generators().size()
				    << " generators of order " << _symmetriesptr->size() + 1 << std::endl;
	  MessageStreams::debug() << "symmetries:" << '\n'
				  << *_symmetriesptr << std::endl;
	  if (CommandlineOptions::require_point()
	      && (CommandlineOptions::required_point() < _no)
	      && (CommandlineOptions::required_point() >= 0)) {
	    
	    // if an existing point is required to be in any simplex, only point-stabilizing symmetries are compatible
	    // because otherwise we need that lex-minimal triangulations requiring a point need to remain
	    // lex-minimal in their orbits:
	    MessageStreams::verbose() << "reducing symmetry group to stabilizer of " << CommandlineOptions::required_point() << " ..." << std::endl;
	    SymmetryGroup compatible_symmetries(_symmetriesptr->stabilizer(IntegerSet(CommandlineOptions::required_point())));
	    *_symmetriesptr = compatible_symmetries;
	    MessageStreams::verbose() << "... order of compatible symmetries: " << _symmetriesptr->size() + 1 << std::endl;
	  }
	}
      }
    }
    else {
      
      // read symmetries anyway into dummy structure to be able to read seed later:
      SymmetryGroup dummy_symmetries(_no);
      dummy_symmetries.read(ist);
      MessageStreams::verbose() << "symmetry generators ignored." << std::endl;
    }
    MessageStreams::verbose() << "... done." << std::endl;
    MessageStreams::verbose() << "checking for seed triangulation ..." << std::endl;
    SimplicialComplex seed;
    if (!seed.read(ist) || seed.empty()) { // empty seed encodes no seed
      MessageStreams::verbose() << "... no non-empty seed given ..." << std::endl;
    }
    else {
      MessageStreams::verbose() << "... found seed " << seed << std::endl;
      _seedptr = new SimplicialComplex(seed); // only here, a seed is given in the input
    }
    MessageStreams::verbose() << "... done." << std::endl;
    MessageStreams::verbose() << "checking for required symmetries for triangulations ..." << std::endl;
    _required_symmetriesptr = new SymmetryGroup(_no); // required symmetries always exist (trivial group for non-symmetric configurations)
    if (CommandlineOptions::observe_required_symmetries()) {
      if (_required_symmetriesptr->read(ist)) {
	MessageStreams::verbose() << "read " << _required_symmetriesptr->generators().size() << " required symmetries beyond the identity" << std::endl;
      }
      else {
	MessageStreams::verbose() << "no valid required symmetry generators found." << std::endl;
      }
    }
    else {
      MessageStreams::verbose() << "required symmetry generators ignored." << std::endl;
    }
    MessageStreams::verbose() << "... done." << std::endl;
    return ist;
  }
  
  const std::pair<size_type, SimplicialComplex> ComputeTriangs::findmin_by_extension(const parameter_type      no,
										     const parameter_type      rank,
										     const PointConfiguration* pointsptr,
										     const Chirotope*          chiroptr,
										     const SymmetryGroup*      symmetriesptr,
										     const SymmetryGroup*      required_symmetriesptr,
										     const bool                output_triangs,
										     const bool                only_fine_triangs) {
    SymmetricExtensionGraphMaster segm(no,
				       rank,
				       pointsptr,
				       chiroptr,
				       symmetriesptr,
				       required_symmetriesptr,
				       output_triangs,
				       only_fine_triangs,
				       true);
    MessageStreams::verbose() << segm.nodecount() << " partial triangulations visited in total." << std::endl;
    MessageStreams::verbose() << segm.deadendcount() << " branching deadends." << std::endl;
    MessageStreams::verbose() << segm.earlydeadendcount() << " early detected deadends." << std::endl;
    MessageStreams::verbose() << segm.mintriang() << " is a minimal triangulation." <<  std::endl;
    MessageStreams::verbose() << segm.mincard() << " simplices in a minimal triangulation." << std::endl;
    return std::pair<size_type, SimplicialComplex>(segm.mincard(), segm.mintriang());
  }

  const size_type ComputeTriangs::enumerate_by_extension(const parameter_type      no,
							 const parameter_type      rank,
							 const PointConfiguration* pointsptr,
							 const Chirotope*          chiroptr,
							 const SymmetryGroup*      symmetriesptr,
							 const SymmetryGroup*      required_symmetriesptr,
							 const bool                output_triangs,
							 const bool                only_fine_triangs) {
    SymmetricExtensionGraphMaster segm(no,
				       rank,
				       pointsptr,
				       chiroptr,
				       symmetriesptr,
				       required_symmetriesptr,
				       output_triangs,
				       only_fine_triangs,
				       false);
    if (Signal::signal_received()) {
      MessageStreams::verbose().print_dumpseparator();
      MessageStreams::verbose() << "### intermediate results at checkpoint forced by signal:" << std::endl;
      MessageStreams::verbose().print_dumpseparator();
    }
    MessageStreams::verbose() << segm.nodecount() << " partial triangulations visited in total." << std::endl;
    MessageStreams::verbose() << segm.deadendcount() << " branching deadends." << std::endl;
    MessageStreams::verbose() << segm.earlydeadendcount() << " early detected deadends." << std::endl;
    MessageStreams::verbose() << segm.maxiter_coversimptighten() << " max iterations in coversimp tightening." << std::endl;
    MessageStreams::verbose() << segm.symcount() << " symmetry classes";
    if (!CommandlineOptions::skip_orbitcount()) {
      MessageStreams::verbose() << " --- " << segm.totalcount() << " triangulations in total";
    }
    MessageStreams::verbose() << "." << std::endl;
    return segm.symcount();
  }

  const size_type ComputeTriangs::enumerate_by_flips(const parameter_type        no,
						     const parameter_type        rank,
						     const PointConfiguration*   pointsptr,
						     const Chirotope*            chiroptr,
						     const SymmetryGroup*        symmetriesptr,
						     const SymmetryGroup*        required_symmetriesptr,
						     const SimplicialComplex*    seedptr,
						     const symmetryptr_datapair* seed_symmetryptrs,
						     const Volumes*              voltableptr,
						     const bool                  output_triangs,
						     const bool                  fine_only) {
    SymmetricFlipGraph sfg(no,
			   rank,
			   pointsptr,
			   chiroptr, 
			   symmetriesptr,
			   required_symmetriesptr,
			   seedptr,
			   seed_symmetryptrs,
			   voltableptr,
			   output_triangs,
			   fine_only);
    MessageStreams::verbose() << sfg.symcount() << " symmetry classes";
    if (!CommandlineOptions::skip_orbitcount()) {
      MessageStreams::verbose() << " --- " << sfg.totalcount() << " triangulations in total";
    }
    MessageStreams::verbose() << "." << std::endl;
    return sfg.symcount();
  }

  int ComputeTriangs::run() {

    // // write TOPCOM header with version information:
    // global::write_topcom_header(std::cerr);

    // write header for computations concerning triangulations:
    write_header(MessageStreams::forced());

    // terminate if non-consistent options have been chosen:
    if (input_chiro && CommandlineOptions::check_regular()) {
      MessageStreams::forced() << "regularity check not possible if only chirotope is given - exiting." << std::endl;
      exit(1);
    }
    if (input_chiro && CommandlineOptions::use_volumes()) {
      MessageStreams::forced() << "volumes not exploitable if only chirotope is given - exiting." << std::endl;
      exit(1);
    }

    // read input from stdin or file (depending on commandline options):
    read_input(std::cin);
    
    // compute according to flags:
    if (compute_all) {
      if (findmin) {
	const std::pair<size_type, SimplicialComplex> mintriang_pair = findmin_by_extension(_no,
											    _rank,
											    _pointsptr,
											    _chiroptr,
											    _symmetriesptr,
											    _required_symmetriesptr,
											    output_triangs,
											    fine_only);
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "... done." << std::endl;
	}
	if (mintriang_pair.second.empty()) {
	  MessageStreams::result() << "[-, -]" << std::endl;
	}
	else {
	  MessageStreams::result() << mintriang_pair << std::endl;
	}
	return Signal::exit_value();
      }
      else {
	const size_type N = enumerate_by_extension(_no,
						   _rank,
						   _pointsptr,
						   _chiroptr,
						   _symmetriesptr,
						   _required_symmetriesptr,
						   output_triangs,
						   fine_only);
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << N << " symmetry classes of triangulations in total." << std::endl;
	  MessageStreams::verbose() << "... done." << std::endl;
	}
	if (!output_triangs) {
	  MessageStreams::result() << N << std::endl;
	}
	return Signal::exit_value();
      }
    }
    else {
      if (CommandlineOptions::use_gkz()
	  && CommandlineOptions::symmetries_are_isometric()
	  && _pointsptr) {
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "computing volumes table ..." << std::endl;
	}
	_voltableptr = new Volumes(*_pointsptr);
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "... done." << std::endl;
	}
      }
      
      if (CommandlineOptions::memopt()) {
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "no preprocessing of simplex table to save memory" << std::endl;
	}
      }
      else {
	const parameter_type pprank = std::min<parameter_type>(_rank + 1, _no);
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "preprocessing simplex table for all simplices up to rank "
				    << pprank << " ..." << std::endl;
	}
	if (_voltableptr) {
	  SimplicialComplex::preprocess_index_table(_no, 0, pprank, *_voltableptr, true);
	}
	else {
	  SimplicialComplex::preprocess_index_table(_no, 0, pprank, *_chiroptr, true);
	}
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "... done: "
				    << SimplicialComplex::no_of_simplices(_rank)
				    << " full-dimensional simplices in rank " << _rank << std::endl;
	}
      }
      
      // for a flipping algorithm we need a seed anyway:
      if (!_seedptr) {
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "no valid seed triangulation found" << std::endl;
	  MessageStreams::verbose() << "computing seed triangulation via placing and pushing ..." 
				    << std::endl;
	}
	if (fine_only) {
	  _seedptr = new FineTriang(*_chiroptr);
	}
	else {
	  _seedptr = new PlacingTriang(*_chiroptr);
	}
      }
      if (CommandlineOptions::verbose()) {
	MessageStreams::verbose() << "seed: " << *_seedptr << std::endl;
	MessageStreams::verbose() << "containing " << _seedptr->card() << " simplices" << std::endl;
	MessageStreams::verbose() << "using the following " << _seedptr->support().card() << " vertices: " 
				  << _seedptr->support() << std::endl;
	MessageStreams::verbose() << "... done." << std::endl;
      }
      
      if (CommandlineOptions::verbose()) {
	MessageStreams::verbose() << "computing symmetries of seed ..." << std::endl;
      }
      if (!_symmetriesptr->complete()) {
	_symmetriesptr->closure();
      }
      _seed_symmetryptrsptr = new symmetryptr_datapair(_symmetriesptr->stabilizer_ptrs(*_seedptr));
      if (CommandlineOptions::verbose()) {
	MessageStreams::verbose() << "... done." << std::endl;
	MessageStreams::verbose() << _seed_symmetryptrsptr->first.size() << " symmetries in total in seed." << std::endl;
	if (CommandlineOptions::debug()) {
	  MessageStreams::debug() << "symmetries:" << '\n'
				  << *_symmetriesptr << std::endl;
	}
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "... done." << std::endl;;
	}
      }
      if (CommandlineOptions::check()) {
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "checking seed triangulation ..." << std::endl;
	}
	if (!(CheckTriang(*_seedptr, *_seed_symmetryptrsptr, *_chiroptr, *_symmetriesptr, *_required_symmetriesptr, fine_only))()) {
	  if (CommandlineOptions::verbose()) {
	    MessageStreams::verbose() << "seed triangulation " << std::endl
				      << *_seedptr << std::endl
				      << "not valid." << std::endl;
	  }
	  return 1;
	}
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "... done." << std::endl;;
	}
      }
      
      if (findflips) {
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << "count all flips of seed ..." << std::endl;
	}
	const TriangNode tn(0, _no, _rank, *_seedptr);
	const TriangFlips tf(*_chiroptr, nullptr, tn, *_seed_symmetryptrsptr, fine_only);
	if (CommandlineOptions::verbose()) {
	  MessageStreams::verbose() << tf.flips().size() << " flips in total." << std::endl;
	  MessageStreams::verbose() << "... done." << std::endl;
	}
	if (output_triangs) {
	  MessageStreams::result() << tf << std::endl;
	}
	else {
	  MessageStreams::result() << tf.flips().size() << std::endl;
	}
	return Signal::exit_value();
      }
      if (CommandlineOptions::verbose()) {
	MessageStreams::verbose() << "exploring all seed-connected symmetry classes of triangulations by flipping ..." << std::endl;
      }
      const size_type N = enumerate_by_flips(_no,
					     _rank,
					     _pointsptr,
					     _chiroptr, 
					     _symmetriesptr,
					     _required_symmetriesptr,
					     _seedptr,
					     _seed_symmetryptrsptr,
					     _voltableptr,
					     output_triangs,
					     fine_only);
      if (CommandlineOptions::verbose()) {
	MessageStreams::verbose() << N << " symmetry classes of triangulations in total." << std::endl;
	MessageStreams::verbose() << "... done." << std::endl;
      }
      if (!output_triangs) {
	MessageStreams::result() << N << std::endl;
      }
      return Signal::exit_value();
    }
  }

}; // namespace topcom

// eof ComputeTriangs.cc
