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

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

namespace topcom {
  Vector trans_12(const Vector& vec) {
    Vector result(5);
    result[0] = vec[1];
    result[1] = vec[0];
    result[2] = vec[2];
    result[3] = vec[3];
    result[4] = vec[4];
    return result;
  }
  Vector cycle_123(const Vector& vec) {
    Vector result(5);
    result[0] = vec[1];
    result[1] = vec[2];
    result[2] = vec[0];
    result[3] = vec[3];
    result[4] = vec[4];
    return result;
  }
  Vector flip_1(const Vector& vec) {
    Vector result(5);
    result[0] = -vec[0];
    result[1] = vec[1];
    result[2] = vec[2];
    result[3] = vec[3];
    result[4] = vec[4];
    return result;
  }
  Vector flip_4(const Vector& vec) {
    Vector result(5);
    result[0] = vec[0];
    result[1] = vec[1];
    result[2] = vec[2];
    result[3] = -vec[3];
    result[4] = vec[4];
    return result;
  }
};


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

  CommandlineOptions::init(argc, argv);

  PointConfiguration result;
  std::set<Vector> pointset;

  // we start with a unit vector in R^4 and its double:
  Vector rootpoint(5);
  rootpoint[0] = Field(FieldConstants::ONE);
  rootpoint[1] = Field(FieldConstants::ZERO);
  rootpoint[2] = Field(FieldConstants::ZERO);
  rootpoint[3] = Field(FieldConstants::ONE);
  rootpoint[4] = Field(FieldConstants::ONE);
  result.augment(rootpoint);
  pointset.insert(rootpoint);
  
  PointConfiguration old_points = result;

  // next, compute the orbit w.r.t. all permutations
  // and inversions of coordinates:
  bool newpoint_found = true;
  std::set<Vector>::const_iterator finditer;
  while (newpoint_found) {
    newpoint_found = false;
    PointConfiguration new_points;
    Vector new_point(5);
    for (parameter_type pointidx = 0; pointidx < old_points.size(); ++pointidx) {
      new_point = cycle_123(old_points[pointidx]);
      finditer = pointset.find(new_point);
      if (finditer == pointset.end()) {
       	newpoint_found = true;
       	new_points.augment(new_point);
       	pointset.insert(new_point);
      }
      new_point = trans_12(old_points[pointidx]);
      finditer = pointset.find(new_point);
      if (finditer == pointset.end()) {
       	newpoint_found = true;
       	new_points.augment(new_point);
       	pointset.insert(new_point);
      }
      new_point = flip_4(old_points[pointidx]);
      finditer = pointset.find(new_point);
      if (finditer == pointset.end()) {
	newpoint_found = true;
	new_points.augment(new_point);
	pointset.insert(new_point);
      }
      new_point = flip_1(old_points[pointidx]);
      finditer = pointset.find(new_point);
      if (finditer == pointset.end()) {
	newpoint_found = true;
	new_points.augment(new_point);
	pointset.insert(new_point);
      }
    }
    result.augment(new_points);
    old_points.swap(new_points);
    new_points.clear();
  }
  
  // print results:
  MessageStreams::result() << "# vertices of the octaprism in homogeneous coordinates" << std::endl;
  MessageStreams::result() << result << std::endl;
  
  // compute all affine symmetries:
  SymmetryGroup symmetries(result, false);

  MessageStreams::result() << "# switch table generators of the affine symmetries:" << std::endl;
  symmetries.write_sorted_generators(MessageStreams::result());
  MessageStreams::result() << std::endl;
  
  // required symmetries are trivial so far:
  SymmetryGroup required_symmetries(result.no());

  MessageStreams::result() << "# generators for required symmetries by cyclic permutation of coordinates:" << std::endl;
  required_symmetries.write_sorted_generators(MessageStreams::result());
  MessageStreams::result() << std::endl;

  return 0;
}

// eof octaprism.cc
