/* Copyright (c) 1997-2006
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
*/

#include <Rational.h>
#include <Poly.h>
#include <Matrix.h>
#include <Vector.h>
#include <HasseDiagram.h>
#include <Graph.h>
#include <FacetList.h>
#include <Array.h>
#include <linalg.h>
#include <bisector.h>

#include <vector>
#include <list>
#include <cstdlib>

namespace polymake { namespace polytope {

void stellar_all_faces(Poly& p_out, Poly& p_in, int end_dim)
{
   static const char* const req[]={ "VERTICES", "FACETS", "REL_INT_POINT", "VERTICES_IN_FACETS", "HASSE_DIAGRAM", "DUAL_GRAPH" };
   p_in.provide(req);
   Matrix<Rational> V=p_in.give("VERTICES");
   const Matrix<Rational> F=p_in.give("FACETS");
   const Vector<Rational> rel_int_point=p_in.give("REL_INT_POINT");
   FacetList VIF=p_in.give("VERTICES_IN_FACETS");
   const HasseDiagram<> HD=p_in.give("HASSE_DIAGRAM");
   typedef Graph< Vector<Rational> > graph;
   graph DG;
   p_in.give("DUAL_GRAPH") >> neighborhood_matrix(DG);

   const int dim = HD.dim();
   if (end_dim < 0)
      end_dim += dim;
   if (end_dim >= dim || end_dim <= 0)
      throw std::runtime_error("end dimension out of range");

   // facets are stored in the nodes of the dual graph
   std::copy( rows(F).begin(), entire(nodes(DG)) );

   int v_count = V.rows();

   int new_size = HD.nodes_of_dim(0).size();
   for (int d=dim-1; d>=end_dim; --d)
      new_size += HD.nodes_of_dim(d).size();
   V.resize(new_size, V.cols());

   // random access to the facets of VIF
   std::vector< FacetList::iterator > facets_of_VIF;
   facets_of_VIF.reserve(VIF.size());
   for (Entire<FacetList>::iterator fl_it=entire(VIF); !fl_it.at_end(); ++fl_it)
      facets_of_VIF.push_back(fl_it);

   // iterate over dimensions dim-1..end_dim and over all faces of each dimension
   // a new vertex is added for each face
   for (int d=dim-1; d>=end_dim; --d) {
      FacetList new_VIF;
      std::vector< FacetList::iterator > facets_of_new_VIF;
      facets_of_new_VIF.reserve(VIF.size() * (d+1));  // lower bound for #facets after stell-sd of faces dim-1, .., d

      graph new_DG;
      std::vector<int> corr_old_facet;
      corr_old_facet.reserve((d+1) * DG.nodes());
      Array< Set<int> > corr_new_facets(DG.nodes());

      int f_count = 0;
      for (Entire<sequence>::const_iterator it=entire(HD.nodes_of_dim(d)); !it.at_end(); ++it) {
	 const Set<int>& face = HD.node(*it);

	 // produce all relevant inequalities = each neighbour (in the dual graph) of the nodes
	 // corresponding to the facets of star(face) produces one inequality
	 Vector<Rational> m = average( rows(V.minor(face, All)) ) - rel_int_point;
	 Rational t_max = Rational(3);

	 Set<int> star_facets;
	 for (FacetList::iteratorMax incid_facets=VIF.findMax(face); !incid_facets.at_end(); ++incid_facets)
	    star_facets += incid_facets.index();

	 for (Entire< Set<int> >::iterator st_it=entire(star_facets); !st_it.at_end(); ++st_it) {
	    Set<int> neighbors = DG.neighbors(*st_it);
	    for (Entire< Set<int> >::iterator s_it=entire(star_facets); !s_it.at_end(); ++s_it)
	       neighbors -= *s_it;

	    for (Entire< Set<int> >::const_iterator n_it=entire(neighbors); !n_it.at_end(); ++n_it) {
	       const int v= (*facets_of_VIF[*st_it] * *facets_of_VIF[*n_it]).front();
	       const Vector<Rational> inequ = bisector(DG.node(*st_it), DG.node(*n_it), V[v]);

	       const Rational denominator = inequ * m;
	       if (denominator == 0)
		  continue;
	      
	       const Rational t = -inequ * rel_int_point / denominator;
	       if (t<t_max)
		  t_max = t;
	    }
	 }

	 // add new vertex
	 const Rational scale = (t_max + 1) / 2;
	 V[v_count] = rel_int_point + m * scale;

	 // add new facets
	 // iterate over all neighbours (in the dual graph) of the nodes
	 // corresponding to the facets of star(face) and add a facet for each one
	 for (Entire< Set<int> >::iterator st_it=entire(star_facets); !st_it.at_end(); ++st_it) {
	    Set<int> neighbors = DG.neighbors(*st_it);
	    for (Entire< Set<int> >::iterator s_it=entire(star_facets); !s_it.at_end(); ++s_it)
	       neighbors -= *s_it;

	    for (Entire< Set<int> >::const_iterator n_it=entire(neighbors); !n_it.at_end(); ++n_it) {
	       Set<int> new_facet = (*facets_of_VIF[*st_it]) * (*facets_of_VIF[*n_it]);
	       new_facet += v_count;

	       new_VIF.insert(new_facet);
	       facets_of_new_VIF.push_back(--new_VIF.end());

	       if (d>end_dim) {  // add node to new_DG and write the facet inequality as it's node attribute
		  Vector<Rational> equ = null_space(V.minor(new_facet,All))[0];
		  if (equ*rel_int_point < 0)
		     inv_sign(equ);
		  new_DG.add_node(equ);

		  corr_old_facet.push_back(*st_it);
		  corr_new_facets[*st_it] += f_count;
	       }

	       ++f_count;
	    }
	 }
	 ++v_count;

      }  // end of faces of dim d
	
      // update VIF
      VIF = new_VIF;
      facets_of_VIF = facets_of_new_VIF;

      if (d==end_dim)  // no update of DG
	 break;

      // add adjacency information to new_DG and update DG
      FacetList ridges;
      std::vector< std::pair<int,int> > corresp_edge;
      corresp_edge.reserve(VIF.size()*(d+1)/2);  // lower bound for #ridges

      // iterate over new faces to determin adjacency
      for (Entire<FacetList>::iterator f_it=entire(VIF); !f_it.at_end(); ++f_it) {
	 Set<int> old_neighbors = DG.neighbors(corr_old_facet[ f_it.index() ]);
	 old_neighbors += corr_old_facet[ f_it.index() ];

	 for (Entire< Set<int> >::const_iterator n_it=entire(old_neighbors); !n_it.at_end(); ++n_it)
	    for (Entire< Set<int> >::iterator cand=entire(corr_new_facets[*n_it]); !cand.at_end(); ++cand)
	       if (f_it.index() < *cand) {
		  const Set<int> ridge = *f_it * *facets_of_VIF[*cand];

		  if (ridge.size() >= dim-1) {  // potential ridge
		     if (ridges.insertMax(ridge)) {  // new potential ridge has been inserted -> remember corresponding edge
			const int index = (--ridges.end()).index();

			if (size_t(index) >= corresp_edge.size())
			   corresp_edge.resize(index+1);
			corresp_edge[index].first = f_it.index();
			corresp_edge[index].second = *cand;
		     }
		  }
	       }
	}  // end iterate over faces

      // add edges to new_DG
      for (Entire<FacetList>::iterator r_it=entire(ridges);
	   !r_it.at_end(); ++r_it)
	 new_DG.edge(corresp_edge[r_it.index()].first,corresp_edge[r_it.index()].second);

      DG = new_DG;
   }  // end of d = dim-1..end_dim
      
   p_out.take("VERTICES") << V;
   p_out.take("VERTICES_IN_FACETS") << VIF;
}

} }

using namespace polymake;

/** @file stellar_all_faces
 *
 *  Perform a stellar subdivision of all proper faces, starting with the facets.
 *
 *  Parameter $d$ specifies the lowest dimension of the faces to be divided.
 *  It can also be negative, then treated as the co-dimension.
 *  Default is 1, that is, the edges of the polytope.
 *
 *  @synopsis stellar_all_faces <out_file> <in_file> [ <d> ]
 *
 *  @reading  VERTICES, FACETS, REL_INT_POINT, VERTICES_IN_FACETS, HASSE_DIAGRAM, DUAL_GRAPH
 *  @writing  VERTICES, VERTICES_IN_FACETS
 *
 *  @index clients Producing a new polyhedron from others
 *
 *  @author Nikolaus Witte <witte@math.tu-berlin.de>
 */

int main(int argc, const char *argv[])
{
   if (argc<3 || argc>4) {
      cerr << "usage: " << argv[0] << " <out_file> <in_file> [ <d> ]" << endl;
      return 1;
   }
   try { 
      Poly p_in(argv[2], ios::in);
      Poly p_out(argv[1], ios::out | ios::trunc);
      const int end_dim= argc==4 ? atoi(argv[3]) : 1;
      polytope::stellar_all_faces(p_out, p_in, end_dim);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
