#  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: MetapostGraph.pm 7514 2006-12-15 17:58:53Z gawrilow $

use strict;
no integer;

package Metapost::File;		# siehe Visual.pm : visualize_explicit()

use Struct (
   [ new => '$;$' ],
   [ '$workfile' => '#1' ],	# 1. argument vom Constructor
   [ '$title' => '#2' ],
   '@figures',
   [ '$unnamed' => '0' ],
);


sub new_drawing {
   my ($self, $title)=@_;
   push @{$self->figures}, new figure( scalar(@{$self->figures}), $title );
   $self;
}

sub append {
   my $self = shift;
   $self->figures->[-1]->append(@_);
}


sub header {
   my ($self) = @_;
   my $who=$ENV{USER};
   my $when=localtime();
   my $title=$self->title;
   if (!length($title) && @{$self->figures}) {
      $title=$self->figures->[0]->title || "unnamed";
   }
   return <<".";
% produced by polymake for $who
% $when
% $title

verbatimtex
%&latex
\\documentclass{article}
\\usepackage{amsmath,amssymb,amsfonts}
\\begin{document}
etex

.
}

sub trailer {
   return <<".";

end
% EOF
.
}

my $global_tuning_params=<<'.';
% general
  numeric u, normalpen, mediumpen, thickpen, dotsize;
  u         := 1cm; % global unit parameter, reduce this if mpost issues '! Value is too large'
  normalpen := .5pt;
  mediumpen := 1pt;
  thickpen  := 2pt;
  dotsize   := 5pt;
.

sub toString {
   my $self=shift;
   my $preamble=$self->header;
   my $content=join("", map { $_->toString } @{$self->figures});
   my (%macros, %tuning);
   foreach my $fig (@{$self->figures}) {
      foreach (@{$fig->elements}) {
         push %macros, %{$_->macros};
	 push %tuning, %{$_->tuning};
      }
   }
   $tuning{default} ||= $global_tuning_params;
   if (keys %tuning) {
      $preamble .= "% tuning parameters\n" . join("\n", values %tuning) . "% end of tuning parameters\n\n";
   }
   if (keys %macros) {
      $preamble .= "% macro definitions\n" . join("\n", values %macros) . "% end of macro definitions\n\n";
   }
   $preamble . $content . $self->trailer;
}

sub print_it {
   my ($self) = @_;
   open my $file, ">".$self->workfile
      or die ref($self), "::print_it: could not write file ", $self->workfile, ": $!\n";
   print $file $self->toString;
   close $file;
}

##############################################################################################
#
#  A Metapost figure
#
package Metapost::figure;

use Struct (
   [ new => '$;$' ],
   '@elements',
   [ '$id' => '#1' ],
   [ '$title' => '#2' ],
);

sub append {
   my ($self, $el)=@_;
   push @{$self->elements}, $el;
   $self->title ||= $el->name;
   $self;
}

sub header {
   my ($self)=@_;
   my $title=$self->title || "anonymous figure";
   my $id=$self->id;
   return <<".";
beginfig($id); % $title
  save p, point_thickness;
  pair p[];    % global point array
  numeric point_thickness[];
.
}

sub trailer
{
   return <<".";
endfig;

.
}

sub toString
{
   my $self=shift;
   my $text=$self->header;
   my $n_points=0;
   foreach my $el (@{$self->elements}) {
      # we suppose all elements are derived from Metapost::element
      $text .= "% point coordinates for ".$el->source->Name."\n" . $el->pointArray($n_points);
      $n_points += @{$el->source->Vertices};
   }
   $n_points=0;
   foreach my $el (@{$self->elements}) {
      $text .= "% elements for ".$el->source->Name."\n" . $el->toString($n_points);
      $n_points += @{$el->source->Vertices};
   }
   return $text . $self->trailer;
}


##############################################################################################
#
#  Basis class for all figure objects handled by Metapost
#
package Metapost::element;

use Struct (
   [ new => '$' ],
   [ '$source' => '#1' ],
   '%macros',
   '%tuning',
);

sub pointArray {
   my ($self, $start_index)=@_;
   my $text="";
   my $k=$start_index;
   my $style=$self->source->VertexStyle;
   foreach (@{$self->source->Vertices}) {
      my $point=$_; chomp $point;
      my ($x,$y)=split /\s+/, $point;
      my $set_r="";
      if (is_code($style)) {
	 my $style=$style->($k-$start_index);
	 if ($style =~ $Visual::thickness_re) {
	    $set_r="  point_thickness[$k] := $1*dotsize;"
	 } elsif ($style =~ $Visual::hidden_re) {
	    $set_r="  point_thickness[$k] := 0;"
	 }
      }
      $text .= "  p[$k] := ($x*u,$y*u);$set_r\n";
      ++$k;
   }
   return $text;
}

# => !fill, "withcolor (R,G,B)"
sub parse_color {
   if (defined $_[0]) {
      my @c=Visual::parse_color($_[0]);
      ( $c[0], "withcolor (".join(",", map {$_/255} @c[2..4]).")" );
   } else {
      ();
   }
}

my $circle_macro=<<'.';
  def point_circle(expr i) =
    begingroup
    save c; path c;
    c := fullcircle scaled(if known point_thickness[i]: point_thickness[i] else: point_thickness.dflt fi) shifted p[i];
    unfill c;
    c
    endgroup
  enddef;
.

my $arrow_tuning=<<'.';
  numeric arrow_w, arrow_l;
  arrow_w := 2pt;   % half width
  arrow_l := 8pt;   % length
.
my $graph_arrow_macro=<<'.';
  path parrow;
  parrow = origin -- (-arrow_l,-arrow_w){dir 60} .. {dir 120}(-arrow_l,arrow_w) -- cycle;
  def arrow(expr i,j) =
    parrow shifted((if known point_thickness[j]: -point_thickness[j] else: -point_thickness.dflt fi, 0)/2) rotated angle(p[j]-p[i]) shifted p[j]
  enddef;
.

sub toString {
   my ($self, $start_index)=@_;
   my $name=$self->source->Name;
   my $style=$self->source->VertexStyle;
   if (is_code($style) || $style !~ $Visual::hidden_re) {
      my $preamble="";
      my $text=<<".";
% vertices for $name
.
      my $color=$self->source->VertexColor;
      my $circle;
      my $labels=$self->source->VertexLabels;
      my $last_point=$start_index+$#{$self->source->Vertices};
      my @options;

      if (!is_code($color)) {
	 ($circle, $color)=parse_color($color);
	 push @options, $color || "withcolor black";
	 if ($circle) {
	    $self->macros->{"vertex:circle"} ||= $circle_macro;
	 }
      }
      if (!is_code($style)) {
	 $preamble .= "  point_thickness.dflt := " . ($style =~ $Visual::thickness_re && "$1*") . "dotsize;\n";
	 push @options, "withpen pencircle scaled " . ($circle ? "normalpen" : "point_thickness.dflt");
      } else {
	 $preamble .= "  point_thickness.dflt := dotsize;\n";
      }
      $text .= "  drawoptions(@options);\n" if @options;

      if (!is_code($style) && !is_code($color)) {
	 # all points share the same color and style - can generate a MetaPost loop here
	 my $draw= $circle ? "draw point_circle(i)" : "drawdot p[i]";
	 $text .= <<".";
  for i=$start_index upto $last_point:
    $draw;
  endfor;
.
	 # put the labels
	 if ($labels) {
            $text .= <<".";
% vertex labels for $name
  drawoptions(withcolor black);
.
            for (my $i=$start_index; $i<=$last_point; ++$i) {
	       $text .= "  label.lrt(\"".$labels->($i-$start_index)."\", p[$i]);\n";
	    }
         }

      } else {
	 # points have different styles and/or colors - must generate separate statements
	 my $label_text="";
	 for (my $i=$start_index; $i<=$last_point; ++$i) {
	    my $style= is_code($style) && $style->($i-$start_index);
	    if ($style !~ $Visual::hidden_re) {
	       @options=();
	       if (is_code($color)) {
 		  ($circle, @options)=parse_color($color->($i-$start_index));
	       }
	       if ($circle) {
		  $self->macros->{"vertex:circle"} ||= $circle_macro;
		  $text .= "  draw point_circle($i)@options;\n";
	       } else {
		  push @options, "withpen pencircle scaled " . ($style =~ $Visual::thickness_re ? "point_thickness[$i]" : "point_thickness.dflt");
		  $text .= "  drawdot p[$i]@options;\n";
	       }
	       if ($labels) {
		  $label_text .= "  label.lrt(\"".$labels->($i-$start_index)."\", p[$i]);\n";
	       }
	    }
	 }
	 if ($label_text) {
	    $text .= <<".";
% vertex labels for $name
  drawoptions(withcolor black);
$label_text
.
	 }
      }
      wantarray ? ($preamble, $text) : $preamble.$text;

   } else {
      "% nodes for $name are hidden\n"
   }
}

##############################################################################################
#
#  Graph
#

package Metapost::graph;
use Struct (
   [ '@ISA' => 'Metapost::element' ],
);

sub toString
{
   my ($self, $start_index)=@_;
   my $name=$self->source->Name;
   my $style=$self->source->EdgeStyle;
   if (is_code($style) || $style !~ $Visual::hidden_re) {
      my $preamble="";
      my $text="% edges for $name\n";

      my $scale="normalpen";
      if (!is_code($style) && $style =~ $Visual::thickness_re) {
	 $scale="(1*normalpen)";
      }
      my @options="withpen pencircle scaled $scale";

      my $color=$self->source->EdgeColor;
      if (!is_code($color)) {
	 push @options, parse_color($color) || "withcolor black";
      }
      $text .= "  drawoptions(@options);\n";

      my $arrow=$self->source->Directed;
      if ($arrow) {
	 $self->macros->{"graph:arrow"} ||= $graph_arrow_macro;
	 $self->tuning->{"graph:arrow"} ||= $arrow_tuning;
      }

      for (my $edge=$self->source->edges; $edge; ++$edge) {
	 my ($s,$t)=$edge->incident_nodes;
	 $s+=$start_index; $t+=$start_index;
	 @options=();
	 my @coloroptions=();

	 if (my $style= is_code($style) && $style->($edge)) {
	    if ($style =~ $Visual::thickness_re) {
	       push @options, "withpen pencircle scaled($1*normalpen)";
	    }
	 }
	 if (my $color= is_code($color) && parse_color($color->($edge))) {
	    push @options, $color;
	    push @coloroptions, $color;
	 }
	 $text .= <<".";
  draw p[$s]--p[$t]@options;
.
	 if ($arrow) {
	    $text .= <<".";
  fill arrow($s,$t)@coloroptions;
.
	 }
      }

      my ($p_preamble, $p_text)=$self->SUPER::toString($start_index);
      wantarray ? ($p_preamble.$preamble, $text.$p_text) : $p_preamble.$preamble.$text.$p_text;

   } else {
      "% edges for $name are hidden\n" . $self->SUPER::toString($start_index);
   }
}

##############################################################################################
#
#  Face Lattice
#

package Metapost::lattice;
use Struct (
   [ '@ISA' => 'Metapost::element' ],
);

my $lattice_node_macro=<<'.';
  numeric x_k;
  def max_k_row(expr ifirst)(text ilist) =
    begingroup
    save i, j, max_k; numeric i, k, max_k;
    i := ifirst;
    max_k := 0;
    for j=ilist:
      k := (2*label_gap+node_gap*u+xpart(urcorner node_label[i] - center node_label[i] + center node_label[j] - ulcorner node_label[j]))/xpart(p[j]-p[i]);
      max_k := max(max_k, k);
      i := j;
    endfor
    max_k
    endgroup
  enddef;
  def scale_nodes(expr b,e) =
    for i=b upto e:
      p[i] := (x_k*xpart p[i], u*y_stretch*ypart p[i]);
    endfor
  enddef;
  def lattice_node(expr i,border_c,fill_c) =
    begingroup
    save b, d; path b; pair d;
    b := (ulcorner node_label[i]+(-label_gap,label_gap)) -- (urcorner node_label[i]+(label_gap,label_gap)) --
         (lrcorner node_label[i]+(label_gap,-label_gap)) -- (llcorner node_label[i]+(-label_gap,-label_gap)) -- cycle;
    d := p[i] - center node_label[i];
    fill b shifted d withcolor fill_c;
    draw b shifted d withcolor border_c withpen pencircle scaled(if known point_thickness[i]: point_thickness[i] else: point_thickness.dflt fi);
    draw node_label[i] shifted d withcolor black;
    endgroup
  enddef;
.
my $lattice_arrow_macro=<<'.';
  def lattice_arrow(expr i,j) =
    begingroup
    save a; numeric a;
    a := angle(p[j]-p[i]);
    parrow shifted(-(0.5*ypart(urcorner node_label[j] - lrcorner node_label[j])+label_gap)/abs(sind(a)), 0) rotated a shifted p[j]
    endgroup
  enddef;
.

# => border_color, fill_color
sub parse_node_color {
   if (defined $_[0]) {
      my @c=Visual::parse_color($_[0]);
      my $c="(".join(",", map {$_/255} @c[2..4]).")";
      if ($c[0]) {
	 ($c, "white")
      } else {
	 ( "black", $c )
      }
   } else {
      ( "black", "white" )
   }
}

sub pointArray {
   my ($self, $start_index)=@_;
   $self->tuning->{"lattice"} ||= <<'.';
% for face lattice drawings
  numeric y_stretch, label_gap;
  y_stretch := 5;       % vertical stretch
  label_gap := 3pt;     % between label text and surrounding box
  node_gap  := 0.25;    % between two nodes (relative to u)
.
   my $text=<<".";
  save node_label;
  picture node_label[];
.
   my $styles=$self->source->VertexStyle;
   if (is_object(my $embedding=$self->source->Coord)) {
      my @label_width=map {
	 # label spacing: twice char width between the boxes, half char width between border and label
	 length($self->source->VertexLabels->($_))+3
      } 0..$self->source->get_number_nodes-1;
      $embedding->splice(2, [ "@label_width\n" ], $self->source->Mode, undef);
   }

   my $last_point=$start_index+$#{$self->source->Vertices};
   my %sorted_by_y;

   for (my $k=$start_index; $k<=$last_point; ++$k) {
      my $point=$self->source->Coord->[$k-$start_index]; chomp $point;
      my ($x,$y)=split /\s+/, $point;
      my $set_r="";
      if (is_code($styles)) {
	 my $style=$styles->($k-$start_index);
	 if ($style =~ $Visual::thickness_re) {
	    $set_r="  point_thickness[$k] := $1*normalpen;"
	 }
      }
      my $label=$self->source->VertexLabels->($k-$start_index);
      $text .= <<".";
  node_label[$k] := thelabel("$label", origin);
  p[$k] := ($x,$y);$set_r
.
      push @{$sorted_by_y{$y}}, [ $x, $k ];
   }

   $text .= "  x_k := max( "
          . join(",\n              ",
		 map {
		    "max_k_row(" . join(",", map { $_->[1] } sort { $a->[0] <=> $b->[0] } @$_) . ")"
		 } grep { $#$_>0 } values %sorted_by_y)
	  . " );\n  scale_nodes($start_index,$last_point);\n\n";

   $self->macros->{"lattice:node"} ||= $lattice_node_macro;
   return $text;
}

sub toString
{
   my ($self, $start_index)=@_;
   my $name=$self->source->Name;
   my $styles=$self->source->EdgeStyle;
   my $preamble="";
   my $text="% edges for $name\n";

   my $scale="normalpen";
   if (!is_code($styles) && $styles =~ $Visual::thickness_re) {
      $scale="($1*normalpen)";
   }
   my @options="withpen pencircle scaled $scale";

   my $colors=$self->source->EdgeColor;
   if (!is_code($colors)) {
      push @options, parse_color($colors) || "withcolor black";
   }
   $text .= "  drawoptions(@options);\n";

   my $arrows=$self->source->ArrowStyle;
   if ($arrows) {
      $self->macros->{"graph:arrow"} ||= $graph_arrow_macro;
      $self->tuning->{"graph:arrow"} ||= $arrow_tuning;
      $self->macros->{"lattice:arrow"} ||= $lattice_arrow_macro;
   }

   for (my $edge=$self->source->edges; $edge; ++$edge) {
      my ($s,$t)=$edge->incident_nodes;
      $s+=$start_index; $t+=$start_index;
      @options=();
      my @coloroptions=();

      if (my $style= is_code($styles) && $styles->($edge)) {
	 next if $style =~ $Visual::hidden_re;
	 if ($style =~ $Visual::thickness_re) {
	    push @options, "withpen pencircle scaled($1*normalpen)"
	 }
      }
      if (my $color= is_code($colors) && parse_color($colors->($edge))) {
	 push @options, $color;
	 push @coloroptions, $color;
      }
      $text .= <<".";
  draw p[$s]--p[$t]@options;
.
      if ($arrows) {
	 my $arrow= is_code($arrows) ? $arrows->($edge) : $arrows;
	 if ($arrow>0) {
	    $text .= <<".";
  fill lattice_arrow($s,$t)@coloroptions;
.
	 } elsif ($arrow<0) {
	    $text .= <<".";
  fill lattice_arrow($t,$s)@coloroptions;
.
	 }
      }
   }

   $styles=$self->source->VertexStyle;
   $colors=$self->source->VertexColor;
   $text .= <<".";
% nodes for $name
.
   my $last_point=$start_index+$#{$self->source->Vertices};
   if (!is_code($styles)) {
      $preamble .= "  point_thickness.dflt := " . ($styles =~ $Visual::thickness_re && "$1*") . "normalpen;\n";
   } else {
      $preamble .= "  point_thickness.dflt := normalpen;\n";
   }
   if (!is_code($colors)) {
      # all nodes share the same color - can generate a MetaPost loop here

      my ($border, $color)=parse_node_color($colors);
      my ($first, $last)=($start_index, $last_point);
      if (is_code($styles)) {
	 # currently only the topmost or the bottommost node are occasionally hidden
	 ++$first if $styles->($first-$start_index) =~ $Visual::hidden_re;
	 --$last if $styles->($last-$start_index) =~ $Visual::hidden_re;
      }
      $text .= <<".";
  for i=$first upto $last:
    lattice_node(i,$border,$color);
  endfor;
.

   } else {
      # points have different colors - must generate separate statements
      for (my $i=$start_index; $i<=$last_point; ++$i) {
	 next if is_code($styles) && $styles->($i-$start_index) =~ $Visual::hidden_re;
	 my ($border, $color)=parse_node_color($colors->($i-$start_index));
	 $text .= <<".";
  lattice_node($i,$border,$color);
.
      }
   }

   wantarray ? ($preamble, $text) : $preamble.$text;
}

1

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