#  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: visual.rules 7578 2007-01-21 22:48:26Z gawrilow $

package Visual::Color;

# special TOPAZ blue for an distinguished subcomplex
custom $distinguished_subcomplex="147 178 255";

# special TOPAZ red for an distinguished set of faces
custom $distinguished_face="231 0 74";

# arrows in MORSE_MATCHING
custom $morse_matching_dim_1="255 211 27";

custom $morse_matching_dim_DIM="133 15 255";

# GRAPH in topaz: black
custom $topaz_gr="0 0 0";

# GRAPH in mixed_graph : yellow
custom $primal_gr="255 255 0";

# DUAL_GRAPH in mixed_graph: blue
custom $dual_gr="0 0 255";

# MIXED_GRAPH: black
custom $mixed_gr="0 0 0";

package default;

custom %mixed_graph_params=(
# edge_weight => 1	# increases the ideal length of the mixed edges
);

sub vis_facets($$$$$$) {
   my ($facets, $coord, $vertex_labels, $face_decor, $edge_decor, $point_decor)=@_;
   $vertex_labels &&= split_labels($vertex_labels);
   map {
      my @face = /\d+/g;
      my @params=( Name => $vertex_labels ? join(" ", @{subset($vertex_labels, @face)}) : "@face",
		   Vertices => subset($coord, @face),
		   VertexLabels => $vertex_labels ? subset($vertex_labels, @face) : \@face,
		 );

      if ($#face >= 2) {
	 my @twofaces = all_subsets_of_k(3,0..$#face);
	 new Visual::Polygons( @params, $face_decor,
			       map { new Visual::Polygon( Facet => "{@$_}" ) } @twofaces );

      } elsif ($#face == 1) {
	 #it's only an edge
	 new Visual::Graph( Graph => [ "{1}", "{0}" ], @params, $edge_decor );

      } else {
	 #it's a singular point
	 new Visual::PointSet( @params, $point_decor );
      }

   } @$facets;
}

object SimplicialComplex;

# category: Visualization
# The nodes of the mixed graph are the nodes of the primal @see GRAPH and
# the @see DUAL_GRAPH. Additional to the primal and dual edges, there is
# an edge between a primal and a dual node iff the primal node represents
# a vertex of the corresponding facet of the dual node.

property MIXED_GRAPH
$type="graph";
$temporary=1;


MIXED_GRAPH : FACETS, GRAPH, DUAL_GRAPH, DIM
   client("t_mixed_graph", $this ,map { ("-$_", $mixed_graph_params{$_}) } keys %mixed_graph_params);



# category: Visualization
# Visualizes the complex.
#
# If @see G_DIM < 4, the @see GRAPH and the facets
# are visualized using the @see GEOMETRIC_REALIZATION.
#
# Otherwise, the spring embedder and the @see GRAPH are used to
# produce coordinates for the visualization.
#
# If @see external.JavaView is used to visualize the complex, all faces of
# one facet build a geometry in the jvx-file, so you may use
# <i>Method -> Effect -> Explode Group of Geometries</i> in the
# @see external.JavaView menu.
#
# option: mixed_graph => boolean
#         if @c true, use the @see MIXED_GRAPH for the spring embedder
# option: seed => random seed value for the string embedder
# return: Visual::SimplicialComplex

user_method VISUAL(%Visual::Polygon::decorations, %Visual::Graph::decorations, { mixed_graph => 0 }, { seed => undef }) {
   my ($this, $face_decor, $graph_decor, $options, $seed)=@_;

   my $coord=do {
      if ( $options->{mixed_graph} ) {
	 slice(spring_embedding_3d($this, "MIXED_GRAPH", "read-edge-weights", undef, %$seed), 0, $this->N_VERTICES-1);
      } elsif ($this->exists("GEOMETRIC_REALIZATION") &&
	       $this->G_DIM<=3) {
	 [ convert_to_float($this->GEOMETRIC_REALIZATION) ]
      } else {
	 spring_embedding_3d($this, "GRAPH", %$seed);
      }
   };

   my $labels=$this->lookup("VERTEX_LABELS");

   my $graph= new Visual::Graph( Name => 'GRAPH of '.$this->name,
				 Graph => $this->GRAPH,
				 Coord => $coord,
				 VertexLabels => $labels,
				 VertexColor => $Visual::Color::topaz_gr,
				 $graph_decor
			       );
   visualize(new Visual::SimplicialComplex(Name => $this->name,
					   SimplicialComplex=>$this, defaults=>$face_decor,
					   vis_facets($this->FACETS, $coord, $labels, {}, {}, {}),
					   $graph));
}


# category: Visualization
# Uses the spring embedder to visualize the @see GRAPH.
# option: seed => random seed value for the string embedder
# return: Visual::Graph

user_method VISUAL_GRAPH(%Visual::Graph::decorations, { seed => undef }) {
   my ($this, $decor, $seed)=@_;
   visualize( new Visual::Graph(
      Name => 'GRAPH of '.$this->name,
      Graph => $this->GRAPH,
      Coord => spring_embedding_3d($this, "GRAPH", %$seed),
      NodeLabels => $this->lookup("VERTEX_LABELS"),
      NodeColor => $Visual::Color::topaz_gr,
      $decor
   ));
}


# category: Visualization
# Uses the spring embedder to visualize the @see DUAL_GRAPH.
# option: seed => random seed value for the string embedder
# return: Visual::Graph

user_method VISUAL_DUAL_GRAPH(%Visual::Graph::decorations, { seed => undef }) {
   my ($this, $decor, $seed)=@_;
   visualize( new Visual::Graph(
      Name => 'DUAL_GRAPH of '.$this->name,
      Graph => $this->DUAL_GRAPH,
      Coord => spring_embedding_3d($this, "DUAL_GRAPH", %$seed),
      NodeColor => $Visual::Color::topaz_gr,
      $decor
   ));
}


# category: Visualization
# Uses the spring embedder to visualize the @see MIXED_GRAPH.
# option: seed => random seed value for the string embedder

user_method VISUAL_MIXED_GRAPH(%Visual::Graph::decorations, { seed => undef }) {
   my ($this, $decor, $seed)=@_;
   my $coord=spring_embedding_3d($this, "MIXED_GRAPH", "read-edge-weights", undef, %$seed);
   compose( $this->name,
	    new Visual::Graph( Name => 'GRAPH of '.$this->name,
			       Graph => $this->GRAPH,
			       Coord => slice($coord,0,$this->N_VERTICES-1),
			       NodeLabels => $this->lookup("VERTEX_LABELS"),
			       NodeColor => $Visual::Color::primal_gr,
			       EdgeColor => $Visual::Color::primal_gr,
			       $decor
			     ),
	    new Visual::Graph( Name => 'DUAL_GRAPH of '.$this->name,
			       Graph => $this->DUAL_GRAPH,
			       Coord => slice($coord, $this->N_VERTICES, $this->N_VERTICES+$this->N_FACETS-1),
			       NodeColor => $Visual::Color::dual_gr,
			       EdgeColor => $Visual::Color::dual_gr,
			       $decor
			     ),
	    new Visual::Graph( Name => 'MIXED_GRAPH of '.$this->name,
			       Graph => $this->MIXED_GRAPH,
			       Coord => $coord,
			       NodeColor => $Visual::Color::mixed_gr,
			       EdgeColor => $Visual::Color::mixed_gr,
			       NodeStyle => "hidden",
			       EdgeStyle => "thickness 0.1666667",
			       $decor
			     )
	  );
}


package Visual::SimplicialComplex;

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

# Add a subcomplex with optional different graphical attributes
# args: "PROPERTY_NAME" or [ Facets ]
user_method SUBCOMPLEX($ %Visual::Polygon::decorations, %Visual::Graph::decorations, %Visual::PointSet::decorations) {
   my ($self, $subcomplex, $facet_decor, $edge_decor, $point_decor)=@_;
   if (!ref($subcomplex)) {
      $subcomplex=$self->SimplicialComplex->give($subcomplex);
   }

   $facet_decor->{FacetColor} ||= $Visual::Color::distinguished_subcomplex;

   $edge_decor->{EdgeStyle} ||= "thickness 6";
   $edge_decor->{EdgeColor} ||= $Visual::Color::distinguished_subcomplex;

   $point_decor->{VertexStyle} ||= "thickness 2";
   $point_decor->{VertexColor} ||= $Visual::Color::distinguished_subcomplex;

   my $labels=$self->SimplicialComplex->lookup("VERTEX_LABELS");

   compose($self, vis_facets($subcomplex, $self->elements->[-1]->Coord, $labels, $facet_decor, $edge_decor, $point_decor));
}

# Add faces with optional different graphical attributes
# args: "PROPERTY_NAME" or [ Faces ]
user_method FACES($ %Visual::Polygon::decorations) {
   my ($self, $faces, $facet_decor)=@_;
   if (!ref($faces)) {
      $faces=$self->SimplicialComplex->give($faces);
   }

   my (%edge_decor, %point_decor);
   if (!exists $facet_decor->{FacetColor} && !exists $facet_decor->{FacetStyle}) {
      $facet_decor->{FacetColor}=$Visual::Color::distinguished_face;
   }

   $edge_decor{EdgeColor}=delete $facet_decor->{EdgeColor} || $Visual::Color::distinguished_face;
   $edge_decor{EdgeStyle}=delete $facet_decor->{EdgeStyle} || "thickness 2";

   $point_decor{VertexColor}=delete $facet_decor->{VertexColor} || $Visual::Color::distinguished_face;
   $point_decor{VertexStyle}=delete $facet_decor->{VertexStyle} || "thickness 2";

   $edge_decor{VertexStyle}="hidden";
   $facet_decor->{EdgeStyle}="hidden";
   $facet_decor->{VertexStyle}="hidden";

   my $labels=$self->SimplicialComplex->lookup("VERTEX_LABELS");

   compose($self, vis_facets($faces, $self->elements->[-1]->Coord, $labels, $facet_decor, \%edge_decor, \%point_decor));
}

# Add the @see SimplicialComplex.MORSE_MATCHING to the visualization of the SimplicialComplex.
user_method MORSE_MATCHING(%Visual::Graph::decorations) {
   my ($this, $decor)=@_;
   my $complex = $this->SimplicialComplex;
   my @mm = @{$complex->MORSE_MATCHING};
   my $complex_dim = $complex->DIM;
   my @vertices = @{$this->elements->[-1]->Coord};
   my $dim = 0;
   my @dims = split(/\s+/,$mm[0]);
   shift @dims;
   shift @mm;
   my @graph;
   my $i = 0;
   my %arrow_colors;

   my @colors;
   my @rgb_1 = split(/\s+/,$Visual::Color::morse_matching_dim_1);
   my @rgb_DIM = split(/\s+/,$Visual::Color::morse_matching_dim_DIM);
   for (my $d=0; $d<$complex_dim; ++$d) {
     my @this_color;
     for (my $j=0; $j<3; ++$j) {
       $this_color[$j] = int( ($d*$rgb_1[$j]+($complex_dim-1-$d)*$rgb_DIM[$j])/($complex_dim-1) );
     }
     push(@colors,join(" ",@this_color));
   }

   my @barycenters;
   foreach my $line (@mm) {
      ++$dim if ($i+1 >= $dims[$dim]);
      my $barycenter;
      chomp $line;
      my ($face, $edges) = ($line =~ m/^<?\(\{(\d+(?:\s+\d+)*)\}\s+{((?:\(\d+\s+[01]\)\s*)+)?}\)$/);
      next if($face eq "");
      my @face = split(/\s+/,$face);
      $barycenters[$i] = barycenter(@vertices[@face]);
      my @edges = ($edges =~ m/\(\d+\s+[01]\)/g);
      foreach my $edge(@edges) {
	 my ($to_node, $matched) = ($edge =~ m/\((\d+)\s+([01])\)/);
	 if ($matched) {
	    $graph[$i] = "{".($to_node-1)."}";
	    $arrow_colors{"$i ".($to_node-1)} = $colors[$dim-1];
	    last;
	 } else {
	    $graph[$i] = "{}";
	 }
      }
      ++$i;
   }
   $this->defaults->{FacetStyle} ||= "transparency 0.5";
   my $mmatching = new Visual::Graph(
				     Name => "MORSE_MATCHING of " . $this->SimplicialComplex->name,
				     Graph => \@graph,
				     Coord => \@barycenters,
				     Directed => 1,
				     NodeStyle => "hidden",
				     EdgeColor => \%arrow_colors,
				     $decor
				    );
   compose($this,$mmatching);
}


sub barycenter(@) {
   no integer;
   my @vertices = @_;
   my @barycenter;
   foreach my $vertex (@vertices) {
      my @vertex = split(/\s+/,$vertex);
      for ( my $j = 0; $j <= $#vertex; ++$j) {
	 $barycenter[$j] += $vertex[$j];
      }
   }
   map { $_ /= (scalar @vertices) } @barycenter;
   return join(" ",@barycenter);
}


object SimplicialComplex;

# category: Visualization
# Visualize the @see HASSE_DIAGRAM of a simplicial complex as a multi-layer graph.
# option: seed => random seed value for the node placement
# return: Visual::SimplicialComplexLattice

user_method VISUAL_FACE_LATTICE(%Visual::Lattice::decorations, { seed => undef }) {
   my ($this, $decor, $seed)=@_;
   my ($dims, @graph)=@{$this->HASSE_DIAGRAM};
   $graph[0] =~ s/^\s*<//; pop @graph;
   my ($bottom_node, $top_node)= $graph[0] =~ /\Q({} {})\E/ ? ($#graph, 0) : (0, $#graph);

   my @vertex_labels = Visual::Lattice::prepare_vertex_labels($this, "VERTEX_LABELS");
   my @labels=("")x@graph;
   my $n=1;
   foreach (@graph[1..$#graph-1]) {	# unless top/bottom node
      s/\{(.*?)\}\s+//;  my $label=$1;
      if (@vertex_labels) {
	 $label =~ s/(\d+)/$vertex_labels[$1]/g;
      }
      $labels[$n]=$label;
      ++$n;
   }
   $labels[$bottom_node]=" ";

   my $L=new Visual::SimplicialComplexLattice(
					      Name => "Face lattice of ".$this->name,
					      SimplicialComplex => $this,
					      Graph => \@graph,
					      Directed => 1,
					      top_node => $top_node,
					      ArrowStyle => 0,
					      Mode => "primal",
					      NodeLabels => \@labels,
					      NodeColor => "white",
					      Coord => hd_embedding($this, "HASSE_DIAGRAM", %$seed),
					      $decor
					     )
        -> merge( NodeStyle => { $top_node => "hidden" },
		  EdgeStyle => sub { ($_[0]->incident_nodes)[1]==$top_node && "hidden" } );
   visualize($L);
}


package Visual::SimplicialComplexLattice;

use Struct(
   [ '@ISA' => 'Visual::Lattice' ],
   '$SimplicialComplex'
);

# Add the @see SimplicialComplex.MORSE_MATCHING to the visualization of the face lattice of the simplicial complex.
# Decoration options @c EdgeColor and @c EdgeStyle apply to the matched edges only.
user_method MORSE_MATCHING(%Visual::Lattice::decorations) {
   my ($self, $decor)=@_;
   $decor->{EdgeColor} ||= $Visual::Color::distinguished_face;
   $self->add_matching($self->SimplicialComplex->MORSE_MATCHING, $decor);
   visualize($self);
}


# Add a subcomplex with different graphical attributes
# args: "PROPERTY_NAME" or [ Faces ]
user_method SUBCOMPLEX($ %Visual::Lattice::decorations) {
   my ($self, $subcomplex, $decor)=@_;
   if (!ref($subcomplex)) {
      $subcomplex=$self->SimplicialComplex->give($subcomplex);
   }
   $decor->{NodeColor} ||= $Visual::Color::distinguished_subcomplex;
   $decor->{EdgeColor} ||= $decor->{NodeColor};
   $self->add_subcomplex($self->SimplicialComplex->HASSE_DIAGRAM, $subcomplex, $decor);
   visualize($self);
}

# Add distinguished faces with different graphical attributes @c NodeColor and @c NodeStyle
# args: "PROPERTY_NAME" or [ Faces ]
user_method FACES($ %Visual::Lattice::decorations) {
   my ($self, $faces, $decor)=@_;
   if (!ref($faces)) {
      $faces=$self->SimplicialComplex->give($faces);
   }
   $decor->{NodeColor} ||= $Visual::Color::distinguished_face;
   $self->add_faces($self->SimplicialComplex->HASSE_DIAGRAM, $faces, $decor);
   visualize($self);
}


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