/* 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: Value.cc 9462 2010-02-09 00:37:30Z gawrilow $
*/

#include <polymake.h>

namespace pm { namespace perl {

   const char* Value::frame_lower_bound()
   {
      char c=0;
      const char *flo=&c;
      return flo;
   }

   False* Value::retrieve(std::string& x) const
   {
      if (pm_perl_is_defined(sv)) {
	 size_t l;
	 const char *p=pm_perl_get_string_value(sv,&l);
	 if (p)
	    x.assign(p,l);
	 else
	    throw std::runtime_error("invalid value for an input string property");
      } else {
	 x.clear();
      }
      return 0;
   }

   False* Value::retrieve(const char* &x) const
   {
      if (pm_perl_is_defined(sv)) {
	 const char *p=pm_perl_get_string_value_nolen(sv);
	 if (p)
	    x=p;
	 else
	    throw std::runtime_error("invalid value for an input string property");
      } else {
	 x=0;
      }
      return 0;
   }

   False* Value::retrieve(char &x) const
   {
      const char *p=pm_perl_string_value(sv);
      if (p) {
	 x=*p;
      } else {
	 switch (pm_perl_number_flags(sv)) {
	 case number_is_int: {
	    int ival=pm_perl_int_value(sv);
	    if (ival>=0 && ival<=9)
	       x=ival+'0';
	    else
	       throw std::runtime_error("invalid value for an input character property");
	    break;
	 }
	 case number_is_float: {
	    double dval=pm_perl_float_value(sv);
	    if (dval>=0 && dval<=9)
	       x=static_cast<int>(dval)+'0';
	    else
	       throw std::runtime_error("invalid value for an input character property");
	    break;
	 }
	 default:
	    p=pm_perl_get_string_value_nolen(sv);
	    if (p)
	       x=*p;
	    else
	       throw std::runtime_error("invalid value for an input character property");
	 }
      }
      return 0;
   }

   False* Value::retrieve(double& x) const
   {
      switch (pm_perl_number_flags(sv)) {
      case number_is_int:
	 x=static_cast<double>(pm_perl_int_value(sv));
	 break;
      case number_is_float:
	 x=pm_perl_float_value(sv);
	 break;
      case number_is_object:
	 x=pm_perl_object_float_value(sv);
	 break;
      default:
	 if (pm_perl_get_cur_length(sv)==0)
	    x=0;
	 else
	    throw std::runtime_error("invalid value for an input floating-point property");
      }
      return 0;
   }

   False* Value::retrieve(Array& x) const
   {
      if (pm_perl_is_defined(sv)) {
	 pm_perl_set_copy(x.sv,sv);
	 x.verify();
      } else if (options & value_allow_undef) {
	 x.clear();
      } else {
	 throw undefined();
      }
      return 0;
   }

   istreambuf::istreambuf(SV *sv)
   {
      size_t l;
      char* p=pm_perl_get_string_value(sv,&l);
      setg(p,p,p+l);
   }

   ostreambuf::ostreambuf(SV *sv)
      : val(sv)
   {
      pm_perl_set_string_value(sv, "", 0);
      char *p=pm_perl_grow_string(sv,24);
      setp(p,p+23);
   }

   ostreambuf::~ostreambuf()
   {
      *pptr()='\0';
      pm_perl_set_cur_length(val,pptr()-pbase());
   }

   ostreambuf::int_type ostreambuf::overflow(int_type c)
   {
      size_t l=pptr()-pbase();
      pm_perl_set_cur_length(val,l);
      char *p=pm_perl_grow_string(val,l+513);
      setp(p,p+l+512);
      pbump(l);
      if (!traits_type::eq(c, traits_type::eof())) {
	 *pptr()=c;
	 pbump(1);
      }
      return traits_type::not_eof(c);
   }

   static inline
   SV** prepare_for_get_type()
   {
      SV **stack=pm_perl_start_funcall();
      stack=pm_perl_start_funcall_intermediate(stack);
      cached_cv self;
      self.addr=pm_perl_app_pkg_fetch("self",4);
      stack=pm_perl_call_func_scalar_intermediate(stack,&self);
      if (!stack) throw exception();
      return stack;
   }

   SV* get_type(const char *type_name, size_t tl)
   {
      SV **stack=prepare_for_get_type();
      stack=pm_perl_push_string_arg(stack,type_name,tl);
      SV *proto=pm_perl_call_method_scalar(stack,"eval_type");
      if (!proto) throw exception();
      return proto;
   }

   SV* get_type(SV *prescribed_pkg, const typeinfo& ti, SV *super_proto)
   {
      SV **stack=prepare_for_get_type();
      stack=pm_perl_push_arg(stack,prescribed_pkg);
      stack=pm_perl_push_string_arg(stack,ti.name(),0);
      if (super_proto) stack=pm_perl_push_arg(stack,super_proto);
      SV *proto=pm_perl_call_method_scalar(stack,"eval_cpp_type");
      if (!proto) throw exception();
      return proto;
   }

   SV* get_type(const char *pkg, size_t pkgl, SV** (*fill)(SV**), bool exact_match)
   {
      SV **stack=pm_perl_start_funcall();
      stack=fill(pm_perl_push_string_arg(stack,pkg,pkgl));
      if (stack) {
	 if (!exact_match) pm_perl_increase_nesting_level();
	 SV *proto=pm_perl_call_method_scalar(stack,"type");
	 if (!proto) throw exception();
	 return proto;
      } else {
	 pm_perl_cancel_funcall();
	 return 0;
      }
   }

   const char* exception::what() const throw()
   {
      return pm_perl_string_value(pm_perl_errsv());
   }

   namespace {
      const std::string undefined_what("unexpected undefined value of an input property");
   }

   undefined::undefined() : std::runtime_error(undefined_what) {}

   SV* propagate(const std::exception& ex)
   {
      if (!dynamic_cast<const exception*>(&ex))
	 pm_perl_set_string_value_nolen(pm_perl_errsv(), ex.what());
      return pm_perl_undef();
   }

   SV* propagate()
   {
      pm_perl_set_string_value_nolen(pm_perl_errsv(), "an exception occured");
      return pm_perl_undef();
   }

   SV* Unprintable::_do(const char*, const char*)
   {
      Value v;
      v.put("<UNPRINTABLE OBJECT>");
      return v.get_temp();
   }
} }
