#  Copyright (c) 1997-2010
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Darmstadt, Germany)
#  http://www.polymake.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: InteractiveViewer.pm 9931 2010-11-19 19:58:46Z gawrilow $

require Polymake::Sockets;
require Polymake::Background;

#  Stuff common for the Java-based interactive visualization engines
#  like JavaView or jReality

###########################################################################################
#
#  The central per-process instance, keeping the communication channels
#  to the background java process
#

package Polymake::InteractiveViewer;

use Polymake::Struct (
   '$name',
   '@windows',
   '@new_windows',
   '@pending_windows',
   '%windows_by_id',
   [ '$server_socket' => 'new ServerSocket' ],
   [ '$data_socket' => 'undef' ],
);


# start the launcher on the java side
sub new {
   $_[0]->instance ||= do {
      my $pkg=$_[0];
      my $self=&_new;

      ($self->name)= $pkg =~ /^($id_re)/o;

      my $jar_path = "$InstallTop/java_build";
      my $native_dir = $java_native_libs || "$InstallArch/lib/jni";
      my (@jars, @natives);
      foreach (known Core::Application) {
         if (-f (my $jar_file="$jar_path/$_.jar")) {
            push @jars, $jar_file;
            if (glob "$native_dir/lib$_.*") {
               push @natives, $_.($DebugLevel ? $ENV{POLYMAKE_DEBUG_CLIENTS} : "");
            }
         }
      }
      @jars or die "could not find any application JAR file: ",
                   $DeveloperMode ? "need to say `make all-java' ?\n" : "installation mishap?\n";
 
      my $jre_properties=$self->start_properties;
      if (@natives) {
         ( $jre_properties->{"java.library.path"}.=":$native_dir" ) =~ s/^://;
      }
      my @java_command=($java,
			(map { "-D$_=$jre_properties->{$_}" } keys %$jre_properties),
			"-cp", join(":", @jars, $self->classpath),
			"de.tuberlin.polymake.common.SelectorThread",
			$self->server_socket->port, @natives);

      new Background::Process(@java_command);
      $self->data_socket=new Watcher($self->server_socket->accept, $self);

      if ($Shell->interactive) {
	 add AtEnd($self->name, sub { if (defined (my $active=$pkg->instance)) { close($active->data_socket) } },
		   before=>"Background", ignore_multiple=>1);
      } else {
	 add AtEnd($self->name, sub { if (defined (my $active=$pkg->instance)) { while (length($active->data_socket->getline)) {} } },
		   ignore_multiple=>1);
      }
      $self;
   }
}

sub total_windows {
   my $self=shift;
   @{$self->windows}+@{$self->pending_windows}+@{$self->new_windows}
}

sub append {
   my ($self, $Vis)=@_;
   $self->new_windows->[-1]->contents->append($Vis);
}

sub run {
   my $self=shift;
   foreach my $window (splice @{$self->new_windows}) {
      $window->launch($self->data_socket);
      push @{$self->pending_windows}, $window;
   }
}

sub find {
   my ($self, $pred)=@_;
   grep { defined($_->interactive_launcher) && $pred->($_->interactive_launcher) } @{$self->windows};
}

sub shutdown {
   my $self=shift;
   undef $self->data_socket;
   undef $self->server_socket;
   undef $self->instance;
}

###########################################################################################
#
#  The helper class, enabling the deadlock-free coexistence with the interactive shell
#  and other background processes.  Does the first processing of incoming feedback messages.
#

package Polymake::InteractiveViewer::Watcher;

use Polymake::Struct (
   [ '@ISA' => 'CollaborativePipe' ],
   [ new => '$$' ],
   [ '$viewer' => 'weak( #2 )' ],	# some derived InteractiveViewer instance
);

sub alone {}

sub in_avail {
   my $self=shift;
   my $pos=length($self->rbuffer);
   if ($self->SUPER::in_avail>0) {
      while ($pos < length($self->rbuffer)) {
	 pos($self->rbuffer)=$pos;
	 if ($self->rbuffer =~ s/\G^c\s+(\S+)\n//mc) {
	    if (defined (my $wi=delete $self->viewer->windows_by_id->{$1})) {
	       my $window=$self->viewer->windows->[$wi];
	       if (defined($window->interactive_launcher)) {
		  $window->interactive_launcher->closed;
	       }
	       splice @{$self->viewer->windows}, $wi, 1;
	       $_>$wi && --$_ for values %{$self->viewer->windows_by_id};
	    } else {
	       err_print( $self->viewer->name, " tells about an unknown Window id '$1'" );
	    }
	 } elsif ($self->rbuffer =~ s/\G^n\s+(\S+)\n//mc) {
	    if (defined (my $wi=$self->viewer->windows_by_id->{$1})) {
	       my $window=$self->viewer->windows->[$wi];
	       if (defined($window->interactive_launcher)) {
		  $window->interactive_launcher->feedback($self->handle, $self->viewer, $window);
	       } else {
		  err_print( $self->viewer->name, " sends feedback from a non-interactive window $1" );
	       }
	    } elsif (defined (my $window=shift @{$self->viewer->pending_windows})) {
	       push @{$self->viewer->windows}, $window;
	       $window->id=$1;
	       $self->viewer->windows_by_id->{$1}=$#{$self->viewer->windows};
	    } else {
	       err_print( $self->viewer->name, " tells about an unknown Window id '$1'" )
	    }
	 } elsif (not $self->rbuffer =~ s/\G^x\n//mc) {
	    my $junk=$self->READLINE;  chomp $junk;
	    err_print( $self->viewer->name, " sends an unknown command '$junk'" );
	 }
      }
   }
}

sub CLOSE {
   my $self=shift;
   Pipe::CLOSE($self,1);
   $self->viewer->shutdown;
   1
}

###########################################################################################
#
#  A window for one drawing
#

package Polymake::InteractiveViewer::Window;

use Polymake::Struct (
   [ new => '$' ],
   [ '$contents' => '#1' ],			# something like JavaView::File
   '$class',					# java class controlling the window
   [ '$interactive_launcher' => 'undef' ],	# something derived from Visual::Interactive (optionally)
   '$id',					# unique ID used in the feedback messages
);

sub launch {
   my ($self, $pipe)=@_;
   my $client_port= defined($self->interactive_launcher) ? $self->interactive_launcher->run : -1;
   if ($DebugLevel>=2) {
      dbg_print( "sending data for ", $self->contents->title, "\n", $self->class, " $client_port\n", $self->contents->toString );
   }
   print $pipe "n ", $self->class, " $client_port\n", $self->contents->toString;
}

1

# Local Variables:
# cperl-indent-level:3
# End:
