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

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

namespace topcom {

  // the following function enumerates all sum-element subsets of dim numbers
  // via DFS and adds the characteristic vector to a point configuration:
  
  void add_ones_dfs(const size_type dim, 
		    const size_type sum,
		    const size_type start, 
		    const size_type stop,
		    Vector& new_column,
		    PointConfiguration& points) {
    if (sum == 0) {
      
      // add the new column to the configuration:
      points.push_back(new_column);
    }
    else {
      for (int i = start; i < stop; ++i) {
	new_column[i] = FieldConstants::ONE;
	add_ones_dfs(dim, sum - 1, i + 1, stop, new_column, points);
	new_column[i] = FieldConstants::ZERO;
      }
    }
  }

  Point flip(const Point& column) {
    Vector result(column);
    for (parameter_type i = 0; i < result.dim(); ++i) {
      result(i) = 1 - result(i);
    }
    return result;
  }

  Symmetry sym_from_flip(const PointConfiguration& points) {
    Symmetry result(points.no());
    for (parameter_type i = 0; i < result.n(); ++i) {
      const Point flipped_point = flip(points.col(i));
      std::pair<bool, parameter_type> find_result = points.index_of_point(flipped_point);
      if (!find_result.first) {
	MessageStreams::forced() << "Symmetry& sym_from_flip(PointConfiguration&): "
		  << "error while searching for image index of "
		  << flipped_point
		  << " from 0-1-flip in "
		  << points.col(i)
		  << " - exiting" << std::endl;
	exit(1);
      }
      result(i) = find_result.second;
    }
    return result;
  }
};

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

  bool three_parameters = false;
  if (argc < 3) {
    MessageStreams::forced() << "usage: " << argv[0] << "<ambient dimension> <coordinate sum>[ <coordinate sum2>]" << std::endl;
    return 1;
  }
  size_type dim(atol(argv[1]));
  size_type sum(atol(argv[2]));
  if ((sum <= 0) || (sum >= dim)) {
    MessageStreams::forced() << argv[0] << ": coordinate sums must be strictly between 0 and dimension - exiting" << std::endl;
    exit(1);
  }
      
  size_type sum2;
  if (argc > 3) {
    three_parameters = true;
    sum2 = atol(argv[3]);
    if ((sum2 <= 0) || (sum2 >= dim) || (sum2 == sum)) {
      MessageStreams::forced() << argv[0] << ": coordinate sums must distinct and strictly between 0 and dimension - exiting" << std::endl;
      exit(1);
    }
  }
  PointConfiguration hypersimplex;

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

  add_ones_dfs(dim, sum, 0, dim, new_column, hypersimplex);

  if (three_parameters) {
    Vector new_column(dim, FieldConstants::ZERO);
    add_ones_dfs(dim, sum2, 0, dim, new_column, hypersimplex);
  }
  symmetry_collectordata rowperms;
  
  // travers through all two cycles to generate the entire symmetric group on rows:
  Permutation perm(hypersimplex.rank(), 2);
  do {
    rowperms.insert(Symmetry(perm, true));
  } while (perm.lexnext());
  symmetry_collectordata sym_generators(SymmetryGroup(hypersimplex.no(), rowperms, false).rowperm_symmetry_generators(hypersimplex));

  // in symmetric parameter cases, add the symmetry generator
  // induced by flipping zeroes and ones:
  if ((!three_parameters && (sum == dim - sum))
      || (three_parameters && (std::min<parameter_type>(sum, sum2) == dim - std::max<parameter_type>(sum, sum2)))) {
    sym_generators.insert(sym_from_flip(hypersimplex));
  }

  // read generators into group without closure computation:
  SymmetryGroup symmetries(hypersimplex.no(), sym_generators, false);

  if (three_parameters) {

    // in this case, the point coordinates are not homogeneous
    // because there are two distinct coordinate sums:
    hypersimplex.homogenize();
  }

  // print points:
  MessageStreams::result() << hypersimplex << std::endl;

  // print symmetry generators sorted in order to
  // obtain a reproducable output:
  symmetries.write_sorted_generators(MessageStreams::result());
  MessageStreams::result() << std::endl;
  return 0;
}

// eof hypersimplex.cc
