#include "polymake/client.h"

#include "polymake/group/permlib.h"
#include "polymake/Array.h"
#include "polymake/Set.h"
#include "polymake/IncidenceMatrix.h"
#include "polymake/hash_map"

namespace polymake { namespace group {

    perl::ListReturn generate_group_data(const Array< Array<int> >& generators){
      
      PermlibGroup permlib_group(generators);
      
      perl::ListReturn result;
      result << permlib_group.strong_gens()
	     << permlib_group.base()
	     << permlib_group.transversals();
      return result;
    }
    

    //action on DOMAIN
    //constructor for permlib::PermutationGroup(Transversals,Base,SG) would be good!!!
    Array< Set<int> > compute_domain_orbits(perl::Object group){
      Array< Array<int> > gens=group.give("GENERATORS");
      PermlibGroupOfCone group_of_cone(gens);
      return group_of_cone.orbits();
    }

template<class PERM>
struct SetOfIndicesAction {
  Set<int> operator()(const PERM& p, const Set<int>& s) {
    Set<int> ret;
    for( Entire< Set<int> >::const_iterator index = entire(s); !index.at_end(); ++index) {
      ret+=p.at(*index);
    }
    return ret;
  }
};

  
    //action on set of domain indices
    Array< Set<int> > compute_induced_orbits(perl::Object group,const IncidenceMatrix<>& inc_mat){
      Array< Array<int> > gens=group.give("GENERATORS");
      PermlibGroupOfCone group_of_cone(gens);

      hash_map<Set<int>, int> inc_mat_rows; //to look up the index in inc_mat belonging to the set orbit_set
      std::list<Set<int> > sets_of_indices;
      for (Entire< Rows< IncidenceMatrix<> > >::const_iterator set=entire(rows(inc_mat)); !set.at_end(); ++set){	
	sets_of_indices.push_back(*set);
	inc_mat_rows[*set]=set->index();
      }
      std::list<boost::shared_ptr<permlib::OrbitSet<permlib::Permutation,Set<int> > > > o = permlib::orbits<Set<int>, SetOfIndicesAction<permlib::Permutation> >(*(group_of_cone.get_permlib_group()), sets_of_indices.begin(), sets_of_indices.end());

      Array< Set<int> > induced_orbits(o.size()); 
      std::list<boost::shared_ptr<permlib::OrbitSet<permlib::Permutation,Set<int> > > >::const_iterator orbit;
      int count=0;
      for(orbit=o.begin(); orbit != o.end(); ++orbit){
	Set<int> one_orbit;
	permlib::OrbitSet<permlib::Permutation,Set<int> >::const_iterator orb_it; 
	for(orb_it=(*orbit)->begin();orb_it!=(*orbit)->end();++orb_it){
	  Set<int> orbit_set=*orb_it; //one set contained in the orbit	  
	  one_orbit+=inc_mat_rows[orbit_set];
	}
	induced_orbits[count]=one_orbit;
	++count;
      }    

      return induced_orbits;      
    }

    template<class PERM, typename Scalar>
struct CoordinateAction {
      Vector<Scalar> operator()(const PERM& p, const Vector<Scalar>& vec) {
	Vector<Scalar> ret(vec);
	for (int i=1;i<vec.size();++i){
	  ret[i]=vec[p.at(i-1)+1];
	}
	return ret;
  }
};


    //FIXME: almost the same as compute_induced_orbits, maybe join to one function? too many templates...
    template<typename _Matrix, typename Scalar>
    Array< Set<int> > compute_coord_act_orbits(perl::Object group,const GenericMatrix<_Matrix, Scalar>& mat){
      Array< Array<int> > gens=group.give("GENERATORS");
      PermlibGroupOfCone group_of_cone(gens);

      hash_map<Vector<Scalar>, int> mat_rows; //to look up the index in mat belonging to the image of vec
      typename std::list< Vector<Scalar> > vectors;
      int i=0;
      for(typename Entire< Rows< _Matrix > >::const_iterator vec=entire(rows(mat.top())); !vec.at_end(); ++vec){
	vectors.push_back(*vec);
	//mat_rows[*vec]=vec->index();
	mat_rows[*vec]=i;
	++i;
      }
      typename std::list<boost::shared_ptr<permlib::OrbitSet<permlib::Permutation,Vector<Scalar> > > > o = permlib::orbits<Vector<Scalar>, CoordinateAction<permlib::Permutation,Scalar> >(*(group_of_cone.get_permlib_group()), vectors.begin(), vectors.end());
      Array< Set<int> > coord_act_orbits(o.size()); 
      typename std::list<boost::shared_ptr<permlib::OrbitSet<permlib::Permutation,Vector<Scalar> > > >::const_iterator orbit;
      int count=0;
      for(orbit=o.begin(); orbit != o.end(); ++orbit){
	Set<int> one_orbit;
	typename permlib::OrbitSet<permlib::Permutation,Vector<Scalar> >::const_iterator orb_it; 
	for(orb_it=(*orbit)->begin();orb_it!=(*orbit)->end();++orb_it){
	  Vector<Scalar> vec_in_orbit=*orb_it; //one vector contained in the orbit	  
	  one_orbit+=mat_rows[vec_in_orbit];
	}
	coord_act_orbits[count]=one_orbit;
	++count;
      }    

      return coord_act_orbits;  
    }



UserFunction4perl("# @category Others"
                  "# "
                  "# @param Array gens some generators of the group"
                  "# @return ListReturn",
                  &generate_group_data,"generate_group_data(Array)");

UserFunction4perl("# @category Others"
                  "# "
                  "# @param Group group a group of a cone"
                  "# @return Array ",
                  &compute_domain_orbits,"compute_domain_orbits(Group)");

UserFunction4perl("# @category Others"
                  "# "
                  "# @param Group group a group of a cone"
		  "# @param IncidenceMatrix inc the incidences between domain elements"
		  "#    and elements on which an action is induced"
                  "# @return Array an array of the orbits of the induced action",
                  &compute_induced_orbits,"compute_induced_orbits(Group,IncidenceMatrix)");

UserFunctionTemplate4perl("# @category Others"
			  "# "
			  "# @param Group group a group acting on the cone by permuting the coordinates"
			  "# @param Matrix<Scalar> mat a matrix with vectors on which the group acts by coordinate permutation"
			  "# @return Array an array of the orbits under the action on the coordinates",
			  "compute_coord_act_orbits(Group,Matrix)");

}
}


