/* Copyright (c) 1997-2004
   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: Poly.cc,v 1.55 2004/12/09 14:11:22 gawrilow Exp $"

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

#include <Poly.h>
#include <Polybuf.h>
#include <Rational.h>

namespace pm { namespace client {

void Object::init(const char *object_arg, std::ios::openmode mode_arg, const char *obj_type)
{
   if (mode
       || !mode_arg
       || (mode_arg & ~(std::ios::in|std::ios::out|std::ios::trunc))
       || (mode_arg & (std::ios::out|std::ios::trunc)) == std::ios::trunc)
      throw std::logic_error("Poly: invalid open mode");

   mode=mode_arg;
   id=streambuf_holder::inst()->new_object(object_arg, !(mode_arg & std::ios::trunc), obj_type);
}

Object::~Object()
{
   if (mode)
      streambuf_holder::inst()->delete_object(id);
}

std::iostream Object::stream_inst(0);

void Object::set_options(const char* opt_arg)
{
   streambuf_holder::inst()->set_options(opt_arg);
}

Object::istream_germ Object::give (const char *req)
{
   if (!(mode & std::ios::in))
      throw std::logic_error("Poly::give() in write-only mode");

   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "give", req);
   mybuf->check_exception();
   mybuf->start_input();
   return istream_germ(mybuf);
}

Object::istream_germ Object::lookup (const char *req)
{
   if (!(mode & std::ios::in))
      throw std::logic_error("Poly::lookup() in write-only mode");

   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "look", req);
   mybuf->check_exception();
   mybuf->start_input();
   return istream_germ(mybuf, std::ios::badbit);
}

Object::ostream_germ Object::take (const char *req)
{
   if (!(mode & std::ios::out))
      throw std::logic_error("Poly::take() in read-only mode");

   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "take", req, false);
   return ostream_germ(mybuf);
}

Object::istream_germ::istream_germ(streambuf *buf)
   : super(Object::stream_inst)
{
   is->rdbuf(buf);
   is->exceptions(is->failbit | is->badbit);
}

Object::istream_germ::istream_germ(streambuf *buf, std::ios::iostate exc_flags)
   : super(Object::stream_inst)
{
   is->rdbuf(buf);
   is->exceptions(exc_flags);
   if (buf->name.empty()) is->setstate((is->failbit | is->eofbit) & ~exc_flags);
}

Object::ostream_germ::ostream_germ(streambuf *buf)
   : super(Object::stream_inst)
{
   os->rdbuf(buf);
   os->precision(20);
   os->exceptions(os->failbit | os->badbit);
}

static const char undefined_token[]= "==UNDEF==\n";

void Object::ostream_germ::operator<< (const undefined&)
{
   os->write(undefined_token, sizeof(undefined_token)-1);
}

Object::istream_germ::istream_germ(const istream_germ& other)
   : super(other)
{
   const_cast<istream_germ&>(other).is=0;
}

Object::istream_germ::~istream_germ()
{
   if (is) {
      is->exceptions(is->goodbit);
      streambuf *mybuf=(streambuf*)is->rdbuf(0);
      mybuf->clear_input();
   }
}

Object::ostream_germ::ostream_germ(const ostream_germ& other)
   : super(other)
{
   const_cast<ostream_germ&>(other).os=0;
}

Object::ostream_germ::~ostream_germ()
{
   if (os) {
      os->exceptions(os->goodbit);
      streambuf *mybuf=(streambuf*)os->rdbuf(0);
      mybuf->finish_output();
   }
}

const std::string& Object::given() const {
   return streambuf_holder::get()->name;
}

bool Object::exists (const char *req)
{
   if (!(mode & std::ios::in))
      throw std::logic_error("Poly::exists() in write-only mode");

   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "exis", req);
   return mybuf->sbumpc();
}

bool Object::defined (const char *req)
{
   if (!(mode & std::ios::in))
      throw std::logic_error("Poly::defined() in write-only mode");

   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "defi", req);
   return mybuf->sbumpc();
}

bool Object::isa (const char *type)
{
   if (!mode) return false;
   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "isa_", type);
   return mybuf->sbumpc();
}

std::string Object::type()
{
   std::string t;
   if (mode) {
      streambuf *mybuf=streambuf_holder::get();
      mybuf->send_cmd(id, "type");
      mybuf->get_string(t, '\n');
   }
   return t;
}

bool Object::vprovide (const char *req, va_list sections)
{
   if (streambuf_holder::inst()->client_mode())
      return true;
   if (!(mode & std::ios::in))
      throw std::logic_error("Poly::provide() in write-only mode");

   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "prov", req, sections);
   return mybuf->sbumpc();
}

void Object::vremove (const char *req, va_list sections)
{
   if (mode & (std::ios::in|std::ios::out) != (std::ios::in|std::ios::out))
      throw std::logic_error("Poly::remove() in read-only or write-only mode");

   streambuf *mybuf=streambuf_holder::get();
   mybuf->send_cmd(id, "remo", req, sections);
}

int istream_common::count_words()
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   if (mybuf->skip_ws()<0) return 0;
   int cnt=0, offset=0;
   do {
      offset=mybuf->next_ws(offset+1);
      ++cnt;
   } while (offset>0  &&  mybuf->seek_forward(offset) != '\n'  &&  (offset=mybuf->next_non_ws(offset+1)) > 0);
   return cnt;
}

int istream_common::count_lines()
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   if (!mybuf->end_of_seek) return mybuf->lines;
   if (mybuf->skip_ws()<0) return 0;
   return mybuf->count_char('\n');
}

int istream_common::count_braced(char opening, char closing)
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   if (mybuf->skip_ws()<0) return 0;
   int cnt=0, offset=0;
   do {
      if (mybuf->gptr()[offset] != opening) {
	 is->setstate(is->failbit);
	 return 0;
      }
      offset=mybuf->matching_brace(opening, closing, offset+1);
      if (offset<0) {
	 is->setstate(is->failbit);
	 return 0;
      }
      ++cnt;
   } while ((offset=mybuf->next_non_ws(offset+1)) > 0);
   return cnt;
}

int istream_common::count_leading(char c)
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   int cnt=0, offset=-1;
   for (;;) {
      if ((offset=mybuf->next_non_ws(offset+1)) < 0) {
	 is->setstate(is->failbit);
	 return 0;
      }
      if (mybuf->gptr()[offset] != c) break;
      ++cnt;
   }
   return cnt;
}

int istream_common::get_dim()
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   char *cur=mybuf->gptr(), *last=mybuf->end_of_seek;
   if (!last) {
      int endl=mybuf->find_char_forward('\n');
      if (endl<0) {
	 is->setstate(is->failbit);
	 return 0;
      }
      last=cur+endl;
   }

   char *closing=0;
   for (;;) {
      if (--last <= cur) {
	 is->setstate(is->failbit);
	 return 0;
      }
      if (!isspace(*last)) {
	 if (closing) {
	    if (!isdigit(*last)) {
	       if (*last == '(') {
		  break;
	       } else {
		  is->setstate(is->failbit);
		  return 0;
	       }
	    }
	 } else {
	    if (*last == ')') {
	       closing=last;
	    } else {
	       is->setstate(is->failbit);
	       return 0;
	    }
	 }
      }
   }

   *closing=0;
   int dim=atoi(last+1);
   *closing=')';
   return dim;
}

bool istream_common::at_end()
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   return mybuf->skip_ws()<0;
}

int istream_common::skip_item()
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   if (mybuf->skip_ws()<0) return -1;

   int offset=0;
   switch (mybuf->sbumpc()) {
   case '<':
      offset=mybuf->matching_brace('<', '>');  break;
   case '(':
      offset=mybuf->matching_brace('(', ')');  break;
   case '{':
      offset=mybuf->matching_brace('{', '}');  break;
   default:
      offset=mybuf->next_ws(0,false);
   }
   if (offset<0) {
      mybuf->skip_all();
      return -1;
   } else {
      mybuf->gbump(offset+1);
      return 0;
   }
}

void istream_common::set_range(char opening, char closing)
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   if (!opening && mybuf->lines<=1) return;

   if (mybuf->skip_ws()<0) {
      is->setstate(is->failbit | is->eofbit);
      return;
   }

   int offset;
   if (closing == '\n') {
      offset=mybuf->find_char_forward('\n');
      if (offset<0) return;
      ++offset;
   } else {
      if (*mybuf->gptr() != opening) {
	 is->setstate(is->failbit);
	 return;
      }
      mybuf->gbump(1);
      offset=mybuf->matching_brace(opening, closing);
      if (offset<0) {
	 is->setstate(is->failbit);
	 return;
      }
   }

   end_of_seek=mybuf->end_of_seek;
   end_of_data=mybuf->egptr();
   mybuf->setg(mybuf->eback(), mybuf->gptr(), (mybuf->end_of_seek = mybuf->gptr()+offset));
}

void istream_common::discard_range(char closing)
{
   streambuf *mybuf=(streambuf*)is->rdbuf();
   if (is->eof())
      is->clear();
   else if (mybuf->skip_ws() >= 0 ||
	    mybuf->gptr() != mybuf->end_of_seek)
      is->setstate(is->failbit);
   if (is->good() && closing != '\n')
      mybuf->gbump(1);
}

istream_common::~istream_common()
{
   if (end_of_data) {
      streambuf *mybuf=(streambuf*)is->rdbuf();
      mybuf->setg(mybuf->eback(), mybuf->gptr(), end_of_data);
      mybuf->end_of_seek=end_of_seek;
   }
}

void istream_common::get_scalar(Rational& x)
{
   static std::string text;
   if (*is >> text) {
      if (text.find_first_of(".eE") != std::string::npos) {
	 x=atof(text.c_str());
      } else {
	 x.set(text.c_str());
      }
   }
}

void istream_common::get_scalar(double& x)
{
   static std::string text;
   if (*is >> text) {
      if (text.find('/') != std::string::npos) {
	 x=Rational(text.c_str());
      } else {
	 char *end;
	 x=strtod(text.c_str(),&end);
	 if (*end) is->setstate(is->failbit);
      }
   }
}

void istream_common::get_string (std::string& s, char delim)
{
   if (((streambuf*)is->rdbuf())->get_string(s,delim) < 0)
	 is->setstate(is->eofbit | is->failbit);
}

} } // end namespace pm::client
