#include "efunc.h"
#include "logger.h"
#include "efunccode.h"
#include "evarhash.h"

efunc::efunc(evar& obj,eclassMethodBase *pmethod,const evararray& args): defargs(args),fReturn(0x00) 
{
  func = new efuncObjMethod(obj,pmethod);
  ++func->pcount;

  updateInfo();
}

efunc::efunc(const evar& obj,eclassMethodBase *pmethod,const evararray& args): defargs(args),fReturn(0x00)
{
  func = new efuncObjMethod(obj,pmethod);
  ++func->pcount;

  updateInfo();
}

const type_info& efunc::getTypeid() const
{
  if (func)
    return(func->getTypeid());
  return(typeid(void));
} 

void efunc::json_serial(estr& data) const
{
  data+=estr("{\"t\":\"")+estr(typeid(efunc).name())+"\",\"d\":{";
  if (func){
    if (getTypeid()==typeid(efuncCode)){
      efuncCode& fcode(*static_cast<efuncCode*>(func));
      data+="\"code\":";
      fcode.code.json_serial(data);
      data+=",\"args\":";
      fcode.args.json_serial(data);
    }
  }
  data+="}}";
}

void efunc::serial(estr& data) const
{
  if (getTypeid()==typeid(efuncCode)){
    efuncCode& fcode(*static_cast<efuncCode*>(func));
    serialuint(1,data);
    fcode.code.serial(data);
    fcode.args.serial(data);
  }else{
    serialuint(0,data);
  }
}

size_t efunc::unserial(const estr& data,size_t i)
{
  clear();
  if (i==-1) return(i);
  unsigned int ftype;
  i=unserialuint(ftype,data,i);
  if (i==-1) return(i);
  if (ftype==1){
    estr codestr;
    estr argsstr;
    i=codestr.unserial(data,i);
    if (i==-1) return(i);
    i=argsstr.unserial(data,i);
    if (i==-1) return(i);

    efuncCode *pfcode=new efuncCode;
    ecodeParser cparser;
    ecodeAtomBlock *catom=new ecodeAtomBlock;
    catom->parse(cparser,codestr);
    pfcode->exec=catom;
    pfcode->code=codestr;
    pfcode->args=argsstr;
    setFunc(pfcode);
  }
  return(i);
}

void efunc::serial(const efile& f) const
{
  if (getTypeid()==typeid(efuncCode)){
    efuncCode& fcode(*static_cast<efuncCode*>(func));
    serialuint(1,f);
    fcode.code.serial(f);
    fcode.args.serial(f);
  }else{
    serialuint(0,f);
  }
}

bool efunc::unserial(const efile& f)
{
  clear();
  unsigned int ftype;
  if (!unserialuint(ftype,f)) return(false);
  if (ftype==1){
    estr codestr;
    estr argsstr;
    if (!codestr.unserial(f)) return(false);
    if (!argsstr.unserial(f)) return(false);

    efuncCode *pfcode=new efuncCode;
    ecodeParser cparser;
    ecodeAtomBlock *catom=new ecodeAtomBlock;
    catom->parse(cparser,codestr);
    pfcode->exec=catom;
    pfcode->code=codestr;
    pfcode->args=argsstr;
    setFunc(pfcode);
  }
  return(true);
}

void efunc::setDefaultArgs(evararray& args) const
{
  int i;
  if (!defargs.size() || args.size()+defargs.size()<fArgs.size()) return;

  for (i=args.size()+defargs.size()-fArgs.size(); i<defargs.size(); ++i)
    args.add(defargs[i]);
}

evar efunc::call(evararray& args) const
{
  if (!func) { ldinfo("efunc: attempting to call unassigned function"); return(evar()); }
  setDefaultArgs(args);
  if (fArgs.size()==1 && *fArgs[0] == typeid(evararray) && ((args.size()==1 && args[0].getTypeid()!=typeid(evararray)) || args.size()!=1)) // TODO: Fix this HACK, used to pass variable number of arguments to functions with only one argument of evararray
    return(func->call(evararray(evar(args))));
  return(func->call(args));
}

evar efunc::call() const
{
  if (!func) { ldinfo("efunc: attempting to call unassigned function"); return(evar()); }
  evararray tmpargs;
  setDefaultArgs(tmpargs);
  if (fArgs.size()==1 && *fArgs[0] == typeid(evararray)) // TODO: Fix this HACK, used to pass variable number of arguments to functions with only one argument of evararray
    return(func->call(evararray(evar(tmpargs))));
  return(func->call(tmpargs));
}

evar efunc::call(const evararray& args) const
{
  if (!func) { ldinfo("efunc: attempting to call unassigned function"); return(evar()); }
  evararray tmpargs(args);
  setDefaultArgs(tmpargs);
  if (fArgs.size()==1 && *fArgs[0] == typeid(evararray) && ((args.size()==1 && args[0].getTypeid()!=typeid(evararray)) || args.size()!=1)) // TODO: Fix this HACK, used to pass variable number of arguments to functions with only one argument of evararray
    return(func->call(evararray(evar(tmpargs))));
  return(func->call(tmpargs));
}

evar efunc::call2(const evararray& args) const
{
  if (!func) { ldinfo("efunc: attempting to call unassigned function"); return(evar()); }
  return(func->call(args));
}

void efunc::call(evar& rval,const evararray& args) const
{
  rval.clear();
  if (!func) { ldinfo("efunc: attempting to call unassigned function"); return; }
  evararray tmpargs(args);
  setDefaultArgs(tmpargs);
  if (fArgs.size()==1 && *fArgs[0] == typeid(evararray) && ((args.size()==1 && args[0].getTypeid()!=typeid(evararray)) || args.size()!=1)) // TODO: Fix this HACK, used to pass variable number of arguments to functions with only one argument of evararray
    { rval.set(func->call(evararray(evar(tmpargs)))); return; }
  else if (tmpargs.size()==fArgs.size()-1 && fArgs.size()>0 && *fArgs[fArgs.size()-1] == typeid(evarhash)){ // Consider a evarhash at the end of the argument list to be optional (pass empty evarhash) when one is missing, this is used for optional parameters whethe interpreter is not passing any options
    tmpargs.add(evarhash());
    rval.set(func->call(tmpargs));
    return;
  }

  rval.set(func->call(tmpargs));
}

/*
evar efunc::call(const estr& args) const
{
  if (!func) { ldinfo("efunc: attempting to call unassigned function"); return(evar()); }

  evararray arr;
  evarSplitArgs(arr,args);
  setDefaultArgs(arr);
  return(call(arr));
}
*/

evar efunc::operator()() const
{
  return(call(evararray()));
}

evar efunc::operator()(const evararray& _args) const
{
  return(call(_args));
}

evar efunc::operator()(evararray& _args) const
{
  return(call(_args));
}

efunc& efunc::setFunc(efuncBase* _func)
{
  clear();
  func = _func;
  if (func)
    ++func->pcount;
  updateInfo();

  return(*this);
}

efunc& efunc::operator=(const efunc& _func)
{
  clear();
  func = _func.func;
  defargs=_func.defargs;
  if (func)
    ++func->pcount;
  updateInfo();

  return(*this);
}

void efunc::clear()
{
  if (func){
    --func->pcount;
    if (!func->pcount)
      delete func;
    func=0x00;
  }
}

efunc::efunc(): func(0x00),fReturn(0x00) {}

efunc::efunc(const efunc& _func): func(0x00),fReturn(0x00)
{
  operator=(_func);
}

efunc::~efunc()
{
  clear();
}


void efunc::updateInfo()
{
  fArgs.clear();
  if (func)
    func->updateInfo(this);
}

bool efunc::operator==(const efunc& f) const
{
  if (!f.func || !func || f.func->getTypeid()!=func->getTypeid()) return(false);
  return(func->equalFunc(f.func));
}

ostream& operator<<(ostream& stream,const efunc& func)
{
  if (func.fReturn==0x00)
    stream << "void ";
  else
    stream << func.fReturn->name() <<" ";

  stream << "(*)(";
  int i;
  for (i=0; i<func.fArgs.size()-1; ++i)
    stream << func.fArgs[i]->name() <<",";
  if (i<func.fArgs.size())
    stream << func.fArgs[i]->name();
  stream <<")";
  return(stream);
}
