#  Copyright (c) 1997-2005
#  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: Server.pm 7556 2007-01-12 17:36:36Z gawrilow $

use strict;
use namespaces;
use integer;

package Poly::Server;
use Socket;
use Fcntl;
use POSIX qw( :signal_h );
use Poly::File;
use Poly::Pipe;

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

my $sa_pipe=new POSIX::SigAction
   sub {
      die "\n";		# leave eval{} in serve()
   };
my $sa_pipe_save=new POSIX::SigAction('DEFAULT');

my $expand_unsigned=$Config::Config{use64bitint};
my $unsigned_minus_one=$expand_unsigned && (1<<32)-1;

sub serve($;$$) {
   my ($socket, $objects, $loose)=@_;
   $objects ||= [ ];
   $loose ||= [ ];

   my $sel=select;  select $socket;  $|=1;
   sigaction SIGPIPE, $sa_pipe, $sa_pipe_save;

   eval {
      local $_;

      while (<$socket>) {
	 $_ .= <$socket> while length($_)<=5;	# the packed index can occasionally contain \n
	 chomp;
	 my ($prefix, $index, $command)=unpack 'ANA4', $_;
	 $index=-1 if $expand_unsigned && $index==$unsigned_minus_one;
	 dbg_print( "client> $command (#$index)" ) if $Switches::d;

	 if ($prefix ne "\e") {
	    die "unrecognized client output: '$_'\n";
	 }

	 if ($command eq "open") {
	    my $arg=<$socket>; chomp $arg;
	    my ($file, $proto_name)=split /\s+/, $arg;
	    dbg_print( "client> file=($file) ", $proto_name ? "type=($proto_name)" : "" ) if $Switches::d;

	    eval {
	       my $proto=defined($proto_name) && do {
		  Poly::Application::prototype_lookup($proto_name)
		     or die "unknown object type $proto_name\n";
	       };
	       $objects->[$index]=load Object($file);
	       die "object stored in $file does not match the required object type ", $proto->name, "\n"
	          if $proto && !$objects->[$index]->isa($proto->object_type);
	    };
	    if ($@) {
	       print "\c@$@\n";
	    } else {
	       print "\cA";
	       $objects->[$index]->begin_transaction->keep_overwritten=1;
	    }

	 } elsif ($command eq "new_") {
	    my $arg=<$socket>; chomp $arg;
	    my ($file, $proto_name)=((split /\s+/, $arg), "default");
	    dbg_print( "client> file=($file) type=($proto_name)" ) if $Switches::d;

	    eval {
	       my $proto=Poly::Application::prototype_lookup($proto_name)
	       or die "unknown object type $proto_name\n";
	       $objects->[$index]=$proto->object_type->new(length($file) ? new File($file) : ());
	    };
	    if ($@) {
	       print "\c@$@\n";
	    } else {
	       print "\cA";
	       $objects->[$index]->begin_transaction;
	    }

	 } elsif ($index > $#$objects) {
	    die "$command: index $index out of range\n";

	 } elsif ($command eq "give") {
	    my $req=<$socket>; chomp $req;
	    dbg_print( "client> req=($req)" ) if $Switches::d;

	    my ($prop_name, @data);
	    if ($req =~ /^\#(\d+)$/) {
	       die "invalid scratch property '$req'\n" if $1>$#$loose;
	       my $data=$loose->[$1];
	       my $header=pack 'cNZ*', 1, scalar(@$data), $req;
	       print $header, @$data, "\n";
	    } else {
	       if (defined (my $pv=eval { $objects->[$index]->give_pv($req) })) {
		  my $header=pack 'cNZ*', 1, scalar($pv->toStringArray), $pv->property->name;
		  print $header, $pv->toStringArray, "\n";
	       } elsif (length($@)) {
		  print "\c@$@\n";
	       } else {
		  my $header=pack 'cNZ*', 1, 0, "";
		  print $header, "\n";
	       }
	    }

	 } elsif ($command eq "take") {
	    my $req=<$socket>; chomp $req;
	    dbg_print( "client> req=($req)" ) if $Switches::d;
	    my ($data, $comments)=read_block($socket);
	    undef $data if @$data==1 and $data->[0] =~ $Poly::PropertyValue::UNDEF_re;
	    if ($req =~ /^\#(\d+)$/) {
	       die "invalid scratch property '$req'\n" if $1>$#$loose;
	       @{$loose->[$1]}=@$data;
	    } else {
	       $objects->[$index]->take($req, $data);
	    }

	 } elsif ($command eq "look") {
	    my $req=<$socket>; chomp $req;
	    dbg_print( "client> req=($req)" ) if $Switches::d;

	    if (defined (my $pv=eval { $objects->[$index]->lookup_pv($req) })) {
	       my $header=pack 'cNZ*', 1, scalar($pv->toStringArray), $pv->property->name;
	       print $header, $pv->toStringArray, "\n";
	    } elsif (length($@)) {
	       print "\c@$@\n";
	    } else {
	       my $header=pack 'cNZ*', 1, 0, "";
	       print $header, "\n";
	    }

	 } elsif ($command eq "comm") {
	    $objects->[$index]->commit;
	    undef $objects->[$index];

	 } elsif ($command eq "roll") {
	    undef $objects->[$index];

	 } elsif ($command eq "remo") {
	    my $list=read_block_chomp($socket);
	    dbg_print( "client> req=(", join(",", @$list), ")" ) if $Switches::d;

	    foreach my $prop_name (@$list) {
	       $objects->[$index]->remove($prop_name);
	    }

	 } elsif ($command eq "defi") {
	    my $req=<$socket>; chomp $req;
	    dbg_print( "client> req=($req)" ) if $Switches::d;

	    my $answer=defined($objects->[$index]->lookup($req));
	    print pack 'C', $answer;

	 } elsif ($command eq "prov") {
	    my $list=read_block_chomp($socket);
	    dbg_print( "client> req=(", join(",", @$list), ")" ) if $Switches::d;

	    my @props=eval { $objects->[$index]->give_pv(@$list) };
	    my $answer=!$@;
	    if ($answer) {
	       foreach (@props) {
		  $answer=0, last unless defined $_;
	       }
	    }
	    print pack 'C', $answer;

	 } elsif ($command eq "exis") {
	    my $req=<$socket>; chomp $req;
	    dbg_print( "client> req=($req)" ) if $Switches::d;

	    my $answer=$objects->[$index]->exists($req);
	    print pack 'C', $answer;

	 } elsif ($command eq "type") {
	    my $type=ref($objects->[$index]);
	    $type =~ s/^Apps::\w+:://;
	    print "$type\n";

	 } elsif ($command eq "isa_") {
	    my $type=<$socket>; chomp $type;
	    dbg_print( "client> type=($type)" ) if $Switches::d;
	    my $answer=$objects->[$index]->isa($type);
	    print pack 'C', $answer;

	 } else {
	    die "unknown client command <$command>\n";
	 }
      }
   };
   select $sel;
   sigaction SIGPIPE, $sa_pipe_save;
}
####################################################################################
sub new_skp {
   my ($local, $remote);
   socketpair $local, $remote, AF_UNIX, SOCK_STREAM, PF_UNSPEC
      or die "socketpair failed: $!\n";
   fcntl $remote, F_SETFD, 0		# keep opened in the client
      or die "fcntl(F_SETFD) failed: $!\n";
   return (new Pipe($local), $remote);
}
####################################################################################
sub Modules::client {
   my $client=shift;
   my (@objects, @loose);
   my ($local, $remote)=new_skp;

   my @args=map {
      if (ref($_)) {
	 if (!is_object($_) || UNIVERSAL::isa($_,"Visual::Embedding")) {
	    # scratch property
	    push @loose, $_;
	    '#'.$#loose

	 } elsif (UNIVERSAL::isa($_,"Poly::Object")) {
	    push @objects, $_;
	    '#'.fileno($remote).".".$#objects

	 } elsif (defined (my $deref=overload::Method($_,'@{}'))) {
	    push @loose, $deref->($_);
	    '#'.$#loose

	 } else {
	    croak( "don't know how to pass an object '", ref($_), "' to a client program" );
	 }

      } elsif (defined $_) {
	 # an usual scalar
	 $_

      } else {
	 # special case for a dummy object without properties
	 '#'.fileno($remote).".-1"
      }
   } @_;

   if ($Switches::d) {
      if (defined(my $debug=$ENV{POLYMAKE_DEBUG_CLIENTS})) {
	 if ($debug =~ m{(?:^|/)valgrind\b}) {
	    unshift @args, split(/\s+/, $debug), "--suppressions=$main::InstallTop/support/test_suppressions",
	                   "--leak-check=yes", "$client-d";
	    $client=shift @args;
	 } elsif ($client =~ qr/^(?:$debug)$/o) {
	    $client.="-d";
	 }
      }
      dbg_print( "running client $client(@args)" );
   }

   my $client_pid;
   unless ($client_pid=fork()) {
      die "fork failed: $!\n" unless defined $client_pid;
      $SIG{INT}='DEFAULT';
      $SIG{ALRM}='DEFAULT';
      if ($client =~ /\bvalgrind/) {
         $ENV{GLIBCXX_FORCE_NEW}="y";
      }
      exec $client, @args
      or do {
	 print STDERR "could not run client $client: $!\n";
	 exec "false";	# avoid executing global destructors
      }
   }

   close $remote
   or die "close(socket) failed: $!\n";

   serve($local, \@objects, \@loose);
   close $local;

   waitpid $client_pid, 0;
   my $rc=$?;
   if (my $sig=$rc & 127) {
      $@ ||= "client $client killed by signal $sig\n";
   } elsif ($rc) {
      $@ ||= "client $client exited with code ". ($rc>>8) ."\n";
   }
   die $@ if $@;
   undef;
}

####################################################################################
if (exists $DB::{DB}) {
   $DB::CreateTTY=2;	# prevent tracing in forked children
}

1
