#include <iostream>
#include <ctype.h>
#include <assert.h>
#include <unordered_map>

#include "Field.hh"
#include "Vector.hh"
#include "Symmetry.hh"
#include "PointConfiguration.hh"
#include "Permutation.hh"
#include "IntegerSet.hh"

namespace topcom {

typedef std::unordered_map<Vector, size_type, Hash<Vector> > column_index_map_type;

// the following function enumerates all sum-element subsets of dim numbers
// via DFS and adds the characteristic vector to a point configuration:

void add_coords_dfs(const size_type dim, 
		    const size_type sum,
		    const size_type start, 
		    const size_type stop,
		    Vector& new_column,
		    PointConfiguration& points,
		    column_index_map_type& col_index) {
  if (sum == 0) {

    // we build a map that stores the column index for each column vector:
    col_index[new_column] = points.no();

    // add the new column to the configuration:
    points.push_back(new_column);
  }
  else {
    for (int i = start; i < stop; ++i) {
      for (int coord = sum; coord > 0; --coord) {
	new_column[i] = coord;
	add_coords_dfs(dim, sum - coord, i + 1, stop, new_column, points, col_index);
      }
      new_column[i] = FieldConstants::ZERO;
    }
  }
}

};

int main(const int argc, const char** argv) {
  using namespace topcom;

  if (argc < 3) {
    MessageStreams::forced() << "usage: " << argv[0] << " <dilation factor> <dimension>" << std::endl;
    return 1;
  }
  size_type sum(atol(argv[1]));
  size_type dim(atol(argv[2]));
  PointConfiguration kDn;
  column_index_map_type col_index;

  // we start with an empty vector:
  Vector new_column(dim + 1, FieldConstants::ZERO);

  add_coords_dfs(dim + 1, sum, 0, dim + 1, new_column, kDn, col_index);

  MessageStreams::result() << "# all lattice points in the "
			   << sum << "-times dilated "
			   << dim << "-simplex:" << std::endl;
  MessageStreams::result() << kDn << std::endl;

  symmetry_collectordata rowperms;

  // travers through all adjacent transpositions to find generators of the symmetric group on rows:
  for (parameter_type row = 0; row < kDn.rank() - 1; ++row) {
    Permutation rowperm(kDn.rank(), 2);
    rowperm[0] = row;
    rowperm[1] = row + 1;
    rowperms.insert(Symmetry(rowperm, true));
  }
  symmetry_collectordata rowperm_generators(SymmetryGroup(kDn.no(), rowperms, false).rowperm_symmetry_generators(kDn));
  SymmetryGroup symmetries(kDn.no(), rowperm_generators, false);

  // as an example for required symmetries, generate all cyclic row permutations:
  Permutation reqrowperm(kDn.rank(), kDn.rank());
  reqrowperm[kDn.rank() - 1] = 0;
  for (parameter_type row = 0; row < kDn.rank() - 1; ++row) {
    reqrowperm[row] = row + 1;
  }
  symmetry_collectordata reqrowperms;
  reqrowperms.insert(Symmetry(reqrowperm, true));
  symmetry_collectordata reqrowperm_generators(SymmetryGroup(kDn.no(), reqrowperms, false).rowperm_symmetry_generators(kDn));
  SymmetryGroup reqsymmetries(kDn.no(), reqrowperm_generators, false);

  // print symmetry generators:
  MessageStreams::result() << std::endl << "# symmetries of the configuration generated by row-permutations:" << std::endl;
  symmetries.write_sorted_generators(MessageStreams::result());
  MessageStreams::result() << std::endl;

  // print required symmetry generators identical to point symmetries:
  MessageStreams::result() << std::endl << "# some possible required symmetries:" << std::endl;

  // required Sym(n) symmetry generators:
  MessageStreams::result() << std::endl << "# required symmetries of the configuration generated by all row-permutations:" << std::endl;
  symmetries.write_sorted_generators(MessageStreams::result());
  MessageStreams::result() << std::endl;  
  
  // required C(n) symmetry generators:
  MessageStreams::result() << std::endl << "# required symmetries generated by cyclic row permutations:" << std::endl;
  reqsymmetries.write_sorted_generators(MessageStreams::result());
  MessageStreams::result() << std::endl;
  
  // requires axis-reflection symmetries (generated by the transposition of the first two rows):
  if (kDn.rank() > 2) {
    Permutation reflectionrowperm(kDn.rank(), kDn.rank());
    reflectionrowperm[1] = 0;
    reflectionrowperm[0] = 1;
    symmetry_collectordata reflectionrowperms;
    reflectionrowperms.insert(Symmetry(reflectionrowperm, false));
    symmetry_collectordata reflectionrowperm_generators(SymmetryGroup(kDn.no(), reflectionrowperms, false).rowperm_symmetry_generators(kDn));
    SymmetryGroup reflectionsymmetries(kDn.no(), reflectionrowperm_generators, false);
    
    // print possible required axis-reflection symmetry generators:
    MessageStreams::result() << std::endl << "# required symmetries generated by the exchange of first two rows:" << std::endl;
    reflectionsymmetries.write_sorted_generators(MessageStreams::result());
    MessageStreams::result() << std::endl;
  }
  return 0;
}

// eof kDn.cc
