/* 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: neighbors_cyclic_normal.cc,v 1.12 2004/09/28 14:28:37 gawrilow Exp $"

#include <list>
#include <Matrix.h>
#include <Graph.h>
#include <Array.h>
#include <IncidenceMatrix.h>
#include <Poly.h>
#include <Rational.h>
#include <linalg.h>
#include <argv_options.h>

namespace polymake { namespace polytope {

namespace {

typedef Array< std::list<int> > CycleList;

template <typename VMatrix, typename IMatrix>
std::pair<CycleList,CycleList>
compute_cycles(int dim,
	       const GenericMatrix<VMatrix>& V,
	       const GenericIncidenceMatrix<IMatrix>& VIF, const Graph<>& DG) {

#if __GNUC__==3 && __GNUC_MINOR__<=1
   (void)VIF.row(0);
#endif

   if (dim==3) { // 3-d case

      const int n_facets=VIF.rows();
      CycleList VIF_cyclic(n_facets), DG_cyclic(n_facets);

      int nf=DG.neighbors(0).front();		// starting neighbor facet
      Set<int> nv=VIF[0] * VIF[nf];		// the ridge between 0 and nf
      int v1=(VIF[0] - VIF[nf]).front(),		// some vertex on the facet 0 but NOT on the ridge
	  v2=(sequence(0, V.rows()) - VIF[0]).front();	// some vertex NOT on the facet 0

      // in the start facet, the right orientation must be chosen using vertex coordinates
      if (det( V.minor(nv,All) / V[v1] / V[v2] ) < 0) {
	 VIF_cyclic[0].push_back(nv.front());  VIF_cyclic[0].push_back(nv.back());
      } else {
	 VIF_cyclic[0].push_back(nv.back());  VIF_cyclic[0].push_back(nv.front());
      }
      DG_cyclic[0].push_back(nf);
      std::list<int> Q;
      Q.push_back(0);

      while (!Q.empty()) {
	 int cf=Q.front(); Q.pop_front();
	 int v0=VIF_cyclic[cf].front(), v=VIF_cyclic[cf].back();
	 nf=DG_cyclic[cf].front();

	 while (true) {
	    nf=(VIF.col(v) * DG.neighbors(cf) - nf).front();
	    DG_cyclic[cf].push_back(nf);
	    v2=(VIF[nf] * VIF[cf] - v).front();
	    if (DG_cyclic[nf].empty()) {
	       // this ridge has the opposite direction in the neighbor facet border!
	       VIF_cyclic[nf].push_back(v2); VIF_cyclic[nf].push_back(v);
	       DG_cyclic[nf].push_back(cf);
	       Q.push_back(nf);
	    }
	    if (v2==v0) break;
	    VIF_cyclic[cf].push_back(v2);  v=v2;
	 }
      }
      return std::pair<CycleList,CycleList>(VIF_cyclic,DG_cyclic);

   } else if (dim==2) { // 2-d case

      CycleList VIF_cyclic(1), DG_cyclic(0);

      int v2=(sequence(0,V.rows()-1) - VIF[0]).front();	// some vertex not on the facet 0
      int v0, v;
      if (det( V.minor(VIF[0],All) / V[v2] ) > 0)
	 v0=VIF[0].front(), v=VIF[0].back();
      else
	 v0=VIF[0].back(), v=VIF[0].front();
      int f=0;
      while (true) {
	 VIF_cyclic[0].push_back(v);
	 if (v==v0) break;
	 f=(VIF.col(v) - f).front();
	 v=(VIF[f] - v).front();
      }
      return std::pair<CycleList,CycleList>(VIF_cyclic,DG_cyclic);

   } else {
      throw std::runtime_error("only 2-d and 3-d polytopes allowed");
   }
}

} // end unnamed namespace

void neighbors_cyclic_normal_primal(Poly& p)
{
   const Matrix<Rational> V=p.give("VERTICES");
   const IncidenceMatrix<> VIF=p.give("VERTICES_IN_FACETS");
   const Graph<> DG=p.give("DUAL_GRAPH");
   const int dim=p.give("DIM");

   std::pair<CycleList,CycleList> cyclic= compute_cycles(dim,V,VIF,DG);
   p.take("VIF_CYCLIC_NORMAL") << cyclic.first;
   p.take("NEIGHBOR_FACETS_CYCLIC_NORMAL") << cyclic.second;
}

void neighbors_cyclic_normal_dual(Poly& p)
{
   const Matrix<Rational> F=p.give("FACETS");
   const IncidenceMatrix<> VIF=p.give("VERTICES_IN_FACETS");
   const Graph<> G=p.give("GRAPH");
   const int dim=p.give("DIM");

   std::pair<CycleList,CycleList> cyclic=compute_cycles(dim,F,T(VIF),G);
   p.take("FTV_CYCLIC_NORMAL") << cyclic.first;
   p.take("NEIGHBOR_VERTICES_CYCLIC_NORMAL") << cyclic.second;
}

} }

using namespace polymake;

/** @file neighbors_cyclic_normal
 *
 *  Convert the combinatorial description of a 2-d or 3-d polytope
 *  to the form suitable for visualization tools.
 *
 *  The rows of the vertex-facet incidence and the facet neighborhood matrices are rearranged
 *  in the facet boundary counterclockwise traversal order, if seen from the outside of the polytope.
 *
 *  In dual mode, the notions of facets and vertices are interchanged.
 *
 *  @synopsis neighbors_cyclic_normal <file> { -primal | -dual }
 *
 *  @reading  in primal mode: BOUNDED, VERTICES, VERTICES_IN_FACETS, DUAL_GRAPH
 *            in dual mode:   CENTERED, FACETS, VERTICES_IN_FACETS, GRAPH
 *  @writing  in primal mode: VIF_CYCLIC_NORMAL, NEIGHBOR_FACETS_CYCLIC_NORMAL
 *            in dual mode:   FTV_CYCLIC_NORMAL, NEIGHBOR_VERTICES_CYCLIC_NORMAL
 */

int main(int argc, const char *argv[])
{
   argv_option options[]={ { "-primal" }, { "-dual" } };
   enum { primal, dual };
   if (argc != 3 || !extract_options(argc,argv,1,options) || argc != 2) {
      cerr << "usage: " << argv[0] << " <file> { -primal | -dual }" << endl;
      return 1;
   }
   try {
      Poly p(argv[1], ios::in | ios::out);
      if (options[primal])
	 polytope::neighbors_cyclic_normal_primal(p);
      else
	 polytope::neighbors_cyclic_normal_dual(p);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
