//////////////////////////////////////////////////////////////////////////
//
// ComputeSubsets.hh (unifies ComputeCircuits and ComputeCocircuits)
//
//    produced: 2024/02/29 jr
//
/////////////////////////////////////////////////////////////////////////
#ifndef COMPUTESUBSETS_HH
#define COMPUTESUBSETS_HH

#include <iostream>


#include "Global.hh"
#include "Message.hh"
#include "CommandlineOptions.hh"

#include "Options.hh"
#include "ConfData.hh"

#include "LabelSet.hh"
#include "PointConfiguration.hh"
#include "Chirotope.hh"
#include "Symmetry.hh"
#include "Circuits.hh"
#include "Cocircuits.hh"
#include "Permutation.hh"
#include "Symmetry.hh"
#include "SymmetricSubsetGraphMaster.hh"
#include "Signal.hh"

#ifdef TOPCOM_CONTAINERS
#include "PlainHashSet.hh"
#include "HashSet.hh"
namespace topcom {
  typedef PlainHashSet<subset_type> subset_container_type;
};
#else
#include <unordered_set>
namespace topcom {
  typedef std::unordered_set<subset_type, Hash<subset_type> > subset_container_type;
};
#endif

namespace topcom {

  // we want to enumerate objects of type T;
  // the class T has a constructor from subsets with
  // cardinality card_offset compared to rank and a
  // function returning a signature subset for T that
  // uniquely determines an object of type T;
  // moreover, bool T.is_valid() and LabelSet T.support() are defined;
  // alternatively, there is a mode for
  // SymmetricSubsetGraphMaster for T
  // (currently used for circuits and cocircuits enumeration):
  template <class T, parameter_type card_offset>
  class ComputeSubsets {
  private:
    ConfData _confdata;
    Options  _options;
  public:
    inline ComputeSubsets() :
      _confdata(Options(Options::NO_SIMPLICIAL_COMPLEX)), // for larger instances, we need to avoid simplicial complex preprocessings
      _options(Options::NO_SIMPLICIAL_COMPLEX) {} // dto.
    inline ComputeSubsets(int flags) :
      _confdata(Options(flags | Options::NO_SIMPLICIAL_COMPLEX)), // dto.
      _options(flags | Options::NO_SIMPLICIAL_COMPLEX) {} // dto.
    inline ~ComputeSubsets() {}

    Message& write_header(Message&) const;
    std::istream& read_input(std::istream&);
    void preprocess_data();

    int run_from_chiro();
    int run_from_points();

    int  run();
  };

  template <class T, parameter_type card_offset>
  Message& ComputeSubsets<T, card_offset>::write_header(Message& msg) const {
    msg << std::endl;
    msg << "------------------------------------------------------------------\n";
    msg << " computing "
	<< T::type_name()
	<< "s of a point configuration up to symmetry \n";
    msg << " TOPCOM client: " << CommandlineOptions::client() << '\n';
    msg << "------------------------------------------------------------------\n";
    msg << std::endl;
    return msg;
  }
  
  template <class T, parameter_type card_offset>
  std::istream& ComputeSubsets<T, card_offset>::read_input(std::istream& ist) {
    _confdata.read_input(ist);
    return ist;
  }

  template <class T, parameter_type card_offset>
  void ComputeSubsets<T, card_offset>::preprocess_data() {
    _confdata.make_chiro();
    if (CommandlineOptions::use_switch_tables()) {
      _confdata.make_subset_switchtable();
    }
    if (CommandlineOptions::use_classified_symmetries()) {
      _confdata.make_classified_subset_symmetries();
    }
    if (CommandlineOptions::output_asy() && (_confdata.rank() == 3)) {
      _confdata.make_vertices();
    }
  }

  template <class T, parameter_type card_offset>
  int ComputeSubsets<T, card_offset>::run_from_chiro() {
      
    // counters:
    size_type iterations(0);
    size_type count(0);
    size_type total_count(0);
    size_type node_count(0);

    // effort estimation:
    size_type no_of_subsets(global::binomial(_confdata.no(), _confdata.rank() + card_offset));
    
    // use either as full storage or cache of already processed sets:
    const bool activate_cache(!CommandlineOptions::memopt() || (CommandlineOptions::localcache() > 0));
    subset_container_type support_sets;
    subset_container_type done_sets;
    if (CommandlineOptions::memopt()) {
      if (activate_cache) {
	done_sets = subset_container_type(std::min<size_type>(CommandlineOptions::localcache(), no_of_subsets));
      }
    }
    else {
      done_sets = subset_container_type(no_of_subsets);
    }
    
    if (_options.output_subsets()) {
      MessageStreams::result() << _confdata.no() << ',' << _confdata.rank() << ":\n{\n";
    }
    
    // use for storage of canonical representatives:
    subset_container_type canonical_support_sets;
    subset_container_type canonical_subsets;
    subset_container_type orbit;
    Permutation subsetperm(_confdata.no(), _confdata.rank() + card_offset);
    
    do {
      if (++iterations % CommandlineOptions::report_frequency() == 0) {
	MessageStreams::verbose() << count << " symmetry classes --- "
				  << total_count << " total subset_objects ("
				  << iterations << " out of "
				  << no_of_subsets << " " << _confdata.rank() + 1 << "-sets investigated [cache load: "
				  << done_sets.size() << ", cache size: "
				  << done_sets.bucket_count() << "])"
				  << std::endl;
      }
      if (done_sets.size() == no_of_subsets) {
	break;
      }
      subset_type subset(subsetperm);
      MessageStreams::debug() << "investigating " << subset << " ..." << std::endl;
      if ((activate_cache) && (done_sets.find(subset) != done_sets.end())) {
	
	// subset already considered and stored/cached:
	continue;
      }
      bool old_subset = false;
      if (CommandlineOptions::use_switch_tables()) {
	if (_confdata.subset_switchtable().not_canonical(subset)) {
	  old_subset = true;
	}
      }
      else {
	for (SymmetryGroup::const_iterator iter = _confdata.symmetries().begin();
	     iter != _confdata.symmetries().end();
	     ++iter) {
	  const Symmetry& g(*iter);
	  
	  // if there is a symmetry mapping this dependent set to something lexicographically smaller,
	  // then we have had an equivalent dependent set already:
	  if (g.lex_decreases(subset)) {
	    MessageStreams::debug() << g.map(subset) << " has been investigated already." << std::endl;
	    old_subset = true;
	    break;
	  }	  
	}
      }
      if (old_subset) {
	
	// nothing new:
	continue;
      }
      
      // store/cache the dependent set as done:
      if (activate_cache) {
	if (CommandlineOptions::memopt()) {
	  if (CommandlineOptions::localcache() > 0) {
	    if (CommandlineOptions::localcache() > done_sets.size()) {
	      done_sets.insert(subset);
	    }
	  }
	}
	else {
	  done_sets.insert(subset);
	}
      }
      
      // store/cache all equivalent dependent sets:
      for (SymmetryGroup::const_iterator iter = _confdata.symmetries().begin();
	   iter != _confdata.symmetries().end();
	   ++iter) {

	// canonicalize the support set:
	const Symmetry& g(*iter);
	const subset_type equivalent_subset(g.map(subset));
	if (activate_cache) {
	  if (CommandlineOptions::memopt()) {
	    if (CommandlineOptions::localcache() > 0) {
	      if (CommandlineOptions::localcache() > done_sets.size()) {
		done_sets.insert(equivalent_subset);
	      }
	    }
	  }
	  else {
	    done_sets.insert(equivalent_subset);
	  }
	}
      }
      T subset_object(_confdata.chiro(), subset);
      if (subset_object.is_valid()) {
	MessageStreams::debug() << "computing subset_object support for " << subset << " ..." << std::endl;
	subset_type support_set(subset_object.support());
	bool old_support_set = false;

	// the first subset found is by definition the canonical subset:
	subset_type canonical_support_set = support_set;
	if (support_sets.find(support_set) != support_sets.end()) {
	  
	  // the subset_object support is in the storage/cache:
	  old_support_set = true;
	}
	else if (canonical_support_sets.find(support_set) != canonical_support_sets.end()) {
	  
	  // the subset_object support was found already:
	  old_support_set = true;
	}
	else {
	  for (SymmetryGroup::const_iterator iter = _confdata.symmetries().begin();
	       iter != _confdata.symmetries().end();
	       ++iter) {
	    const Symmetry& g(*iter);
	    const subset_type equivalent_support_set(g.map(support_set));
	    if (support_sets.find(equivalent_support_set) != support_sets.end()) {

	      // a symmetric image of the subset_object support is in the storage
	      old_support_set = true;
	      break;
	    }
	    else if (canonical_support_sets.find(equivalent_support_set) != canonical_support_sets.end()) {

	      // a symmetric image of the subset_object support was found already
	      old_support_set = true;
	      break;
	    }
	  }
	}
	if (!old_support_set) {
	  if (_options.output_subsets()) {
	    if (CommandlineOptions::debug()) {
	      MessageStreams::result() << "C[" << count << "] := " << subset_object << ";" << std::endl;
	    }
	    else {
	      MessageStreams::result() << subset_object << std::endl;
	    }
	  }
	  
	  // add the canonicalized support set to the set of already found canonical support sets:
	  canonical_support_sets.insert(canonical_support_set);

	  // insert into support sets:
	  if (activate_cache) {
	    if (CommandlineOptions::memopt()) {
	      if (CommandlineOptions::localcache() > 0) {
		if (CommandlineOptions::localcache() > support_sets.size()) {
		  support_sets.insert(support_set);
		}
	      }
	    }
	    else {
	      support_sets.insert(support_set);
	    }
	  }
	  
	  // we now need to count the number of support sets in the orbit:
	  if (CommandlineOptions::use_switch_tables()) {

	    // stabilizer formula:
	    total_count += _confdata.subset_switchtable().orbit_size(support_set);
	  }
	  else {
	    orbit.insert(support_set);
	    for (SymmetryGroup::const_iterator iter = _confdata.symmetries().begin();
		 iter != _confdata.symmetries().end();
		 ++iter) {
	      
	      // store all equivalent support sets:
	      const Symmetry& g(*iter);
	      const subset_type equivalent_support_set(g.map(support_set));
	      orbit.insert(equivalent_support_set);
	      
	      // store/cache all equivalent support sets as found:
	      if (activate_cache) {
		if (CommandlineOptions::memopt()) {
		  if (CommandlineOptions::localcache() > 0) {
		    if (CommandlineOptions::localcache() > support_sets.size()) {
		      support_sets.insert(equivalent_support_set);
		    }
		  }
		}
		else {
		  support_sets.insert(equivalent_support_set);
		}
	      }
	    }
	    total_count += orbit.size();
	  }
	  count += 1;
	  orbit.clear();
	}
      }
      MessageStreams::debug() << "... done." << std::endl;
    } while (subsetperm.lexnext());
    if (_options.output_subsets()) {
      MessageStreams::result() << "}" << std::endl;
    }
    MessageStreams::verbose() << count << " symmetry classes";
    if (!CommandlineOptions::skip_orbitcount()) {
      MessageStreams::verbose() << " | " << total_count << " total " << T::type_name() << "s";
    }
    MessageStreams::verbose() << "." << std::endl;
    if (!_options.output_subsets()) {
      MessageStreams::result() << count << std::endl;
    }
    return 0;
  }
  
  template <>
  int ComputeSubsets<Circuit, 1>::run_from_points() {
    SymmetricSubsetGraphMaster<circuits> ssgm(_confdata, _options);
    const Integer uniform_number = field::binomial(Integer(_confdata.no()), Integer(_confdata.rank() + 1));
    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() << ssgm.symcount() << " symmetry classes";
    if (!CommandlineOptions::skip_orbitcount()) {
      MessageStreams::verbose() << " | " << ssgm.totalcount() << " total circuits";
    }
    MessageStreams::verbose() << " using " << ssgm.nodecount() << " nodes";
    MessageStreams::verbose() << " | " << ssgm.deadendcount() << " branching deadends";
    MessageStreams::verbose() << " | " << ssgm.earlydeadendcount() << " early detected deadends";
    MessageStreams::verbose() << "." << std::endl;
    MessageStreams::verbose() << "(total number is " << ssgm.totalcount() * 100.0 / uniform_number.get_d()
			      << "% of uniform number = " << uniform_number
			      << ")";
    MessageStreams::verbose() << std::endl;
    MessageStreams::verbose() << ssgm.workbuffersize() << " open nodes left to process";
    MessageStreams::verbose() << "." << std::endl;
    if (!_options.output_subsets()) {
      MessageStreams::result() << ssgm.symcount() << std::endl;
    }
    return Signal::exit_value();
  }

  template <>
  int ComputeSubsets<Cocircuit, -1>::run_from_points() {
    SymmetricSubsetGraphMaster<cocircuits> ssgm(_confdata, _options);
    const Integer uniform_number = field::binomial(Integer(_confdata.no()), Integer(_confdata.rank() + 1));
    if (Signal::signal_received()) {
      MessageStreams::verbose().print_dumpseparator();
      MessageStreams::verbose() << "### intermediate results forced by signal:" << std::endl;
      MessageStreams::verbose().print_dumpseparator();
    }
    MessageStreams::verbose() << ssgm.symcount() << " symmetry classes";
    if (!CommandlineOptions::skip_orbitcount()) {
      MessageStreams::verbose() << " | " << ssgm.totalcount() << " total cocircuits";
    }
    MessageStreams::verbose() << " using " << ssgm.nodecount() << " nodes";
    MessageStreams::verbose() << " | " << ssgm.deadendcount() << " branching deadends";
    MessageStreams::verbose() << " | " << ssgm.earlydeadendcount() << " early detected deadends";
    MessageStreams::verbose() << "." << std::endl;
    MessageStreams::verbose() << "(total number is " << ssgm.totalcount() * 100.0 / uniform_number.get_d()
			      << "% of uniform number = " << uniform_number
			      << ")";
    MessageStreams::verbose() << std::endl;
    MessageStreams::verbose() << ssgm.workbuffersize() << " open nodes left to process";
    MessageStreams::verbose() << "." << std::endl;
    if (!_options.output_subsets()) {
      MessageStreams::result() << ssgm.symcount() << std::endl;
    }
    return Signal::exit_value();
  }
  
  template <class T, parameter_type card_offset>
  int ComputeSubsets<T, card_offset>::run() {
    
    // write header for computations concerning subsets:
    write_header(MessageStreams::verbose());
 
    // read input from stdin or file (depending on commandline options):
    read_input(std::cin);

    // preprocess according to commandline options:
    preprocess_data();

    // get the trivial case out of the way first:
    if (_confdata.no() == _confdata.rank()) {
      MessageStreams::verbose() << 0 << " subset_objects in total." << std::endl;
      if (!_options.output_subsets()) {
	MessageStreams::result() << 0 << std::endl;
      }
      return 0;
    }

    if (_options.input_chiro()) {

      // there is no other way currently than to check all rank + 1 subsets mod symmetry:
      return run_from_chiro();
    }
    else {
      
      // if we know the points, we can enumerate subset_objects by symmetric lexicographic subset reverse search:
      return run_from_points();
    }
  }

}; // namespace topcom

#endif

// eof ComputeSubsets.hh
