/* 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.
*/

#ident "$Project: polymake $$Id: Polybuf.cc,v 1.8 2006/01/19 14:21:20 gawrilow Exp $"

#if defined(__GNUC__) && __GNUC__==3 && __GNUC_MINOR__<3 && !defined(__APPLE__) && !defined(__COMO__)
#pragma implementation
#endif

#include <Polybuf.h>
#include <CharBuffer.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <cstdio>

namespace {
   const char comm_err[]="Poly: communication error";
}

namespace pm { namespace client {

void streambuf::send_cmd (int id, const char *cmd, bool immed)
{
   last_nl=0;
   uint32_t id_net=htonl(id);
   if ((sputc('\033') == ctraits::eof()
	|| sputn((char*)&id_net, sizeof(id)) != sizeof(id)
	|| sputn(cmd, 4) != 4
	|| sputc('\n') == ctraits::eof()
	|| (immed && sync()<0) )
       && !std::uncaught_exception())
      throw std::runtime_error(comm_err);
}

void streambuf::send_cmd (int id, const char *cmd, const char *arg, bool immed)
{
   send_cmd(id, cmd, false);
   int l=strlen(arg);
   if (sputn(arg, l) != l
       || sputc('\n') == ctraits::eof()
       || (immed && sync()<0) )
      throw std::runtime_error(comm_err);
}

void streambuf::send_arg (const char *arg)
{
   if (arg) {
      int l=strlen(arg);
      if (sputn(arg, l) != l
	  || sputc('\n') == ctraits::eof())
	 throw std::runtime_error(comm_err);
   } else {
      if (sputc('\n') == ctraits::eof()
	  || sync()<0)
      throw std::runtime_error(comm_err);
   }
}

streambuf::int_type streambuf::underflow()
{
   const int_type eof_char=ctraits::eof();
   if (last_nl >= 2) return eof_char;

   int_type c=std::socketbuf::underflow();
   if (c != eof_char) {
      char *start=eback(), *last=egptr()-1;
      if (last == start) {
	 if (*start=='\n') {
	    if (++last_nl >= 2) {
	       setg(start, start, start);
	       c=eof_char;
	    }
	 } else {
	    last_nl=0;
	 }
      } else {
	 last_nl=0;
	 if (*last == '\n') {
	    ++last_nl;
	    if (last[-1] == '\n') {
	       ++last_nl;
	       setg(start, gptr(), last);
	    }
	 }
      }
   }
   return c;
}

void streambuf::check_exception()
{
   if (!sbumpc()) {
      int i=CharBuffer::find_char_forward(this, '\n', 0);
      if (i>=0) {
	 std::string err_txt(gptr(), i);
	 gbump(i+1);
	 throw std::runtime_error("Poly: " + err_txt);
      }
      throw std::runtime_error(comm_err);
   }
}

void streambuf::start_input()
{
   uint32_t lines_net;
   int end_of_name=CharBuffer::find_char_forward(this, '\0', sizeof(lines_net));
   if (end_of_name<0)
      throw std::runtime_error(comm_err);
   name.assign(gptr()+sizeof(lines_net), end_of_name-sizeof(lines_net));
   memcpy(&lines_net, gptr(), sizeof(lines_net));
   gbump(end_of_name+1);
   _lines=ntohl(lines_net);
   if (_lines == 0) {
      sbumpc();		// hide the trailing \n in an empty section by consuming it
      last_nl=2;
   }
}

void streambuf::clear_input()
{
   socketbuf::input_limit=0;
   char *b=eback();
   while (last_nl != 2) {
      int l=read(_fd, b, bufsize);
      if (l<=0) break;
      if (l==1) {
	 if (*b == '\n') ++last_nl;
	 else last_nl=0;
      } else {
	 last_nl=0;
	 if (b[l-1] == '\n') {
	    ++last_nl;
	    if (b[l-2] == '\n') ++last_nl;
	 }
      }
   }
   setg(b,b,b);
}

void streambuf::finish_output()
{
   char *p=pptr();
   if ((p != pbase() && p[-1] != '\n' && sputc('\n') == ctraits::eof()
	|| sputc('\n') == ctraits::eof())
       && !std::uncaught_exception())
	 throw std::runtime_error(comm_err);
   sync();
}

void streambuf_holder::set_options(const char *options)
{
   if (object_cnt >= 0)
      throw std::logic_error("too late to set server options");
   server_options=options;
}

int streambuf_holder::new_object (const char* object_arg, bool exists, const char *obj_type)
{
   if (object_arg && *object_arg == '#') {
      // expects an argument built in perl Poly::client(): '#<fd>.<objnum>'
      char *fd_end=0;
      int fd=std::strtol(object_arg+1, &fd_end, 10);
      if (fd_end > object_arg+1 && *fd_end == '.') {

	 if (buf) {
	    if (fd != buf->fd())
	       throw std::runtime_error("Poly: multiple sockets opened in one client");
	 } else {
	    buf=new streambuf(fd);
	 }

	 char *id_end=0;
	 int id=std::strtol(fd_end+1, &id_end, 10);
	 if (id_end==fd_end+1 || *id_end)
	    throw std::runtime_error("Poly: invalid object id");
	 if (id>object_cnt) object_cnt=id;
	 return id;
      }
   }

   if (!buf) {
      int skp[2];
      if (socketpair(AF_UNIX,SOCK_STREAM,0,skp))
	 throw std::runtime_error("Poly: socketpair() failed");
      server=fork();
      if (server<0)
	 throw std::runtime_error("Poly: fork() failed");
      if (!server) {
	 // child branch
	 close(skp[0]);
	 char fd[10];
	 std::sprintf(fd,"%d",skp[1]);

	 if (server_options) {
	    const char switches_name[]="POLYMAKE_SWITCHES";
	    int l=sizeof(switches_name)+strlen(server_options)+1;
	    const char *switches_var=getenv(switches_name);
	    if (switches_var) l+=strlen(switches_var)+1;
	    char *newsw=new char[l];
	    if (switches_var)
	       std::sprintf(newsw,"%s=%s %s", switches_name, switches_var, server_options);
	    else
	       std::sprintf(newsw,"%s=%s", switches_name, server_options);
	    putenv(newsw);
	 }

	 execlp("polymake", "polymake", "--server", fd, NULL);
	 std::cerr << "Poly: exec(polymake --server) failed" << std::endl;
	 exit(1);
      }

      // parent branch goes on here
      close(skp[1]);
      buf=new streambuf(skp[0]);
   }

   int id=++object_cnt;

   if (exists && !object_arg)
      throw std::runtime_error("Poly: an unnamed temporary object must be created in 'trunc' mode");

   std::string arg=object_arg ? object_arg : "";
   if (obj_type) (arg += " ") += obj_type;
   buf->send_cmd(id, exists ? "open" : "new_", arg.c_str());
   buf->check_exception();
   return id;
}

void streambuf_holder::delete_object (int id)
{
   if (!client_mode())
      buf->send_cmd(id, std::uncaught_exception() ? "roll" : "comm");
}

streambuf_holder* streambuf_holder::inst()
{
   static streambuf_holder h;
   return &h;
}

streambuf_holder::~streambuf_holder()
{
   delete buf;
   if (server) waitpid(server,0,0);
}

} } // end namespace pm::client

