/* Copyright (c) 1997-2004
   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.
*/

#ident "$Project: polymake $$Id: blending.cc,v 1.31 2005/11/11 10:38:59 gawrilow Exp $"

#include <cstdlib>
#include <argv_options.h>
#include <labels.h>
#include <vector>
#include <Array.h>
#include <Poly.h>
#include <IncidenceMatrix.h>
#include <Graph.h>

#if __GNUC__==3 && __GNUC_MINOR__<=1
template class pm::SingleElementSet<int>;
#endif

enum { relabel=0 };

namespace polymake { namespace polytope {

void blending(Poly& p_out, Poly& p_in1, Poly& p_in2,
	      const int vertex1, const int vertex2,
	      const Array<int>& permutation,
	      const argv_option* options)
{
   const int dim=p_in1.give("DIM"),
             dim2=p_in2.give("DIM");
   if (dim != dim2)
      throw std::runtime_error("dimension mismatch");

   const IncidenceMatrix<> VIF1=p_in1.give("VERTICES_IN_FACETS"),
                           VIF2=p_in2.give("VERTICES_IN_FACETS");
   const int n_vertices1=VIF1.cols(), n_facets1=VIF1.rows(),
      n_vertices2=VIF2.cols();

   if (vertex1 < 0 || vertex1 >= n_vertices1)
      throw std::runtime_error("first vertex number out of range");
   if (vertex2 < 0 || vertex2 >= n_vertices2)
      throw std::runtime_error("second vertex number out of range");

   const Graph<> G1=p_in1.give("GRAPH"),
	         G2=p_in2.give("GRAPH");

   if (G1.degree(vertex1) != dim)
      throw std::runtime_error("first vertex not simple");
   if (G2.degree(vertex2) != dim)
      throw std::runtime_error("second vertex not simple");

   Array<int> neighbors2(dim,-1);

   // reorder the neighbor vertices in P2 if required
   Entire<Graph<>::neighbor_list>::const_iterator nb=entire(G2.neighbors(vertex2));
   if (permutation.empty()) {
      std::copy(nb, neighbors2.begin());
   } else {
      if (permutation.size() != dim)
	 throw std::runtime_error("wrong permutation size");

      for (Array<int>::const_iterator p_i=permutation.begin(); !nb.at_end(); ++p_i, ++nb) {
	 if (*p_i < 0 || *p_i >= dim)
	    throw std::runtime_error("permutation element out of range");

	 if (neighbors2[*p_i] >= 0)
	    throw std::runtime_error("permutation not surjective");

	 neighbors2[*p_i] = *nb;
      }
   }

   // initialize as a block-diagonal matrix
   IncidenceMatrix<> VIF_out=diag(VIF1,VIF2);

   // Facets to be glued are identified via opposite neighbors of vertex{1,2}
   // (that is, not belonging to them).
   // Since both vertices are simple, this mapping is unambigiuos
   Array<int>::const_iterator nb2=neighbors2.begin();
   for (nb=entire(G1.neighbors(vertex1)); !nb.at_end(); ++nb, ++nb2) {
      int f1=(VIF1.col(vertex1) - VIF1.col(*nb)).front(),
	 f2=(VIF2.col(vertex2) - VIF2.col(*nb2)).front() + n_facets1;
      VIF_out.row(f1) += VIF_out.row(f2);
      VIF_out.row(f2).clear();
   }

   // get rid of the vertices
   VIF_out.col(vertex1).clear();
   VIF_out.col(vertex2+n_vertices1).clear();
   VIF_out.squeeze();

   p_out.take("DIM") << dim;
   p_out.take("N_VERTICES") << n_vertices1+n_vertices2-2;
   p_out.take("VERTICES_IN_FACETS") << VIF_out;

   if (options[relabel]) {
      std::vector<std::string> labels1(n_vertices1), labels2(n_vertices2),
	                       labels_out(n_vertices1+n_vertices2-2);
      read_labels(p_in1, "VERTEX_LABELS", labels1.begin(), n_vertices1);
      read_labels(p_in2, "VERTEX_LABELS", labels2.begin(), n_vertices2);

      const std::string tick="'";
      std::copy(entire(concatenate(select(labels1, ~scalar2set(vertex1)),
				   attach_operation(select(labels2, ~scalar2set(vertex2)),
						    constant(tick), operations::add()))),
		labels_out.begin());

      p_out.take("VERTEX_LABELS") << labels_out;
   }
}

} }

using namespace polymake;

/** @file blending
 *
 *  Compute the blending of two polyhedra at simple vertices.
 *  This is a slightly less standard construction.
 *  A vertex is $simple$ if its vertex figure is a simplex.
 *
 *  Moving a vertex $v$ of a bounded polytope to infinity yields an unbounded polyhedron
 *  with all edges through $v$ becoming mutually parallel rays.  Do this to both
 *  input polytopes $P$ and $P'$ at simple vertices $v$ and $v'$, respectively.
 *  Up to an affine transformation one can assume that the orthogonal projections
 *  of $P$ and $P'$ in direction $v$ and $v'$, respectively, are mutually congruent.
 *
 *  Any bijection $b$ from the set of edges through $v$ to the edges through $v'$
 *  uniquely defines a way of glueing the unbounded polyhedra to obtain a new bounded
 *  polytope, the $blending$ with respect to $b$. The bijection is specified as a $permutation$
 *  of indices $0 1 2$ etc. Default permutation is identity.
 *
 *  The number of vertices of the blending is the sum of the numbers of vertices of the
 *  input polytopes minus $2$.  The number of facets is the sum of the numbers of facets
 *  of the input polytopes minus the dimension.
 *  
 *  The resulting polytope is described only combinatorially.
 *
 *  @synopsis blending <output_file> <input_file1> <vertex1> <input_file2> <vertex2> [ -relabel ] [ <permutation> ]
 *
 *  @reading  DIM, VERTICES_IN_FACETS, GRAPH
 *  @writing  DIM, VERTICES_IN_FACETS
 *
 *  @index clients Producing a new polyhedron from others
 *
 *  @author Kerstin Fritzsche (initial version)
 */

int main(int argc, const char* argv[]) {
   argv_option options[]={ { "-relabel" } };
   if (!extract_options(argc,argv,5,options)) {
      cerr << "usage: " << argv[0] << " <out_file> <in_file1> <vertex1> <in_file2> <vertex2> [ -relabel ] [ <permutation> ]" << endl;
      return 1;
   }
   try {
      Poly p_in1(argv[2], ios::in);
      Poly p_in2(argv[4], ios::in);
      Poly p_out(argv[1], ios::out | ios::trunc);

      const int vertex1 = atoi(argv[3]),
	        vertex2 = atoi(argv[5]);

      const Array<int> permutation(argc-6, attach_converter<int>(ptr2container(argv+6)).begin());

      polytope::blending(p_out, p_in1, p_in2, vertex1, vertex2, permutation, options);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
