#  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_framework.rules 7538 2006-12-21 13:48:05Z thilosch $
#########################################################################################

object Framework;

# category: Visualization
#
# Visualize the @see FRAMEWORK.
#
# return: Visual::Framework
user_method VISUAL(%Visual::Graph::decorations) {
   my ($this, $decor)=@_;
   die "dimension must be two or three for visualization\n" if ($this->DIM >= 4 || $this->DIM <= 1);
   my $G=new Visual::Graph(
			   Name => "GRAPH of " . $this->name,
			   Graph => $this->FRAMEWORK,
			   NodeLabels => $this->lookup("NODE_LABELS"),
			   Coord => [convert_to_float($this->EMBEDDING)],
			   $decor
			  );
   visualize( new Visual::Framework( Name => $this->name,
				     Framework => $this,
				     $G ));
}

package Visual::Framework;

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

# Add the infinitesimal motions to the visualization.
# If the optional argument @a onewindow is false, each motion is displayed in a separate window.
#
# args: [ onewindow ]
user_method INFINITESIMAL_MOTIONS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_motions($this,$onewindow,"INFINITESIMAL");
}

# Add the @see Framework.EXPANSIVE_MOTIONS to the visualization.
# if the optional argument @a onewindow is false, every motion is displayed in a separate window
#
# args: [ onewindow ]
user_method EXPANSIVE_MOTIONS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_motions($this,$onewindow,"EXPANSIVE");
}

# Add the @see Framework.INFINITESIMAL_MOTIONS and @see Framework.EXPANSIVE_MOTIONS to the visualization.
# If the optional argument @a onewindow is false, every motion is displayed in a separate window.
#
# args: [ onewindow ]
user_method ALL_MOTIONS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_motions($this, $onewindow, "INFINITESIMAL","EXPANSIVE");
}


# Add the @see Framework.INFINITESIMAL_PATTERNS to the visualization. The expanding edges are shown in green,
# the shrinking edges in red and those with fixed length in black.
# If the optional argument @a onewindow is false, every pattern is displayed in a separate window.
#
# args: [ onewindow ]
user_method INFINITESIMAL_PATTERNS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_patterns($this,$onewindow,"INFINITESIMAL");
}

# Add the @see Framework.EXPANSIVE_PATTERNS to the visualization. The expanding edges are shown in green.
# If the optional argument @a onewindow is false, every patternis displayed in a separate window.
#
# args: [ onewindow ]
user_method EXPANSIVE_PATTERNS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_patterns($this,$onewindow,"EXPANSIVE");
}

# Add the @see Framework.EXPANSIVE_PATTERNS and the @see Framework.INFINITESIMAL_PATTERNS to the visualization.
# If the optional argument @a onewindow is false, every pattern is displayed in a separate window.
#
# args: [ onewindow ]
user_method ALL_PATTERNS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_patterns($this,$onewindow,"INFINITESIMAL", "EXPANSIVE");
}

# Add the @see Framework.INFINITESIMAL_RIGID_COMPONENTS to the visualization of a framework, i.e. 
# for each @see Framework.INFINITESIMAL_MOTIONS add its rigid components.
# If the optional argument @a onewindow is false, every component is displayed in a separate window.
#
# args: [ onewindow ]
user_method INFINITESIMAL_RIGID_COMPONENTS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_rigid_components($this,$onewindow,"INFINITESIMAL");
}

# Add the @see Framework.EXPANSIVE_RIGID_COMPONENTS to the visualization of a framework, i.e. 
# for each @see Framework.EXPANSIVE_MOTIONS add its rigid components.
# If the optional argument @a onewindow is false, every component is displayed in a separate window.
#
# args: [ onewindow ]
user_method EXPANSIVE_RIGID_COMPONENTS(;$=1) {
  my ($this, $onewindow)=@_;
  visual_rigid_components($this,$onewindow,"EXPANSIVE");
}

# Show the @see FRAMEWORK, with @see Framework.EXPANSIVE_MOTIONS, @see Framework.EXPANSIVE_PATTERNS and 
# @see Framework.EXPANSIVE_RIGID_COMPONENTS.
# If the optional argument @a onewindow is false, every motion is displayed in a separate window.
#
# args: [ onewindow ]
user_method EXPANSIVE(;$=1) {
  my ($this, $onewindow)=@_;
  visual_all($this,$onewindow,"EXPANSIVE");
}

# Show the @see FRAMEWORK, with @see Framework.INFINITESIMAL_MOTIONS, @see Framework.INFINITESIMAL_PATTERNS and 
# @see Framework.INFINITESIMAL_RIGID_COMPONENTS.
# If the optional argument @a onewindow is false, every motion is displayed in a separate window.
#
# args: [ onewindow ]
user_method INFINITESIMAL(;$=1) {
  my ($this, $onewindow)=@_;
  visual_all($this,$onewindow,"INFINITESIMAL");
}

################################################################################
# helper functions

sub visual_motions($$@) {
   # $type is either: INFINITESIMAL, OTHER, SHRINKING, EXPANSIVE
   my ($this,$onewindow,@types)=@_;
   my $framework = $this->Framework;
   my $dim = $framework->DIM;
   die "dimension must be 2 or 3\n" unless($dim == 2 || $dim == 3);
   my @motions;
   no integer;
   foreach my $type(@types) {
      my $i = 1;
      foreach my $motion (@{$framework->give(($type)."_MOTION_COORDINATES")}) {
	 if ($onewindow) {
	    push @motions, graph_from_motion($framework,$type,$motion,$i);
	 } else {
	    compose($this, graph_from_motion($framework,$type,$motion,$i));
	 }
	 ++$i;
      }
   }
   compose($this,@motions) if $onewindow;
}

sub graph_from_motion($$$$) {
  my ($framework,$type,$motion,$motion_number) = @_;
  my $dim = $framework->DIM;
   my $n_nodes = $framework->N_NODES;
  my $n_motions = scalar @{$framework->give(($type)."_MOTION_COORDINATES")};
  my @point_motions = ($motion =~ m/((?:[-]?\d+(?:\/\d+)?\s+){$dim})/g);#\s+[-]?\d+(?:\/\d+)?)/g);
  map { $_ = $_."\n" } @point_motions;
  my $motions = [ convert_to_float(\@point_motions) ];
  my $color = join(" ",map { $_ *= 255; $_ = POSIX::floor($_); } Visual::hsv2rgb((360/$n_motions)*$motion_number,1,0.8));
  my @graph;
  map { 
    $graph[$_] = "{".($_+$n_nodes)."}\n";
    $graph[$_+$n_nodes] = "{}\n";
  } (0..$n_nodes-1);
  return new Visual::Graph(
				   Name => "$type flex #$motion_number",
				   Graph => \@graph,
				   Directed => 1,
				   Coord => [ @{[convert_to_float($framework->EMBEDDING)]}, @{$motions} ],
				   NodeStyle => "hidden",
				   EdgeStyle => "thickness 1.5",
				   EdgeColor => $color
				  );
}

sub visual_patterns($$@) {
   # $type is either: INFINITESIMAL, OTHER, SHRINKING, EXPANSIVE
   my ($this,$onewindow,@types)=@_;
   my $framework = $this->Framework;
   my $dim = $framework->DIM;
   die "dimension must be 2 or 3\n" unless($dim == 2 || $dim == 3);
   my @patterns;
   no integer;
   foreach my $type(@types) {
      my $p = 1;
      foreach my $pattern (@{$framework->give(($type)."_PATTERNS")}) {
	 if ($onewindow) {
	    push @patterns, graph_from_pattern($framework,$type,$pattern,$p);
	 } else {
	    compose($this, graph_from_pattern($framework,$type,$pattern,$p));
	 }
	 ++$p;
      }
   }
   compose($this,@patterns) if $onewindow;
}

sub graph_from_pattern($$$$) {
  my ($framework,$type,$pattern,$pattern_number) = @_;
  my $n_nodes = $framework->N_NODES;
  my @edge_expansion = split(/\s+/,$pattern);
  my @graph = ("a")x$n_nodes;
  for my $i (0..$n_nodes-1) {
    $graph[$i] = [0..$i-1,$i+1..$n_nodes-1];
  }
  map { 
    $_ = "{".join(" ",@{$_})."}\n";
  } @graph;
  my %edge_colors;
  my ($from_node, $to_node) = (0,1);
  foreach my $edge( @edge_expansion ) {
    if($edge == 0) {
      $edge_colors{"$from_node $to_node"} = "100 100 100";
      $edge_colors{"$to_node $from_node"} = "100 100 100";
    }
    if($edge > 0) {
      $edge_colors{"$from_node $to_node"} = "0 255 0";
      $edge_colors{"$to_node $from_node"} = "0 255 0";
    } elsif($edge < 0) {
      $edge_colors{"$from_node $to_node"} = "255 0 0";
      $edge_colors{"$to_node $from_node"} = "255 0 0";
    }
    $to_node++;
    if($to_node == $n_nodes) {
      $from_node++; $to_node = $from_node+1;
    }
  }
  return new Visual::Graph(
				   Name => "$type pattern #$pattern_number",
				   Graph => \@graph,
				   Coord => [convert_to_float($framework->EMBEDDING)],
				   NodeStyle => "hidden",
				   EdgeColor => \%edge_colors,
				   EdgeStyle => "thickness 1"
				  );
}

sub visual_rigid_components($$@) {
   # $type is either: EXPANSIVE, INFINITESIMAL
   my ($this,$onewindow,@types)=@_;
   my $framework = $this->Framework;
   my $dim = $framework->DIM;
   die "dimension must be 2 or 3\n" unless($dim == 2 || $dim == 3);
   my @pattern_rigid_components;
   no integer;
   foreach my $type(@types) {
      my $p = 1;
      foreach my $pattern_rc (@{$framework->give(($type)."_RIGID_COMPONENTS")}) {
	 if ($onewindow) {
	    push @pattern_rigid_components, polytopes_from_rigid_components($framework,$type,$pattern_rc,$p);
	 } else {
	    compose($this, polytopes_from_rigid_components($framework,$type,$pattern_rc,$p));
	 }
	 ++$p;
      }
   }
   compose($this,@pattern_rigid_components) if $onewindow;
}

sub polytopes_from_rigid_components($$$$) {
  my ($framework,$type,$pattern_rc,$pattern_number) = @_;
  my @cliques = ($pattern_rc =~ m/\{\d+(?:\s+\d+)*\s+\d+\}/g);
  my @visual_cliques;
  my $i = 1;
  foreach my $clique( @cliques ) {
    $clique =~ s/\{(.*)\}/$1/;
    my @clique = split(/\s+/,$clique);
    my $C = new Apps::polytope::RationalPolytope("$type pattern #$pattern_number, Clique #$i");
    my @embedding = @{$framework->EMBEDDING}[@clique];
    map { $_ = "1 $_" } @embedding;
    $C->POINTS = [@embedding];
    #	  $C->commit();
    # 	Modules::client("lrs_ch_client",$C,"-dual");
    if ($C->DIM >= 2) {
      push @visual_cliques, new Visual::Polyhedron(Name => $C->name,
						   Vertices => [ $C->dehomogenize($C->VERTICES) ],
						   Facets => $C->VIF_CYCLIC_NORMAL,
						   FacetColor => "255 0 0",
						   FacetStyle => "transparency 0.5",
						   EdgeStyle => "hidden",
						   VertexStyle => "hidden",
						   Closed => 0,
						  );
      ++$i;
    }
  }
  return @visual_cliques;
}

sub visual_all($$$) {
   my ($this,$onewindow,@types) = @_;
   my $framework = $this->Framework;
   my $dim = $framework->DIM;
   die "dimension must be 2 or 3\n" unless($dim == 2 || $dim == 3);
   no integer;
   my @all;
   foreach my $type(@types) {
      my $p = 1;
      for (my $p = 0; $p <= $#{$framework->give(($type)."_PATTERNS")}; ++$p) {
	 my @patterns;
	 my $motion = ${$framework->give(($type)."_MOTION_COORDINATES")}[$p];
	 push @patterns, graph_from_motion($framework,$type,$motion,$p);
	 my $pattern = ${$framework->give(($type)."_PATTERNS")}[$p];
	 push @patterns, graph_from_pattern($framework,$type,$pattern,$p);
	 my $pattern_rc = ${$framework->give(($type)."_RIGID_COMPONENTS")}[$p];
	 push @patterns, polytopes_from_rigid_components($framework,$type,$pattern_rc,$p);
	 if ($onewindow) {
	    push @all, @patterns;
	 } else {
	    compose($this,@patterns);
	 }
      }
   }
   compose($this,@all) if $onewindow;
}

1;

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