#  Copyright (c) 1997-2018
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
#  http://www.polymake.org
#
#  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.
#-------------------------------------------------------------------------------

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";

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

# @topic objects/Visual::SimplicialComplex
# Visualization of the simplicial complex.
# @relates SimplicialComplex

package Visual::SimplicialComplex;

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

# where to keep the view transformation matrix etc.
method representative { $_[0]->SimplicialComplex }

# @topic objects/Visual::SimplicialComplexLattice
# Visualization of the [[HASSE_DIAGRAM]] of a simplicial complex as a multi-layer graph.
# @relates SimplicialComplex

package Visual::SimplicialComplexLattice;

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

# where to keep the view transformation matrix etc.
method representative { $_[0]->SimplicialComplex }

###########################################################################
package application;

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)=@_;
   my @cells;
   map{
      my $face = $_;
      my $verts=$coord->minor($face,All);
      my @labels= defined($vertex_labels) ? map {$vertex_labels->[$_]} @$face : @$face;
      
      if ($#$face >= 2) {
         my @twofaces = all_subsets_of_k(3,0..$#$face);
         new Visual::Polygons( Name=> (join " ", @labels),
                               Vertices=>$verts,
                               VertexLabels=>\@labels,
                               Facets => \@twofaces,
                               NEdges => $#$face*($#$face+1)/2,
                               $face_decor );

      } elsif ($#$face == 1) {
         #it's only an edge
          my $adj = new props::Graph("{1}\n{0}\n");
         new Visual::Graph( Name=> (join " ", @labels), Graph => new Graph(ADJACENCY => $adj), Coord => $verts, NodeLabels => \@labels, $edge_decor );

      } else {
         #it's a singular point
         new Visual::PointSet( Name=>$labels[0], Points => $verts,VertexLabels => \@labels, $point_decor );
      }

   } @$facets;
}


object SimplicialComplex {

# @category Visualization
# The nodes of the mixed graph are the nodes of the primal [[GRAPH]] and
# the [[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 : Graph<Undirected> {

   # Associated edge weights.
   property EDGE_WEIGHTS : EdgeMap<Undirected,Float> : construct(ADJACENCY);
}


rule MIXED_GRAPH.ADJACENCY, MIXED_GRAPH.EDGE_WEIGHTS : FACETS, GRAPH.ADJACENCY, DUAL_GRAPH.ADJACENCY, DIM {
   mixed_graph($this);
}


# @category Visualization
# Visualizes the complex.
#
# If [[G_DIM]] < 4, the [[GRAPH]] and the facets
# are visualized using the [[COORDINATES]].
#
# Otherwise, the spring embedder and the [[GRAPH]] are used to
# produce coordinates for the visualization.
#
# If [[wiki:external_software#javaview|JavaView]] is used to visualize the complex, all faces of
# one facet build a geometry in the jvx-file, so you may use
# __Method -> Effect -> Explode Group of Geometries__ in the JavaView menu.
#
# @option Bool mixed_graph  use the [[MIXED_GRAPH]] for the spring embedder
# @option Int seed  random seed value for the string embedder
# @return Visual::SimplicialComplex

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

   my $coord=$options->{coord} //
             ( $options->{mixed_graph}
               ? spring_embedding_3d($this->MIXED_GRAPH->ADJACENCY, 'edge-weights' => $this->MIXED_GRAPH->EDGE_WEIGHTS, $seed)
                 ->compute->minor(sequence(0, $this->N_VERTICES), All)
               : spring_embedding_3d($this->GRAPH->ADJACENCY, $seed)->compute);

   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, $graph,
                                           vis_facets($this->FACETS, $coord, $labels, $face_decor,$face_decor,$face_decor)));
}


# @category Visualization
# Uses the spring embedder to visualize the [[GRAPH]].
# @option Int seed  random seed value for the string embedder
# @return Visual::SimplicialComplex

user_method VISUAL_GRAPH(%Visual::Graph::decorations, { seed => undef }) {
   my ($this, $decor, $seed)=@_;
   my $VG=$this->GRAPH->VISUAL( Name => $this->name,
                                NodeColor => $Visual::Color::topaz_gr,
                                $decor, $seed );
   visualize( new Visual::SimplicialComplex(Name => 'GRAPH of '.$this->name, SimplicialComplex=>$this, $VG));
}


# @category Visualization
# Uses the spring embedder to visualize the [[DUAL_GRAPH]].
# @option Int seed  random seed value for the string embedder
# @return Visual::SimplicialComplex

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


# @category Visualization
# Uses the spring embedder to visualize the [[MIXED_GRAPH]].
# @option Int seed  random seed value for the string embedder
# @return Visual::Container

user_method VISUAL_MIXED_GRAPH(%Visual::Graph::decorations, { seed => undef }) {
   my ($this, $decor, $seed)=@_;
   my $coord=spring_embedding_3d($this->MIXED_GRAPH->ADJACENCY,'edge-weights' => $this->MIXED_GRAPH->EDGE_WEIGHTS, $seed)->compute;
   compose( new Visual::Graph( Name => 'GRAPH of '.$this->name,
                               Graph => $this->GRAPH,
                               Coord => $coord->minor(sequence(0,$this->N_VERTICES),All),
                               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 => $coord->minor(range($this->N_VERTICES, $this->N_VERTICES+$this->N_FACETS-1),All),
                               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,
                               EdgeThickness => 0.1666667,
                               $decor
                             ),
            Title => $this->name
          );
}

}

object GeometricSimplicialComplex {

# TODO: consider all inherited user methods; then: user_method ... : COORDINATES ... 

user_method VISUAL(%Visual::Polygon::decorations, %Visual::Graph::decorations, { mixed_graph => 0 }, { seed => undef }) {
   my ($this, $face_decor, $graph_decor, $options, $seed)=@_;
   if (!$options->{mixed_graph} && defined($this->G_DIM) && $this->G_DIM<=3) {
      $options->{coord}=convert_to<Float>(defined($this->lookup("VERTEX_INDICES")) ? $this->COORDINATES->minor($this->VERTEX_INDICES, All) : $this->COORDINATES);
   }
   $this->SUPER::VISUAL($face_decor, $graph_decor, $options, $seed);
}

# TODO: precondition : G_DIM { $this->G_DIM <= 3 }

}

package Visual::SimplicialComplex;

# Add a subcomplex with optional different graphical attributes.
# @param String 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->{EdgeThickness} ||= 6;
   $edge_decor->{EdgeColor} ||= $Visual::Color::distinguished_subcomplex;

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

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

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

# Add faces with optional different graphical attributes.
# @param String 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->[0]->Coord, $labels, $facet_decor, \%edge_decor, \%point_decor));
}

# Add the [[MORSE_MATCHING.MATCHING]] to the visualization of the [[SimplicialComplex]].
user_method MORSE_MATCHING(%Visual::Graph::decorations) {
   my ($this, $decor)=@_;
   my $complex = $this->SimplicialComplex;
   my $hd = $complex->HASSE_DIAGRAM;
   my $matching = $complex->MORSE_MATCHING->MATCHING;
   my $complex_dim = $complex->DIM;
   my $vertices = $this->elements->[0]->Coord;
   my $faces = $complex->FACETS;
      
   my $adj = new props::Graph($hd->N_NODES-2);
   my @barycenters;
   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));
   }
   
   for (my $dim=$complex_dim; $dim>-1; --$dim) {
      foreach my $n (@{$hd->nodes_of_dim($dim)}) {
         $barycenters[$n-1] = barycenter($vertices->minor($hd->FACES->[$n],All));
         foreach my $tail (@{$hd->ADJACENCY->in_adjacent_nodes($n)}) {
            if ($matching->edge($tail,$n)) { #false for $dim=0
               $adj->add_edge($tail-1,$n-1);
               push(@arrow_colors,$colors[$complex_dim-$dim]);
               last;
            }
         }
      }
   } 
   my $coords = convert_to<Float>(new Matrix(@barycenters));
   $this->defaults->{FacetTransparency} ||= "0.5";
   my $mmatching = new Visual::Graph(
                                     Name => "MORSE_MATCHING of " . $complex->name,
                                     Graph => new graph::Graph(ADJACENCY => $adj),
                                     Coord => $coords,
                                     ArrowStyle => 1,
                                     NodeStyle => "hidden",
                                     EdgeColor => \@arrow_colors,
                                     $decor
                                    );
   compose($this,$mmatching);
}


object SimplicialComplex {

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

user_method VISUAL_FACE_LATTICE(%Visual::Lattice::decorations, { seed => undef }) : \
            HASSE_DIAGRAM.ADJACENCY, HASSE_DIAGRAM.DECORATION, HASSE_DIAGRAM.INVERSE_RANK_MAP, HASSE_DIAGRAM.TOP_NODE, HASSE_DIAGRAM.BOTTOM_NODE {
   my ($this, $decor, $seed)=@_;
   $decor->{Name} ||= $this->name;
   visualize( new Visual::SimplicialComplexLattice( Name => "Face lattice of ".$this->name,
                                           SimplicialComplex => $this,
                                           $this->HASSE_DIAGRAM->VISUAL(AtomLabels=>$this->lookup("VERTEX_LABELS"),
                                                                        $decor,$seed)
                                         ));
}

}

package Visual::SimplicialComplexLattice;

# Add the [[MORSE_MATCHING.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.
# @return Visual::SimplicialComplexLattice
user_method MORSE_MATCHING(%Visual::Lattice::decorations) {
   my ($self, $decor)=@_;
   $decor->{EdgeColor} ||= $Visual::Color::distinguished_face;
   $self->elements->[0]->add_matching(($self->SimplicialComplex->MORSE_MATCHING->MATCHING), $decor);
   visualize($self);
}

# Add a subcomplex with different graphical attributes.
# @param String property name of the subcomplex property (to be changed in the near future)
# @option Bool show_filter containment relationship between the subcomplex and the lattice faces
# @return Visual::SimplicialComplexLattice
user_method SUBCOMPLEX($ %Visual::Lattice::decorations, { show_filter => undef }) {
   my ($self, $subcomplex, $decor, $show_filter)=@_;
   my $filter = $show_filter->{show_filter};
   if (!ref($subcomplex)) {
      $subcomplex=$self->SimplicialComplex->give($subcomplex);
   }
   if (defined($filter) && $filter == 1) {
      $decor->{show_filter} = 1; 
   }
   $decor->{NodeColor} ||= $Visual::Color::distinguished_subcomplex;
   $decor->{EdgeColor} ||= $decor->{NodeColor};
   $self->elements->[0]->add_subcomplex($self->SimplicialComplex->HASSE_DIAGRAM, $subcomplex, $decor);
   visualize($self);
}

# Add distinguished faces with different graphical attributes //NodeColor// and //NodeStyle//.
# @param Array<Set> faces (to be changed in the near future)
# @return Visual::SimplicialComplexLattice
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->elements->[0]->add_faces($self->SimplicialComplex->HASSE_DIAGRAM, $faces, $decor);
   visualize($self);
}


# Local Variables:
# mode: perl
# cperl-indent-level:3
# indent-tabs-mode:nil
# End:
