/* Copyright (c) 1997-2005
   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: product.cc,v 1.23 2005/09/26 14:48:13 gawrilow Exp $"

#include <ctime>
#include <argv_options.h>
#include <labels.h>
#include <vector>
#include <Rational.h>
#include <Poly.h>
#include <Matrix.h>
#include <Set.h>
#include <IncidenceMatrix.h>

namespace polymake { namespace polytope {

namespace {

enum { no_coordinates, relabel };

struct product_label {
   typedef std::string first_argument_type;
   typedef std::string second_argument_type;
   typedef std::string result_type;

   result_type operator() (const first_argument_type& l1, const second_argument_type& l2) const
   {
      return l1 + '*' + l2;
   }
};

} // end unnamed namespace

Matrix<Rational>
product_coord(const Matrix<Rational>& V1, const Matrix<Rational>& V2,
	      int& n_vertices1, int& n_vertices2, int& n_vertices_out, int n_rays,
	      const Set<int>& rays1, const Set<int>& rays2);

void product(Poly& p_out, Poly& p_in1, Poly& p_in2, const argv_option *options)
{
   int n_vertices1=0, n_vertices2=0, n_vertices_out=0, n_rays=0;
   
   Set<int> rays1, rays2;
   if (!options[no_coordinates]) {
      p_in1.give("FAR_FACE") >> rays1;
      p_in2.give("FAR_FACE") >> rays2;
      n_rays=rays1.size()+rays2.size();
   }

   if (options[no_coordinates]
       || p_in1.exists("VERTICES_IN_FACETS") && p_in2.exists("VERTICES_IN_FACETS")) {
      const IncidenceMatrix<> VIF1=p_in1.give("VERTICES_IN_FACETS"),
	                      VIF2=p_in2.give("VERTICES_IN_FACETS");
      n_vertices1=VIF1.cols();  n_vertices2=VIF2.cols();
      n_vertices_out= (n_vertices1 - rays1.size()) * (n_vertices2 - rays2.size()) + n_rays;

      Set<int> far_facet1=accumulate(cols(VIF1.minor(All,rays1)), operations::mul()),
	       far_facet2=accumulate(cols(VIF2.minor(All,rays2)), operations::mul());
      if (far_facet1.size() > 1) far_facet1.clear();		// if size()>1 then FAR_FACE is not a facet
      if (far_facet2.size() > 1) far_facet2.clear();

      const bool has_far_facet= !far_facet1.empty() && !far_facet2.empty();
      const int n_facets1=VIF1.rows() - far_facet1.size(),
	        n_facets2=VIF2.rows() - far_facet2.size(),
	        n_facets_out=n_facets1 + n_facets2 + has_far_facet;
      IncidenceMatrix<> VIF_out(n_facets_out, n_vertices_out);
      if (n_rays==0) {
	 std::copy(entire(pm::product(cols(VIF1), cols(VIF2), operations::concat())),
		   cols(VIF_out).begin());
      } else {
	 // affine vertices come first
	 std::copy(entire(pm::product(cols(VIF1.minor(~far_facet1,~rays1)), cols(VIF2.minor(~far_facet2,~rays2)),
				      operations::concat())), cols(VIF_out).begin());
	 // rays from P1 and P2
	 VIF_out.minor(sequence(0, n_facets_out-has_far_facet), sequence(n_vertices_out-n_rays, n_rays)) =
	    diag_1(VIF1.minor(~far_facet1,rays1), VIF2.minor(~far_facet2,rays2));
	 if (has_far_facet)
	    VIF_out[n_facets_out-1]=sequence(n_vertices_out-n_rays, n_rays);
      }

      p_out.take("N_VERTICES") << n_vertices_out;
      p_out.take("VERTICES_IN_FACETS") << VIF_out;
   }

   if (options[no_coordinates] && p_in1.exists("DIM") && p_in2.exists("DIM")) {
      const int dim1=p_in1.give("DIM"),
	        dim2=p_in2.give("DIM");
      p_out.take("DIM") << dim1+dim2;
   }

   if (!options[no_coordinates]) {

      const Matrix<Rational> V1=p_in1.give("VERTICES | POINTS"),
	                     V2=p_in2.give("VERTICES | POINTS");
      const bool VERTICES_out= p_in1.given()=="VERTICES" && p_in2.given()=="VERTICES";

      const Matrix<Rational> V_out=product_coord(V1, V2, n_vertices1, n_vertices2, n_vertices_out, n_rays, rays1, rays2);

      p_out.take(VERTICES_out ? "VERTICES" : "POINTS") << V_out;
   }

   if (options[relabel]) {
      std::vector<std::string> labels1(n_vertices1), labels2(n_vertices2),
	                       labels_out(n_vertices_out);
      read_labels(p_in1, "VERTEX_LABELS", labels1.begin(), n_vertices1);
      read_labels(p_in2, "VERTEX_LABELS", labels2.begin(), n_vertices2);
      if (n_rays==0) {
	 std::copy(entire(pm::product(labels1, labels2, product_label())), labels_out.begin());
      } else {
	 std::vector<std::string>::iterator l=labels_out.begin();
	 l=std::copy(entire(pm::product(select(labels1,~rays1), select(labels2,~rays2), product_label())), l);
	 const std::string all("all");
	 l=std::copy(entire(attach_operation(select(labels1,rays1), constant(all), product_label())), l);
	 std::copy(entire(attach_operation(constant(all), select(labels2,rays2), product_label())), l);
      }
      p_out.take("VERTEX_LABELS") << labels_out;
   }
}

} }

using namespace polymake;

/** @file product
 *
 *  Construct a new polytope as the product of two given ones.
 *  Unbounded polyhedra are not allowed.
 *
 *  If the option @c -noc (no coordinates) is set, only combinatorial information is handled.
 *
 *  The option @c -relabel creates an additional section @see VERTEX_LABELS.
 *  The label of a new vertex corresponding to $v_1 &oplus; v_2$ will
 *  have the form "$LABEL_1*LABEL_2$".
 *
 *  @synopsis product <output_file> <input_file_1> <input_file_2> [ -noc ] [ -relabel ]
 *
 *  @reading  VERTICES | POINTS or VERTICES_IN_FACETS or both
 *  @writing  VERTICES | POINTS or VERTICES_IN_FACETS, N_VERTICES or both
 *
 *  @index clients Producing a new polyhedron from others
 */

int main(int argc, const char *argv[]) {
   argv_option options[]={ { "-noc" }, { "-relabel" } };
   if (!extract_options(argc,argv,3,options) || argc!=4) {
      cerr << "usage: " << argv[0] << " <out_file> <in_file_1> <in_file_2> [ -noc ] [ -relabel ]" << endl;
      return 1;
   }
   try {
      Poly p_in1(argv[2], ios::in);
      Poly p_in2(argv[3], ios::in);
      Poly p_out(argv[1], ios::out | ios::trunc);
      polytope::product(p_out, p_in1, p_in2, options);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
