/* 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: calls.cc 10007 2011-01-05 16:39:38Z gawrilow $
*/

#include "polymake/perl/glue.h"
#include "polymake/perl/calls.h"

namespace pm { namespace perl {

static glue::cached_cv
   load_data_cv={ "Polymake::User::load_data", 0 },
   save_data_cv={ "Polymake::User::save_data", 0 };

SV* PropertyValue::_load_data(const std::string& filename)
{
   dTHX;
   PmStartFuncall;
   mXPUSHp(filename.c_str(), filename.size());
   PUTBACK;
   return glue::call_func_scalar(aTHX_ load_data_cv);
}

void PropertyValue::_save_data(const std::string& filename, const std::string& description)
{
   dTHX;
   PmStartFuncall;
   XPUSHs(sv);
   mXPUSHp(filename.c_str(), filename.size());
   mXPUSHp(description.c_str(), description.size());
   PUTBACK;
   glue::call_func_void(aTHX_ save_data_cv);
}

FunCall::FunCall() : Stack(true,1)
{
   dTHXa(pi);
   func=&PL_sv_undef;
}

void FunCall::prepare_function_call(const char* name, size_t nl)
{
   dTHXa(pi);
   PmStartFuncall;
   SP=glue::push_current_application(aTHX_ SP);
   SV* const app=POPs;
   PUTBACK;
   if ((func=(SV*)pm_perl_namespace_lookup_sub(aTHX_ glue::User_stash, name, nl, (CV*)SvRV(PmArray(app)[glue::Application_eval_expr_index]))) == NULL) {
      PmCancelFuncall;
      throw std::runtime_error(std::string("polymake function ") + name + " not found");
   }
}

void FunCall::push_arg_list(SV *args) const
{
   dTHXa(pi);
   dSP;
   AV* const av=(AV*)SvRV(args);
   int n=AvFILL(av)+1;
   EXTEND(SP, n);
   AvREAL_off(av);
   for (int i=0; i<n; ++i)
      PUSHs(sv_2mortal(AvARRAY(av)[i]));
   PUTBACK;
}

SV* FunCall::call() const
{
   // all arguments are already on the stack
   dTHXa(pi);
   SV *sv=func;  func=NULL;
   return glue::call_func_scalar(aTHX_ sv);
}

int FunCall::list_call() const
{
   // all arguments are already on the stack
   dTHXa(pi);
   SV *sv=func;  func=NULL;
   return glue::call_func_list(aTHX_ sv);
}

void FunCall::void_evaluate() const
{
   // all arguments are already on the stack
   dTHXa(pi);
   SV *sv=func;  func=NULL;
   glue::call_func_void(aTHX_ sv);
}

SV* FunCall::evaluate_method(SV* obj, const char* name) const
{
   // all arguments are already on the stack, an empty slot is waiting for the object
   dTHXa(pi);
   PL_stack_base[TOPMARK+1]=obj;
   func=NULL;
   return glue::call_method_scalar(aTHX_ name);
}

int FunCall::list_evaluate_method(SV* obj, const char* name) const
{
   // all arguments are already on the stack, an empty slot is waiting for the object
   dTHXa(pi);
   PL_stack_base[TOPMARK+1]=obj;
   func=NULL;
   return glue::call_method_list(aTHX_ name);
}

void FunCall::void_evaluate_method(SV* obj, const char* name) const
{
   // all arguments are already on the stack, an empty slot is waiting for the object
   dTHXa(pi);
   PL_stack_base[TOPMARK+1]=obj;
   func=NULL;
   glue::call_method_void(aTHX_ name);
}

ListResult::ListResult(int items, const FunCall& fc) : Array(items)
{
   if (items) {
      dTHXa(fc.pi);
      dSP;
      SV **dst=PmArray(sv)+items-1;
      do {
         if (SvTEMP(*SP)) SvREFCNT_inc(*SP);
         *dst=*SP;
         --dst; --SP;
      } while (--items>0);
      PUTBACK; FREETMPS; LEAVE;
   }
}

namespace glue {

HV* current_application_pkg(pTHX)
{
   if (cur_wrapper_cv) {
      return CvSTASH(glue::cur_wrapper_cv);
   } else {
      SV* const current_app=GvSV(User_application);
      if (current_app && SvROK(current_app)) {
         return gv_stashsv(PmArray(current_app)[Application_pkg_index], false);
      } else {
         PmCancelFuncall;
         throw std::runtime_error("current application not set");
      }
   }
}

SV** push_current_application(pTHX_ SV **SP)
{
   if (cur_wrapper_cv) {
      PUSHMARK(SP);
      PUTBACK;
      call_sv(*hv_fetch(CvSTASH(cur_wrapper_cv), "self", 4, 0), G_SCALAR | G_EVAL);
      SPAGAIN;
      if (__builtin_expect(SvTRUE(ERRSV), 0)) {
         PmCancelFuncall;
         throw exception();
      }
      PUTBACK;
   } else {
      SV* current_app=GvSV(User_application);
      if (current_app && SvROK(current_app)) {
         XPUSHs(current_app);
      } else {
         PmCancelFuncall;
         throw exception("current application not set");
      }
   }
   return SP;
}

void fill_cached_cv(pTHX_ cached_cv& cv)
{
   if (!(cv.addr=(SV*)get_cv(cv.name, false))) {
      sv_setpvf(ERRSV, "unknown perl subroutine %s", cv.name);
      PmCancelFuncall;
      throw exception();
   }
}

SV* call_func_scalar(pTHX_ SV *cv)
{
   call_sv(cv, G_SCALAR | G_EVAL);
   dSP;
   if (__builtin_expect(SvTRUE(ERRSV), 0)) {
      PmFuncallFailed;
   }
   SV* const ret=POPs;
   if (SvTEMP(ret)) SvREFCNT_inc(ret);  // prevent from being destroyed in FREETMPS
   PUTBACK; FREETMPS; LEAVE;
   return ret;
}

bool call_func_bool(pTHX_ SV *cv, int boolean_check)
{
   call_sv(cv, G_SCALAR | G_EVAL);
   dSP;
   if (__builtin_expect(SvTRUE(ERRSV), 0)) {
      PmFuncallFailed;
   }
   SV *retval=POPs;
   const bool ret=boolean_check ? SvTRUE(retval) : SvOK(retval);
   PUTBACK; FREETMPS; LEAVE;
   return ret;
}

int call_func_list(pTHX_ SV *cv)
{
   const int ret=call_sv(cv, G_ARRAY | G_EVAL);
   if (__builtin_expect(SvTRUE(ERRSV), 0)) {
      FREETMPS; LEAVE;
      throw exception();
   }
   if (ret==0) {
      FREETMPS; LEAVE;
   }
   return ret;
}

void call_func_void(pTHX_ SV *cv)
{
   const int ret=call_sv(cv, G_VOID | G_EVAL);
   if (ret>0) {
      dSP;
      (void)POPs;
      PUTBACK;
   }
   FREETMPS; LEAVE;
   if (__builtin_expect(SvTRUE(ERRSV), 0)) {
      throw exception();
   }
}

SV* call_method_scalar(pTHX_ const char *name)
{
   call_method(name, G_SCALAR | G_EVAL);
   dSP;
   if (SvTRUE(ERRSV)) {
      PmFuncallFailed;
   }
   SV* const ret=POPs;
   if (SvTEMP(ret)) SvREFCNT_inc(ret);  // prevent from being destroyed in FREETMPS
   PUTBACK; FREETMPS; LEAVE;
   return ret;
}

int call_method_list(pTHX_ const char *name)
{
   const int ret=call_method(name, G_ARRAY | G_EVAL);
   if (__builtin_expect(SvTRUE(ERRSV), 0)) {
      FREETMPS; LEAVE;
      throw exception();
   }
   if (ret==0) {
      FREETMPS; LEAVE;
   }
   return ret;
}

void call_method_void(pTHX_ const char *name)
{
   const int ret=call_method(name, G_VOID | G_EVAL);
   if (ret>0) {
      dSP;
      (void)POPs;
      PUTBACK;
   }
   FREETMPS; LEAVE;
   if (__builtin_expect(SvTRUE(ERRSV), 0)) {
      throw exception();
   }
}

} } }

// Local Variables:
// mode:C++
// c-basic-offset:3
// indent-tabs-mode:nil
// End:
