#  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.
#-----------------------------------------------------------------------------
#  $Project: polymake $$Id: schlegel.rules 7540 2006-12-21 21:04:18Z gawrilow $


object Polytope;

# category: Visualization
# Set of stored parameters to create a Schlegel diagram.
# This is a temporary solution until the hierarchical objects are fully implemented.
#
property SCHLEGEL_PARAMS
$type="text";


# A Schlegel diagram of a polytope
object SchlegelDiagram


# A reference to the polytope being visualized.
# As soon as the object hierarchy is properly implemented, this property becomes the
# regular 'parent' link.
#
property Polytope
$type="object";

# The facet number in the original polytope, giving the projection plane.
property FACET
$type="cardinal";
$accept=sub : method {
   my ($this, $facet)=@_;
   if ($facet<0 || $facet>=$this->Polytope->N_FACETS) {
      die "facet index out of valid range 0..", $this->Polytope->N_FACETS-1, "\n";
   }
   return $facet;
};


# Zoom factor.
property ZOOM
$type="scalar";
$accept=sub {
   no integer; my $z=eval $_[0];	# FIXME: until Rational are directly available in perl
   die "zoom factor not in (0..1]\n" if $z<=0 || $z>1;
   return $_[0];		# return the original format
};


# The intersection point of the projection facet and the view ray.
property FACET_POINT
$type="vector";

# The center point of the projection, lying outside the polytope.
property VIEWPOINT
$type="vector";

# A point on the view ray lying inside the polytope.
property INNER_POINT
$type="vector";


# Create the default set of parameters for the schlegel diagram
FACET_POINT, INNER_POINT : Polytope, FACET
### Polytope.{VERTICES, VERTICES_IN_FACETS, VERTEX_BARYCENTER}
   client("schlegel_params", $this, $this->Polytope);


# Matrix of a projective transformation mapping the whole polytope into the @see FACET
# The points belonging to this facet stay fixed.

property TRANSFORM
$type="matrix";


# Coordinates in affine 3-space of the vertices which
# correspond to a 3-dimensional (Schlegel-) projection of a 4-polytope.

property VERTICES
$type="matrix";
$temporary=1;


TRANSFORM, VIEWPOINT : Polytope, FACET, ZOOM, FACET_POINT, INNER_POINT
### Polytope.{VERTICES, FACETS, DUAL_GRAPH}
### PRECONDITION: Polytope.BOUNDED
###   $BOUNDED
### BODY:
   client("schlegel_transform", $this, $this->Polytope);


method compute_vertices {
   my ($this, $result, @sections)=@_;
   client("schlegel_vertices", $this, $this->Polytope, $result, @sections);
}

VERTICES : Polytope, TRANSFORM, FACET
### Polytope.{VERTICES, FACETS}
   $this->compute_vertices("VERTICES", "VERTICES");


##################################################################################
# visualization

package Visual::Schlegel;
use Struct (
   [ '@ISA' => 'Visual::Container' ],
   '$Schlegel',
);

# the diagram itself as an embedded graph
method basis_graph { $_[0]->elements->[0] }

# Color of the Schlegel projection facet (RGB)
custom $proj_facet="0 0 0";

# Color of the facets on the Schlegel projection (RGB)
custom $solid_color="0 0 255";

# Illustrate the behavior of a linear objective function on the polytope.
# Superpose the drawing with the directed graph induced by the objective function.

user_method DIRECTED_GRAPH {
   my ($self)=@_;
   my $lp=$self->Schlegel->Polytope;
   $self->basis_graph->EdgeStyle="thickness 0.333";
   push @{$self->elements},
        new Visual::Graph(
	       Name => "DIRECTED_GRAPH of " . $self->Schlegel->Polytope->name,
	       Graph => $lp->DIRECTED_GRAPH,
	       Coord => $self->basis_graph->Coord,
	       NodeStyle => "hidden",
	       Directed => 1,
	   );
   visualize($self);
}

# Illustrate the behavior of a linear objective function on the polytope.
# Color the vertices according to the values of the objective function.

user_method VERTEX_COLORS {
   my ($self)=@_;
   my $lp=$self->Schlegel->Polytope;
   $self->basis_graph->NodeColor=$lp->VERTEX_COLORS;
   visualize($self);
}

method append_points {
   my ($self, $points)=@_;
   my $G=$self->basis_graph;
   # important: $G->Coord remains magical!
   my $coord=$G->Coord;
   my $emb=detect_dynamic($coord);
   my $n=$emb->get_n_vertices;
   if ($emb==$G->Coord) {
      # no points besides own VERTICES so far
      $G->Coord=slice($G->Coord,0,$n-1);
   }
   $emb->push($points);
   return slice($G->Coord, $n, $n+@$points);
}

# Draw the edges of the @see Polytope.TRIANGULATION_BOUNDARY
user_method TRIANGULATION_BOUNDARY {
   my ($self)=@_;
   my $P=$self->Schlegel->Polytope;
   $self->Name="Triangulation Boundary of ".$P->name;
   push @{$self->elements},
        new Visual::Graph(
	       Name => $P->name."-inner-edges",
	       Graph => $P->TriangulationBoundaryInnerEdges($self->basis_graph),
               Coord => $self->basis_graph->Coord,
               NodeStyle => "hidden",
               EdgeStyle => "thickness 0.333",
	    );
   visualize($self);
}

user_method TRIANGULATION_BOUNDARY_SOLID(%Visual::Polyhedron::decorations) {
   my ($self, $decor)=@_;
   my $simplex_faces_3d=[ "{0 2 1}", "{0 1 3}", "{0 3 2}", "{1 2 3}" ];
   my $G=$self->basis_graph;
   $G->VertexStyle = "hidden";
   my $P=$self->Schlegel->Polytope;
   my $TB = $P->TRIANGULATION_BOUNDARY;
   my @triangulation;
   my $i = 0;

   foreach (@{$TB}) {
      my $facet = $_;
      $facet =~ s/^{(.*)}$/$1/;
      my @simplices = ($facet =~ m/{([\d\s]+)}/g);
      foreach my $simplex (@simplices) {
	 my @points = ($simplex =~ /\d+/g);
	 push @triangulation,
	      new Visual::Polyhedron(
		     FacetColor => $Visual::Schlegel::solid_color,
		     VertexLabels => \@points,
		     $decor,
		     Name => "Triangulation of Facet $i of ".$P->name. ", $simplex",
		     Vertices => subset($G->Coord, @points),
		     Facets => $simplex_faces_3d,
		     VertexStyle => "hidden",
		     Closed => 0,
		  );
      }
      ++$i;
   }
   $self->Name="Triangulation Boundary of ".$P->name;
   push @{$self->elements}, \@triangulation;
   visualize($self);
}


# Draw the facets of the Schlegel diagram as polytopes.

user_method SOLID(%Visual::Polyhedron::decorations) {
   my ($self, $decor)=@_;
   my $G=$self->basis_graph;
   $G->VertexStyle = "hidden";
   my $P=$self->Schlegel->Polytope;
   my @facets;
   for (my $i = 0; $i < $P->N_FACETS; ++$i) {
      next if $i==$self->Schlegel->FACET;
      my $F = new Apps::polytope::RationalPolytope("Facet #$i");
      Modules::client("facet",$F,$P,$i,"-relabel");
      push @facets,
           new Visual::Polyhedron(
		  FacetColor => $Visual::Schlegel::solid_color,
		  VertexLabels => $F->VERTEX_LABELS,
		  $decor,
		  Name => $F->name ." of ".$P->name,
		  Vertices => subset($G->Coord, split(/\s+/,$F->VERTEX_LABELS)),
		  Facets => $F->VIF_CYCLIC_NORMAL,
		  FacetNeighbors => $F->NEIGHBOR_FACETS_CYCLIC_NORMAL,
		  VertexStyle => "hidden",
		  Closed => 1,
	       );

   }
   push @{$self->elements}, \@facets;
   visualize($self);
}

# Visualize the construction of a 3D Schlegel diagram, that is, the Viewpoint, the 3-polytope and
# the projection onto one facet

user_method CONSTRUCTION(%Visual::Polyhedron::decorations) {
   my ($self, $decor)=@_;
   my $P=$self->Schlegel->Polytope;
   die "polytope must be 3-dimensional.\n" if $P->DIM != 3;
   my $VP = new Visual::Polyhedron(
		   EdgeStyle => "thickness 1",
		   $decor,
		   Name => $P->name,
		   Vertices => [ $P->dehomogenize($P->VERTICES) ],
		   Facets => $P->VIF_CYCLIC_NORMAL,
		   FacetNeighbors => $P->NEIGHBOR_FACETS_CYCLIC_NORMAL,
		   FacetStyle => "hidden",
		   VertexStyle => "hidden",
		);
   client("schlegel_vertices_on_facet", $self->Schlegel, $P, "VERTICES", "VERTICES");
   $self->basis_graph->Coord = $self->Schlegel->VERTICES;
   my @graph = ("{0}\n")x($P->N_VERTICES+1);
   $graph[0] = "{". join(" ",(1..($P->N_VERTICES+1)))."}\n";
   my $CG=new Visual::Graph(
	         Name =>  "Construction of Schlegel diagram of ". $P->name,
		 Graph => \@graph,
		 Coord => [$P->dehomogenize($self->Schlegel->VIEWPOINT),
			   $P->dehomogenize($P->VERTICES)],
		 NodeStyle => "hidden",
		 EdgeColor => "0 0 0",
		 EdgeStyle => "thickness 0.25"
	      );
   my $VV=new Visual::PointSet(
		 Name => "Viewpoint of Schlegel diagram of ".$P->name,
		 Points => [$P->dehomogenize($self->Schlegel->VIEWPOINT)],
		 PointStyle =>"thickness 2",
		 PointColor => "0 0 255",
		 PointLabels => ["ViewPoint"]
	      );
   $self->basis_graph->EdgeStyle = "thickness 1.5";
   push @{$self->elements}, $VP, $CG, $VV;
   visualize($self);
}

#################################################################################
package Visual::SchlegelEmbedding;
@ISA=qw( Visual::Embedding );

sub compute {
   unbless($_[0]);
   my ($Schlegel, $n_vert)=splice @{$_[0]}, 0, 2;
   $Schlegel->compute_vertices($_[0], @{$_[0]});
   $_[0];
}

sub get_n_vertices {
   retrieve($_[0],1);
}

sub push {
   my $self=shift;
   defuse_magic($self);
   $self->[1] += @_==1 ? scalar(@{$_[0]}) : $_[0];
   push @$self, $_[-1];
}

#################################################################################

object SchlegelDiagram;

function schlegel_embedding {
   new Visual::SchlegelEmbedding(@_);
}

function dim(Visual::SchlegelEmbedding) { 3 }

# Draw the Schlegel diagram
# return: Visual::Schlegel
user_method VISUAL(%Visual::Graph::decorations) {
   my ($self, $decor)=@_;
   my $P=$self->Polytope;
   if (!$P->BOUNDED) {
      die "polytope ", $P->name, " is not BOUNDED and therefore cannot be visualized\n";
   }

   my @edge_colors=($Visual::Color::facets, $Visual::Schlegel::proj_facet);
   if (exists $decor->{EdgeColor}) {
      if (is_ARRAY($decor->{EdgeColor}) && @{$decor->{EdgeColor}}==2) {
	 @edge_colors=@{delete $decor->{EdgeColor}};
      } elsif (!ref($decor->{EdgeColor})) {
	 $edge_colors[0]=delete $decor->{EdgeColor};
      }
   }
   my $basis_graph=new Visual::Graph(
			  Name => $P->name,
			  Graph => $P->GRAPH,
			  VertexLabels => $P->lookup("VERTEX_LABELS"),
			  Coord => schlegel_embedding($self, $P->N_VERTICES, "VERTICES"),
			  $decor
		       );

   my %proj_facet=map { $_ => 1 } $P->VERTICES_IN_FACETS->[$self->FACET] =~ /\d+/g;
   visualize( new Visual::Schlegel(
		     Name => "Schlegel diagram of " . $P->name,
		     Schlegel => $self,
		     defaults => {
		        EdgeColor => sub {
			                my ($s, $t)=(shift)->incident_nodes;
					$edge_colors[$proj_facet{$s} && $proj_facet{$t}]
				     },
		     },
		     $basis_graph
	          ) );
}

#################################################################################

object Polytope;

declare %make_schlegel_params=( FACET => undef, ZOOM => undef );

# category: Visualization
# Create a SchlegelDiagram object.
#
# The projection ray will join the vertex barycenters of the polytope
# and the projection facet.
#
# Default zoom factor is 9/10.
#
# option: FACET => projection facet index  (default: 0)
# option: ZOOM => zoom factor (rational or double)
#
# return: SchlegelDiagram

user_method makeSchlegelDiagram(%make_schlegel_params) {
   my ($this, $params)=@_;
   my @props=(Polytope => $this, %$params);

   my $stored_params=$this->lookup("SCHLEGEL_PARAMS");
   if (my @stored_params= $stored_params =~ /\A\s* (\d+) \s+ (\S+) \s* ^(.*)$ \s* ^(.*)$/xms) {
      if (!exists $params->{FACET}) {
	 push @props, (FACET => $stored_params[0],
		       FACET_POINT => $stored_params[2],
		       INNER_POINT => $stored_params[3]);
      }
      if (!exists $params->{ZOOM}) {
	 push @props, (ZOOM => $stored_params[1]);
      }
   } else {
      if (!exists $params->{FACET}) {
	 push @props, (FACET => 0);
      }
      if (!exists $params->{ZOOM}) {
	 push @props, (ZOOM => "9/10");
      }
   }

   new Apps::polytope::SchlegelDiagram(@props);
}

# category: Visualization
# Create a Schlegel diagram and draw it.
# return: Visual::Schlegel

user_method SCHLEGEL(%Visual::Graph::decorations, %make_schlegel_params) {
   my ($this, $decor, $params)=@_;
   $this->makeSchlegelDiagram($params)->VISUAL($decor);
}


# Local Variables:
# mode: perl
# c-basic-offset:3
# End:
