/*
 * Copyright (c) 2000 IET Inc.
 * Copyright (c) 1994-1999 Vectaport Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the names of the copyright holders not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  The copyright holders make
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 */

#include <ComTerp/comfunc.h>
#include <ComTerp/comterp.h>
#include <ComTerp/comvalue.h>
#include <ComUtil/comutil.h>
#include <Attribute/attrlist.h>
#include <string.h>

#define TITLE "ComFunc"

#ifdef LEAKCHECK
extern LeakChecker AttributeValuechecker;
#endif

/*****************************************************************************/

int ComFunc::_symid = -1;

ComFunc::ComFunc(ComTerp* comterp) {
    _comterp = comterp;
    _context = nil;
    _docstring2 = nil;
}

void ComFunc::reset_stack() {
  if (!post_eval()) {
    int count = nargs() + nkeys() - npops();
    #if 0 // now done immediately with stack_pop
    for (int i=1; i<=npops(); i++) {
      ((AttributeValue)_comterp->stack_top(i)).AttributeValue::~AttributeValue();
        #ifdef LEAKCHECK
	AttributeValuechecker.create();
        #endif
    }
    #endif

    _comterp->decr_stack(count);
  } else 
    _comterp->decr_stack(1);
  _comterp->_just_reset = true;
  npops() = 0;
}


ComValue& ComFunc::stack_arg(int n, boolean symbol, ComValue& dflt) {
    if (post_eval()) return stack_arg_post(n, symbol, dflt);

    int count = nargs() + nkeys() - npops();

    for (int i=0; i<count; i++) {
        ComValue& argref = _comterp->stack_top(i-count+1);
        if( argref.type() == ComValue::KeywordType) 
	    return dflt;
        if (i == n) {
  	    if (i+1 < count) {
	      ComValue& keyref = _comterp->stack_top(i-count+2);
	      if (keyref.is_type(ComValue::KeywordType) &&
		  keyref.keynarg_val())
		return dflt;
	    }
	    if (!symbol) 
	        argref = _comterp->lookup_symval(argref);
	    return argref;
	}
    }
    return dflt;
}

ComValue& ComFunc::stack_key(int id, boolean symbol, ComValue& dflt, boolean use_dflt_for_no_key) {
  if (post_eval()) 
    return stack_key_post(id, symbol, dflt, use_dflt_for_no_key);

  int count = nargs() + nkeys() - npops();
  for (int i=0; i<count; i++) {
    ComValue& keyref = _comterp->stack_top(-i);
    if( keyref.type() == ComValue::KeywordType) {
	    if (keyref.symbol_val() == id) {
	      if (i+1==count || keyref.keynarg_val() == 0) {
		if (use_dflt_for_no_key) 
		  return ComValue::trueval();
		else
		  return dflt;
	      } else {
		ComValue& valref = _comterp->stack_top(-i-1);
		if (valref.type() == ComValue::KeywordType) {
		  if (use_dflt_for_no_key) 
		    return ComValue::trueval();
		  else
		    return dflt;
		} else {
		  if (!symbol)
		    valref = _comterp->lookup_symval(valref);
		  return valref;
		}
	      }
	    }
    }
  }
  return use_dflt_for_no_key ? dflt : ComValue::nullval();
}

ComValue& ComFunc::stack_dotname(int n) {
    return _comterp->stack_top(n+1+npops());
}

ComValue ComFunc::stack_arg_post_eval(int n, boolean symbol, ComValue& dflt) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int argcnt;
  for (int i=0; i<nkeys(); i++) {
    argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
  }

  if (n>=nargsfixed()) return dflt;  

  for (int j=nargsfixed(); j>n; j--) {
    argcnt = 0;
    skip_arg_in_expr(offtop, argcnt);
  }

  comterp()->post_eval_expr(argcnt, offtop, pedepth()+1 
#ifdef POSTEVAL_EXPERIMENT
			    , symbol
#endif
			    );

  return comterp()->pop_stack(!symbol);
}

int ComFunc::stack_arg_post_eval_size(int n) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int argcnt;
  for (int i=0; i<nkeys(); i++) {
    argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
  }

  for (int j=nargsfixed(); j>n; j--) {
    argcnt = 0;
    skip_arg_in_expr(offtop, argcnt);
  }

  return argcnt;
}

void ComFunc::print_stack_arg_post_eval(int n) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int argcnt;
  for (int i=0; i<nkeys(); i++) {
    argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
  }

  if (n>=nargsfixed()) return;

  for (int j=nargsfixed(); j>n; j--) {
    argcnt = 0;
    skip_arg_in_expr(offtop, argcnt);
  }

  comterp()->print_post_eval_expr(argcnt, offtop, pedepth()+1);
  return;
}

postfix_token* ComFunc::copy_stack_arg_post_eval(int n, int& ntoks) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int argcnt;
  for (int i=0; i<nkeys(); i++) {
    argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
  }

  if (n>=nargsfixed()) return NULL;

  for (int j=nargsfixed(); j>n; j--) {
    argcnt = 0;
    skip_arg_in_expr(offtop, argcnt);
  }

  postfix_token* tokbuf = comterp()->copy_post_eval_expr(argcnt, offtop);
  ntoks = argcnt;
  return tokbuf;
}

ComValue** ComFunc::stack_arg_post_eval_nargsfixed(boolean symbol, ComValue& dflt) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int argcnt;
  for (int i=0; i<nkeys(); i++) {
    argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
  }

  int* offtopbuf = new int[nargsfixed()];
  int* argcntbuf = new int[nargsfixed()];
  for (int j=nargsfixed(); j>0; j--) {
    argcnt = 0;
    skip_arg_in_expr(offtop, argcnt);
    offtopbuf[j-1] = offtop;
    argcntbuf[j-1] = argcnt;
  }

  ComValue** vals = new ComValue*[nargsfixed()];

  for(int i=0; i<nargsfixed(); i++) {
      comterp()->post_eval_expr(argcntbuf[i], offtopbuf[i], pedepth()+1 
#ifdef POSTEVAL_EXPERIMENT
                                , symbol
#endif
          );
      vals[i] = new ComValue(comterp()->pop_stack(!symbol));
  }

  delete [] offtopbuf;
  delete [] argcntbuf;
  return vals;
}

ComValue ComFunc::stack_key_post_eval
(int id, boolean symbol, ComValue& dflt, boolean use_dflt_for_no_key) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int count = 0;
  while (count < nkeys()) {
    ComValue& curr = comterp()->expr_top(offtop);
    if (!curr.is_type(ComValue::KeywordType))
      return use_dflt_for_no_key ? dflt : ComValue::nullval();
    count++;
    int argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
    if (curr.symbol_val() == id) {
      if (argcnt) {
	comterp()->post_eval_expr(argcnt, offtop, pedepth()+1);
	return comterp()->pop_stack(!symbol);
      } else
	return use_dflt_for_no_key ? dflt : ComValue::trueval();
    } 
  }
  return use_dflt_for_no_key ? dflt : ComValue::nullval();
}

ComValue& ComFunc::stack_arg_post(int n, boolean symbol, ComValue& dflt) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int argcnt;
  for (int i=0; i<nkeys(); i++) {
    argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
  }

  if (n>=nargsfixed()) return dflt;  

  for (int j=nargsfixed(); j>n; j--) {
    argcnt = 0;
    skip_arg_in_expr(offtop, argcnt);
  }

  int loc = comterp()->_pfnum + offtop + argcnt-1;
  if (loc<0) {fprintf(stderr, "unexpected negative index for _pfcomvals\n"); return ComValue::nullval();}
  else return comterp()->_pfcomvals[loc];
}

ComValue& ComFunc::stack_key_post
(int id, boolean symbol, ComValue& dflt, boolean use_dflt_for_no_key) {
  ComValue argoff(comterp()->stack_top());
  int offtop = argoff.int_val()-comterp()->_pfnum;
  int count = 0;
  while (count < nkeys()) {
    ComValue& curr = comterp()->expr_top(offtop);
    if (!curr.is_type(ComValue::KeywordType))
      return use_dflt_for_no_key ? dflt : ComValue::nullval();
    count++;
    int argcnt = 0;
    skip_key_in_expr(offtop, argcnt);
    if (curr.symbol_val() == id) {
      if (argcnt) {
	int loc = comterp()->_pfnum + offtop + argcnt-1;
	return comterp()->_pfcomvals[loc];
      } else
	return use_dflt_for_no_key ? dflt : ComValue::trueval();
    } 
  }
  return use_dflt_for_no_key ? dflt : ComValue::nullval();
}

boolean ComFunc::skip_key_on_stack(int& stackptr, int& argcnt) {
  return comterp()->skip_key(&comterp()->stack_top(), stackptr, 
			     -comterp()->stack_height(), argcnt);
}

boolean ComFunc::skip_arg_on_stack(int& stackptr, int& argcnt) {
  return comterp()->skip_arg(&comterp()->stack_top(), stackptr, 
			     -comterp()->stack_height(), argcnt);
}

boolean ComFunc::skip_key_in_expr(int& offtop, int& argcnt) {
  return comterp()->skip_key(&comterp()->_pfcomvals[comterp()->_pfnum-1], 
			     offtop, -comterp()->_pfnum, argcnt);
}

boolean ComFunc::skip_arg_in_expr(int& offtop, int& argcnt) {
  return comterp()->skip_arg(&comterp()->_pfcomvals[comterp()->_pfnum-1], 
			     offtop, -comterp()->_pfnum, argcnt);
}

ComValue ComFunc::pop_stack() {
    return _comterp->pop_stack();

#if 0
    /* get rid of keywords -- use stack_key and stack_arg to get those */
    if (!npops() && nkeys()) {
        int count = nargs() + nkeys();
	int nkey = nkeys();
        for (int i=0; i<count; i++) {
	    ComValue val(_comterp->pop_stack());
	    npops()++;
	    if (val.type() == ComValue::KeywordType) nkey--;
	    if (nkey==0) break;    
	}
    }

    if (npops()<nargs()+nkeys()) {
        npops()++;
	return _comterp->pop_stack();
    } else 
        return ComValue::nullval();
#endif
}

ComValue ComFunc::pop_symbol() {
    return _comterp->pop_symbol();
#if 0  
    /* get rid of keywords -- use stack_key and stack_arg to get those */
    if (!npops() && nkeys()) {
        int count = nargs() + nkeys();
	int nkey = nkeys();
        for (int i=0; i<count; i++) {
	    ComValue val = _comterp->pop_stack();
	    npops()++;
	    if (val.type() == ComValue::KeywordType) nkey--;
	    if (nkey==0) break;    
	}
    }

    if (npops()<nargs()+nkeys()) {
        npops()++;
	return _comterp->pop_symbol();
    } else 
        return ComValue::nullval();
#endif    
}

void ComFunc::push_stack(ComValue& val) {
    _comterp->push_stack(val);
}

void ComFunc::push_stack(AttributeValue& val) {
    _comterp->push_stack(val);
}

ComValue& ComFunc::lookup_symval(ComValue& sym) {
    ComValue& retval = _comterp->lookup_symval(sym);
    return retval;
}

void ComFunc::assign_symval(int id, ComValue* sym) {
    _comterp->localtable()->insert(id, sym);
    return;
}

int ComFunc::bintest(const char* command) {
  char combuf[BUFSIZ];
#if 0
  sprintf( combuf, "echo-n $PATH; which %s", command );
#else
  sprintf( combuf, "which %s 2> /dev/null", command );
#endif
  FILE* fptr = popen(combuf, "r");
  char testbuf[BUFSIZ];	
  fgets(testbuf, BUFSIZ, fptr);  
  pclose(fptr);
  if (strncmp(testbuf+strlen(testbuf)-strlen(command)-1, 
	      command, strlen(command)) != 0) {
    return -1;
  }
  return 0;
}

boolean ComFunc::bincheck(const char* command) {
  int status = bintest(command);
  return !status;
}

ComFuncState* ComFunc::funcstate() {
  return _comterp->top_funcstate();
}

void ComFunc::push_funcstate(int nargs, int nkeys, int pedepth,
			     int command_symid, unsigned linenum) {
  ComFuncState cfs(nargs, nkeys, pedepth, 
		   command_symid==0 ? classid() : command_symid, linenum );
  _comterp->push_funcstate(cfs);
}

void ComFunc::pop_funcstate() {
  _comterp->pop_funcstate();
}

void ComFunc::exec(int nargs, int nkeys, int pedepth,
		   int command_symid) {
  push_funcstate(nargs, nkeys, pedepth, command_symid, 0);
  execute();
  pop_funcstate();
}

int& ComFunc::nargs() {
  return _comterp->top_funcstate()->nargs();
}

int& ComFunc::nkeys() {
  return _comterp->top_funcstate()->nkeys();
}

int& ComFunc::npops() {
  return _comterp->top_funcstate()->npops();
}

int ComFunc::nargspost() {
  ComFuncState* funcstate = _comterp->top_funcstate();
  if (funcstate->nargspost()>=0) 
    return funcstate->nargspost();

  int nargs = funcstate->nargs();
  int nkeys = funcstate->nkeys();
  int topptr = 0;
  if (post_eval()) {
    ComValue argoff(comterp()->stack_top());
    topptr = argoff.int_val()-comterp()->_pfnum;
  }
  int argcnt=0;
  while (nargs>0 || nkeys>0) {
    ComValue& val = comterp()->expr_top(topptr);
    int cnt = 0;
    if (val.is_type(ComValue::KeywordType)) {
      argcnt++;
      skip_key_in_expr(topptr, cnt);
      argcnt += cnt;
      nargs -= cnt ? 1 : 0;
      nkeys--;
    } else {
      skip_arg_in_expr(topptr, cnt);
      argcnt += cnt;
      nargs--;
    }
  }
  return argcnt;
}

int ComFunc::nargskey() {
  ComFuncState* funcstate = _comterp->top_funcstate();
  if (funcstate->nargskey()>=0) 
    return funcstate->nargskey();

  int nkeys = funcstate->nkeys();
  int topptr = 0;
  if (post_eval()) {
    ComValue argoff(comterp()->stack_top());
    topptr = argoff.int_val()-comterp()->_pfnum;
  }
  int nkeyargs = 0;
  while (nkeys>0) {
    int argcnt = 0;
    if (!post_eval()) { 
      skip_key_on_stack(topptr, argcnt);
    } else {
      skip_key_in_expr(topptr, argcnt);
    }
    nkeyargs += argcnt ? 1 : 0;
    nkeys--;
  }
  funcstate->nargskey() = nkeyargs;
  return nkeyargs;
}

int& ComFunc::pedepth() {
  return _comterp->top_funcstate()->pedepth();
}

AttributeList* ComFunc::stack_keys(boolean symbol, AttributeValue& dflt) {
  AttributeList* al = new AttributeList();
  int count = nargs() + nkeys() - npops();
  for (int i=0; i<count; i++) {
    ComValue& keyref = _comterp->stack_top(-i);
    if( keyref.type() == ComValue::KeywordType) {
      int key_symid = keyref.symbol_val();
      if (i+1==count || keyref.keynarg_val() == 0) 
	al->add_attr(key_symid, dflt);
      else {
	ComValue& valref = _comterp->stack_top(-i-1);
	if (valref.type() == ComValue::KeywordType) 
	  al->add_attr(key_symid, dflt);
	else {
	  if (!symbol) 
	    valref = _comterp->lookup_symval(valref);
	  al->add_attr(key_symid, valref);
	}
      }
    }
  }
  return al;
}

ComTerpServ* ComFunc::comterpserv() { 
  return _comterp && _comterp->is_serv() ? (ComTerpServ*)_comterp : nil; 
}

ostream& operator<< (ostream& out, const ComFunc& cf) {
    out << symbol_pntr(cf.funcid());
    return out;
}



/*****************************************************************************/

ComFuncState::ComFuncState(int narg, int nkey, int pedepth, 
			   int command_symid, unsigned linenum) {
  _nargs = narg;
  _nkeys = nkey;
  _npops = 0;
  _nargskey = -1;
  _nargspost = -1;
  _pedepth = pedepth;
  _command_symid = command_symid;
  _linenum=linenum;
}

ComFuncState::ComFuncState(const ComFuncState& cfs) {
  *this = cfs;
}

ostream& operator<< (ostream& out, const ComFuncState& cf) {
    out << "nargs = " << cf._nargs;
    out << ";nkeys = " << cf._nkeys;
    return out;
}


