#include "evar_dec.h"

#include "logger.h"
#include "evarclass.h"

#include "evar.h"

#include "edistcomp.h"

estr getClassName(const type_info* tinfo)
{
  if (getClasses().exists(tinfo->name()) && getClasses().values(tinfo->name()).name.len())
    return(getClasses().values(tinfo->name()).name);
  return(tinfo->name());
}

template <>
evar evarCopy(const evar& obj)
{
  return(obj);
}

template <>
evar evarRef(evar& obj)
{
  return(obj);
}

template <>
evar evarRef(evar* obj)
{
  return(*obj);
}


template <>
evar evarRef(void* obj)
{
  return(static_cast<evarBase*>(new evarType<void>(obj)));
}

void json_serial(const evar& var,estr& data)
{
  var.json_serial(data);
}
void serial(const evar& var,estr& data)
{
  var.serial(data);
}
evar unserial(const estr& data)
{
  evar var;
  var.unserial(data);
  return(var);
}

const char *evar::getClass() const
{
  if (!var) return("empty");
  return(var->getClass());
}
bool evar::isTypeid(const type_info& tid) const
{
  if (isNull()) return(false);
  return(var->isTypeid(tid));
}

const type_info& evar::getPTypeid() const
{
  if (!isNull())
    return(var->getPTypeid());
  return(typeid(void));
}
const type_info& evar::getTypeid() const
{
  if (!isNull())
    return(var->getTypeid());
  return(typeid(void));
}
bool evar::isNull() const
{
  if (!var) return(true);
  return(false);
}

#include <inttypes.h>

void json_serialbool(bool v,estr &data)
{
  data+=(v?"true":"false");
}
void serialbool(bool v,estr &data)
{
  data.reserve(data.len()+sizeof(int8_t));
  *((int8_t*)&data[data._strlen])=(int8_t)(v?1:0);
  data._strlen+=sizeof(int8_t);
  data[data._strlen]=0x00;
}
size_t unserialbool(bool& v,const estr& data,size_t i)
{
  if (i==-1) return(i);
  if (data.len()<i+sizeof(int8_t)) return(-1);
  v=*((int8_t*)&data._str[i]);
  return(i+sizeof(int8_t));
}
void serialbool(bool v,const efile& f)
{
  char tstr=(v?1:0);
  f.write(&tstr,sizeof(char));
}
bool unserialbool(bool& v,const efile& f)
{
  char tstr;
  long l=f.read(&tstr,sizeof(char));
  if (l!=sizeof(char)) return(false);
  v=(tstr==1);
  return(true);
}


void json_serialint(int v,estr &data)
{
  data+=v;
}
void serialint(int v,estr &data)
{
  data.reserve(data.len()+sizeof(int32_t));
  *((int32_t*)&data[data._strlen])=(int32_t)v;
  data._strlen+=sizeof(int32_t);
  data[data._strlen]=0x00;
}
size_t unserialint(int& v,const estr& data,size_t i)
{
  if (i==-1) return(i);
  if (data.len()<i+sizeof(int32_t)) return(-1);
  v=*((int32_t*)&data._str[i]);
  return(i+sizeof(int32_t));
}
void serialint(int v,const efile& f)
{
  char tstr[sizeof(int32_t)];
  *((int32_t*)tstr)=(int32_t)v;
  f.write(tstr,sizeof(int32_t));
}
bool unserialint(int& v,const efile& f)
{
  char tstr[sizeof(int32_t)];
  long l=f.read(tstr,sizeof(int32_t));
  if (l!=sizeof(int32_t)) return(false);
  v=*((int32_t*)tstr);
  return(true);
}

void json_serialuint(unsigned int v,estr &data)
{
  data+=v;
}
void serialuint(unsigned int v,estr &data)
{
  data.reserve(data.len()+sizeof(uint32_t));
  *((uint32_t*)&data[data._strlen])=(uint32_t)v;
  data._strlen+=sizeof(uint32_t);
  data[data._strlen]=0x00;
}
size_t unserialuint(unsigned int& v,const estr& data,size_t i)
{
  if (i==-1) return(i);
  if (data.len()<i+sizeof(uint32_t)) return(-1);
  v=*((uint32_t*)&data._str[i]);
  return(i+sizeof(uint32_t));
}
void serialuint(unsigned int v,const efile& f)
{
  char tstr[sizeof(uint32_t)];
  *((uint32_t*)tstr)=(uint32_t)v;
  f.write(tstr,sizeof(uint32_t));
}
bool unserialuint(unsigned int& v,const efile& f)
{
  char tstr[sizeof(uint32_t)];
  long l=f.read(tstr,sizeof(uint32_t));
  if (l!=sizeof(uint32_t)) return(false);
  v=*((uint32_t*)tstr);
  return(true);
}


void json_serialulong(unsigned long v,estr &data)
{
  data+=v;
}
void serialulong(unsigned long v,estr &data)
{
  data.reserve(data.len()+sizeof(uint64_t));
  *((uint64_t*)&data[data._strlen])=(uint64_t)v;
  data._strlen+=sizeof(uint64_t);
  data[data._strlen]=0x00;
}
size_t unserialulong(unsigned long& v,const estr& data,size_t i)
{
  if (i==-1) return(i);
  if (data.len()<i+sizeof(uint64_t)) return(-1);
  v=*((uint64_t*)&data._str[i]);
  return(i+sizeof(uint64_t));
}
void serialulong(unsigned long v,const efile& f)
{
  char tstr[sizeof(uint64_t)];
  *((uint64_t*)tstr)=(uint64_t)v;
  f.write(tstr,sizeof(uint64_t));
}
bool unserialulong(unsigned long& v,const efile& f)
{
  char tstr[sizeof(uint64_t)];
  long l=f.read(tstr,sizeof(uint64_t));
  if (l!=sizeof(uint64_t)) return(false);
  v=*((uint64_t*)tstr);
  return(true);
}


void json_seriallong(long v,estr &data)
{
  data+=v;
}
void seriallong(long v,estr &data)
{
  data.reserve(data.len()+sizeof(int64_t));
  *((int64_t*)&data[data._strlen])=(int64_t)v;
  data._strlen+=sizeof(int64_t);
  data[data._strlen]=0x00;
}
size_t unseriallong(long& v,const estr& data,size_t i)
{
  if (i==-1) return(i);
  if (data.len()<i+sizeof(int64_t)) return(-1);
  v=*((int64_t*)&data._str[i]);
  return(i+sizeof(int64_t));
}
void seriallong(long v,const efile& f)
{
  char tstr[sizeof(int64_t)];
  *((int64_t*)tstr)=(int64_t)v;
  f.write(tstr,sizeof(int64_t));
}
bool unseriallong(long& v,const efile& f)
{
  char tstr[sizeof(int64_t)];
  long l=f.read(tstr,sizeof(int64_t));
  if (l!=sizeof(int64_t)) return(false);
  v=*((int64_t*)tstr);
  return(true);
}

void json_serialfloat(float v,estr &data)
{
  data+=v;
}
void serialfloat(float v,estr &data)
{
  data.reserve(data.len()+sizeof(float));
  *((float*)&data[data._strlen])=(float)v;
  data._strlen+=sizeof(float);
  data[data._strlen]=0x00;
}
size_t unserialfloat(float& v,const estr& data,size_t i)
{
  if (i==-1) return(i);
  if (data.len()<i+sizeof(float)) return(-1);
  v=*((float*)&data._str[i]);
  return(i+sizeof(float));
}
void serialfloat(float v,const efile& f)
{
  char tstr[sizeof(float)];
  *((float*)tstr)=(float)v;
  f.write(tstr,sizeof(float));
}
bool unserialfloat(float& v,const efile& f)
{
  char tstr[sizeof(float)];
  long l=f.read(tstr,sizeof(float));
  if (l!=sizeof(float)) return(false);
  v=*((float*)tstr);
  return(true);
}


void json_serialdouble(double v,estr &data)
{
  data+=v;
}
void serialdouble(double v,estr &data)
{
  data.reserve(data.len()+sizeof(double));
  *((double*)&data[data._strlen])=(double)v;
  data._strlen+=sizeof(double);
  data[data._strlen]=0x00;
}
size_t unserialdouble(double& v,const estr& data,size_t i)
{
  if (i==-1) return(i);
  if (data.len()<i+sizeof(double)) return(-1);
  v=*((double*)&data._str[i]);
  return(i+sizeof(double));
}
void serialdouble(double v,const efile& f)
{
  char tstr[sizeof(double)];
  *((double*)tstr)=(double)v;
  f.write(tstr,sizeof(double));
}
bool unserialdouble(double& v,const efile& f)
{
  char tstr[sizeof(double)];
  long l=f.read(tstr,sizeof(double));
  if (l!=sizeof(double)) return(false);
  v=*((double*)tstr);
  return(true);
}


void addpstr(estr &data,const estr& str)
{
  data.reserve(data.len()+sizeof(uint32_t)+str.len());
  *((uint32_t*)&data[data._strlen])=(uint32_t)str.len();
  memcpy(&data[data._strlen+sizeof(uint32_t)],str._str,str.len());
  data._strlen+=str.len()+sizeof(uint32_t);
}
size_t readpstr(estr &value, const estr& data,size_t i)
{
  if (data.len()<i+sizeof(uint32_t)) { lerror("truncated serial string?"); return(-1); }
  size_t len;
  len=*((uint32_t*)&data._str[i]);
  if (data.len()<i+sizeof(uint32_t)+len){ lerror("truncated serial string?"); return(-1); }

  value.reserve(len);
  memcpy(value._str,&data._str[i+sizeof(uint32_t)],len);
  value[len]=0x00;
  value._strlen=len;
  return(i+len+sizeof(uint32_t));
}
void writepstr(estr &v,const efile& f)
{
  serialuint(v.len(),f);
  f.write(v._str,v.len());
}
bool readpstr(estr& v,const efile& f)
{
  unsigned int len;
  if (!unserialuint(len,f)) return(false);
  v.reserve(len);

  long l=f.read(v._str,len);
  if (l!=len) return(false);

  v._str[len]=0x00;
  v._strlen=len;
  return(true);
}


void evar::create(const estr& type)
{
  if (!getClasses().exists(type) || !getClasses().values(type).create.size()) { lwarn("unable to create object of class: "+type); return; }

  set(getClasses().values(type).create[0]());
}

size_t evar::unserial(const estr& data,size_t i)
{
  if (i==-1) return(i);

  estr type;
//  ldinfo("unserial: data.len: "+estr(data.len())+" i: "+estr(i));
  i=readpstr(type,data,i);
  if (i==-1) return(-1);
  if (!getClasses().exists(type)) return(i);

  if (type!=getClass())
    create(type);

  if(var==0x00){ lwarn("unable to create object of type: "+type); return(i); }

//  ldinfo("unserial type: "+type);
  if (getClasses().exists(type))
    return(getClasses().values(type).funserial(var,data,i));
  
  return(i);
}

bool evar::unserial(const efile& f)
{
  estr type;

  if (!type.unserial(f)) return(false);
  if (!getClasses().exists(type)) return(true);

  if (type!=getClass())
    create(type);

  if(var==0x00){ lwarn("unable to create object of type: "+type); return(true); }

//  ldinfo("unserial type: "+type);
  if (getClasses().exists(type))
    return(getClasses().values(type).funserialf(var,f));
  
  return(true);
}


void evar::json_serial(estr& data) const
{
  data+=estr("{\"t\":\"")+getClass()+"\",\"d\":";
  if (getClasses().exists(getClass()))
    getClasses().values(getClass()).fjson_serial(var,data);
  else
    data+="null";
  data+="}";
}

void evar::serial(estr& data) const
{
  addpstr(data,getClass());
  if (!getClasses().exists(getClass())) return;
  getClasses().values(getClass()).fserial(var,data);

/*  
  int i;
  if (getParser()->classes[getClass()].getProperties().size()){
    for (i=0; i<getParser()->classes[getClass()].getProperties().size(); ++i){
      addpstr(data,getParser()->classes[getClass()].getProperties().at(i));
      get(getParser()->classes[getClass()].getProperties().at(i)).serial(data);
    }
  }
*/
}

void evar::serial(const efile& f) const
{
  estr(getClass()).serial(f);
  if (!getClasses().exists(getClass())) return;
  getClasses().values(getClass()).fserialf(var,f);

/*  
  int i;
  if (getParser()->classes[getClass()].getProperties().size()){
    for (i=0; i<getParser()->classes[getClass()].getProperties().size(); ++i){
      addpstr(data,getParser()->classes[getClass()].getProperties().at(i));
      get(getParser()->classes[getClass()].getProperties().at(i)).serial(data);
    }
  }
*/
}


evar& evar::get() const { return((evar&)*this); }
evar* evar::getarg(void (*pfunc)(evar*)) const { return((evar*)this); }
evar& evar::getarg(void (*pfunc)(evar)) const { return((evar&)*this); }
const evar& evar::getarg(void (*pfunc)(const evar&)) const { return(*this); }
evar& evar::getarg(void (*pfunc)(evar&)) const { return((evar&)*this); }
//evar& evar::getarg(void (*pfunc)(evar&&)) const { return((evar&)*this); }


evar evar::convert(const type_info& ti) const
{
  if (var==0x00) { lwarn("empty var"); throw("trying to get from empty envar"); }

  if (var->isTypeid(ti) || ti == typeid(evar))
    return(*this);

  if (var->isTypeid(typeid(evarRemote)))
    return(evarRemoteValue(var));

  // if typeid T is one of the parent classes, you can return the value directly
  eclassConverterBase *converter=getConverter(ti,var->getTypeid());
  if (converter==0x00) { lwarn("unable to convert variable from type: "+estr(var->getClass())+" to type: "+estr(ti.name())); return(evar()); }

  return((*converter)( var )); 
}

bool evar::isConvertible(const type_info& ti) const
{
  if (var==0x00){ return(false); } 
//  if (var==0x00){ lwarn("empty var"); throw("trying to get from empty envar"); } 

  if (var->isTypeid(ti) || ti==typeid(evar))
    return(true);

  eclassConverterBase *converter=getConverter(ti,var->getTypeid());
  if (converter) return(true);
  return(false);
}




void evarSplitArgs(evararray &arr,const estr& args)
{
  estrarray strarr;
  if (args.len())
    strarr = args.explode(",");
  int i;
  for (i=0; i<strarr.size(); ++i)
    arr.add(new estr(strarr[i]));
}

/*
evar evar::call(const estr& method,const estr& args)
{
  if (!var){ ldinfo("error: evar is not set"); return(evar()); }

  if (var->getClassTypeid() == typeid(evar))
    return( ((evarType<evar>*)var)->object->call(method,args).var );

  evararray arr;
  evarSplitArgs(arr,args); 
  return(var->call(method,arr).var);
}
*/




evar::evar(): var(0x00) {}

evar::evar(void (*f)())
{
  var=new evarType<void ()>(f);
  retain();
}

evar::evar(evarBase* value): var(value)
{
  retain();
}
evar::evar(evar* value): var(value->var)
{
  retain();
}
evar::evar(const evar* value): var(value->var)
{
  retain();
}
evar::evar(const evar& value): var(value.var)
{
  retain();
}
evar::evar(evar& value): var(value.var)
{
  retain();
}

evar::evar(const char* value)
{
  var = new evarTypeClean<estr>(new estr(value));
  retain();
}

//#if !defined(__APPLE__) && !defined(_MSC_VER) // needed in linux to instatiate this constructor, but gives an error in osx
template evar::evar(double&);
//#endif

/*
evar::evar(double& value)
{
  var = new evarTypeClean<double>(new double(value));
  retain();
}

evar::evar(float& value)
{
  var = new evarTypeClean<float>(value);
  retain();
}

evar::evar(unsigned long int& value)
{
  var = new evarType<unsigned long int>(value);
  retain();
}

evar::evar(unsigned int& value)
{
  var = new evarType<unsigned int>(value);
  retain();
}

evar::evar(unsigned char& value)
{
  var = new evarType<unsigned char>(value);
  retain();
}

evar::evar(long int& value)
{
  var = new evarType<long int>(value);
  retain();
}

evar::evar(int& value)
{
  var = new evarType<int>(value);
  retain();
}

evar::evar(char& value)
{
  var = new evarType<char>(value);
  retain();
}

evar::evar(bool& value)
{
  var = new evarType<bool>(value);
  retain();
}

evar::evar(const double& value)
{
  var = new evarTypeClean<double>(new double(value));
  retain();
}

evar::evar(const float& value)
{
  var = new evarTypeClean<float>(new float(value));
  retain();
}

evar::evar(const unsigned long int& value)
{
  var = new evarTypeClean<unsigned long int>(new unsigned long int(value));
  retain();
}

evar::evar(const unsigned int& value)
{
  var = new evarTypeClean<unsigned int>(new unsigned int(value));
  retain();
}

evar::evar(const unsigned char& value)
{
  var = new evarTypeClean<unsigned char>(new unsigned char(value));
  retain();
}

evar::evar(const long int& value)
{
  var = new evarTypeClean<long int>(new long int(value));
  retain();
}

evar::evar(const int& value)
{
  var = new evarTypeClean<int>(new int(value));
  retain();
}

evar::evar(const char& value)
{
  var = new evarTypeClean<char>(new char(value));
  retain();
}

evar::evar(const bool& value)
{
  var = new evarTypeClean<bool>(new bool(value));
  retain();
}
*/

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

void evar::clear()
{
  release();
  var=0x00;
}

evar& evar::set(const evar& value)
{
  clear();
  var=value.var;
  retain();
  return(*this);
}
evar evar::copyPtr() const
{
  if (!var) return(evar());
  return(var->copy());
}
evar evar::copy() const
{
  if (!getClasses().exists(getClass())) return(evar());

  return(getClasses().values(getClass()).fcopy(var));
}
evar& evar::copy(const evar& value)
{
  clear();
  if (!getClasses().exists(value.getClass())) return(*this);

  var=getClasses().values(value.getClass()).fcopy(value.var);
  retain();
  return(*this);
}
evar& evar::operator=(evarBase* _var)
{
  clear();
  var=_var;
  retain();
  return(*this);
}
evar& evar::operator=(const char* value)
{
  clear();
  var = new evarType<estr>(*new estr(value));
  retain();
  return(*this);
}

void evar::retain()
{
  if (var)
    ++var->pcount;
}

void evar::release()
{
  if (var){
    --var->pcount;
    if (var->pcount==0)
      delete var;
    var=0x00;
  }
}


eclassMethodBase* findMethod(ebasicarray<eclassMethodBase*>& methods,const evararray& args,eclassMethodBase*& bmethod,int& bscore)
{
  unsigned int i,j;

/*
  if (methods.size()==1){
    bmethod=methods.operator[](0);
    bscore=1;
    return(methods.operator[](0));
  }
*/

  eclassMethodBase *m;

  int score;

  int bestscore;
  int bestmatch;

  bestscore=0;
  bestmatch=-1;
  // TODO: Improve overloaded function matching. we can check if conversion is possible,
  //   and also implement default values for arguments
  for (i=0; i<methods.size(); ++i){
    m=methods.operator[](i);
    score=0;

//    ldinfo("evaluating methods: "+estr(i)+"/"+estr((int)methods.size())+" args: "+estr((int)args.size())+" m->fArgs: "+estr((int)m->fArgs.size()));
//    ldinfo((m->fReturn?estr(getClassName(m->fReturn)):estr("void"))+" method(");
//    for (int j=0; j<m->fArgs.size(); ++j)
//      ldinfo(getClassName(m->fArgs.at(j)));
//    ldinfo(")");


    if (args.size()==0 && m->fArgs.size()==0){
      bmethod=m;
      bscore=2;
      return(m);
    }

    if (args.size()==0 && m->fArgs.size()>0 && m->fArgs.size() - m->defargs.size() == 0){  // there should only be one function with enough default arguments in this case
      bmethod=m;
      bscore=2;
      return(m);
    }

    if (m->fArgs.size() && m->fArgs.size() - m->defargs.size() > args.size()) // not enough arguments to call function
      continue;
    
    for (j=0; j<args.size() && j<m->fArgs.size(); ++j){ // j+1 because first element must be skipped
//      cout << args[j].getClassTypeid().name() << " --- "<<m->fArgs[j]->name()<<endl;
      if ((args[j].getTypeid() == *m->fArgs[j]))
        score+=3;
      else if (*m->fArgs[j]==typeid(evar))
        score+=1;
      else{
        if (!args[j].isConvertible(*m->fArgs[j])){
          score=-1;
          break;
        }else
         score+=2;
      }
    }

//    ldinfo("evaluating methods: "+estr(i)+"/"+estr((int)methods.size())+" args: "+estr((int)args.size())+" m->fArgs: "+estr((int)m->fArgs.size())+" score: "+estr(score));
    // if function args matches number and exact classes then use this function
    if (max(args.size()*3,m->fArgs.size()) == score){
      bmethod=m;
      bscore=2;
      return(m);
    }

    if (score>bestscore){ bestscore=score; bestmatch=i; }
  }

  if (bestmatch!=-1){
    if (bscore==0){
      bmethod=methods.operator[](bestmatch);
      bscore=1;
    }
    return(bmethod);
  }

  lwarn("did not find any compatible method");
//  return(methods.operator[](0));
  return(0x00);
}

eclassMethodBase* findMethod(ebasicarray<eclassMethodBase*>& methods,const evararray& args)
{
 int bscore=0;
 eclassMethodBase *bmethod=0x00;
 findMethod(methods,args,bmethod,bscore);
 if (bmethod==0x00)
   return(methods[0]);
 return(bmethod);
}


evar& evar::operator=(const evar& value)
{
  if (!getClasses().exists(value.getClass())) return(*this);

  if (getTypeid()==value.getTypeid()){
    getClasses().values(getClass()).fassign(var,value.var);
    return(*this);
  }

  if (value.isConvertible(getTypeid())){
    evar conv(value.convert(getTypeid()));
    getClasses().values(getClass()).fassign(var,conv.var);
    return(*this);
  }

  evararray args(value);
  // look for defined "operator=" methods

  if (getClasses().values(getClass()).methods.exists("=")){
    eclassMethodBase *bestmatch=findMethod(getClasses().values(getClass()).methods.values("="),args);
    (*bestmatch)(var,args);
    return(*this);
  }

  int i;
  for (i=0; i<getClasses().values(var->getClass()).parents.size(); ++i){
    if (getClasses().values(var->getClass()).parents[i]->methods.exists("=")){
      evar tmpvar;
      tmpvar=convert(getClasses().values(var->getClass()).parents[i]->getTypeid()).var;
      ldieif(tmpvar.var==0x00,"class is parent but conversion failed?");
      eclassMethodBase *bestmatch=findMethod(getClasses().values(var->getClass()).parents[i]->methods.values("="),args);
      (*bestmatch)(tmpvar.var,args);
      return(*this);
    }
  }
  lwarn("operator= does not exist in object of class: "+estr(getClass()));
  return(*this);
}


earray<estr> evar::methods() const
{
  if (var==0x00 || !getClasses().exists(var->getClass())) return(earray<estr>());

  earray<estr> arr;
  int i;
  for (i=0; i<getClasses().values(var->getClass()).methods.size(); ++i)
    arr.add(getClasses().values(var->getClass()).methods.keys(i));
  return(arr);
}

earray<estr> evar::properties() const
{
  if (var==0x00 || !getClasses().exists(var->getClass())) return(earray<estr>());

  earray<estr> arr;
  int i;
  for (i=0; i<getClasses().values(var->getClass()).properties.size(); ++i)
    arr.add(getClasses().values(var->getClass()).properties.keys(i));
  return(arr);
}

bool evar::hasProperty(const estr& pname)
{
  if (var==0x00) { lwarn("evar is null"); return(false); }

  if (!getClasses().exists(var->getClass())) { lwarn("class: "+estr(var->getClass())+" not registered"); return(false); }

  if (getClasses().values(var->getClass()).properties.exists(pname))
    return(true);

  int i;
  for (i=0; i<getClasses().values(var->getClass()).parents.size(); ++i){
    if (getClasses().values(var->getClass()).parents[i]->properties.exists(pname))
      return(true);
  }

  return(false);
}

bool evar::hasMethod(const estr& mname)
{
  if (var==0x00) { lwarn("evar is null"); return(false); }

  if (!getClasses().exists(getClass())) { lwarn("class: "+estr(getClass())+" not registered"); return(false); }

  if (getClasses().values(getClass()).methods.exists(mname))
    return(true);

  int i;
  for (i=0; i<getClasses().values(var->getClass()).parents.size(); ++i){
    if (getClasses().values(var->getClass()).parents[i]->methods.exists(mname))
      return(true);
  }
  lwarn("method: \""+mname+"\" does not exist in object of class: "+getClass());
  return(false);
}

eclassMethodBase* evar::getCallMethod(const estr& mname,const evararray& args) const
{
  lerrorif(var==0x00,"var is null");
  lerrorif(!getClasses().exists(getClass()),"var class does not exist");
  if (var==0x00 || !getClasses().exists(getClass())) return(0x00);

  eclassBase& c(getClasses().values(getClass()));

  if (c.methods.exists(mname))
    return(findMethod(c.methods.values(mname),args));


  lerror("method not found");
//    return((*bestmatch)(var,args));
  return(0x00);
}


evar evar::call(const estr& mname,const evararray& args) const
{
  if (var==0x00) { lwarn("evar is null"); return(evar()); }

  if (var->isTypeid(typeid(evarRemote)))
    return(evarRemoteMethod(var,mname,args));

  if (!getClasses().exists(getClass())) { lwarn("class: "+estr(getClass())+" not registered"); return(evar()); }

  int bscore=0;
  eclassMethodBase *bmethod=0x00;

//  ldinfo(" args: (");
//  for (int j=0; j<args.size(); ++j)
//    ldinfo(getClassName(&args[j].getTypeid()));
//  ldinfo(")");

  if (getClasses().values(getClass()).methods.exists(mname)){
    findMethod(getClasses().values(getClass()).methods.values(mname),args,bmethod,bscore);
    if (bscore==2)
      return((*bmethod)(var,args));
  }

  int i;
  for (i=0; i<getClasses().values(var->getClass()).parents.size(); ++i){
    if (getClasses().values(var->getClass()).parents[i]->methods.exists(mname)){
//      evar tmpvar;
//      tmpvar=convert(getClasses().values(var->getClass()).parents[i]->getTypeid()).var;
//      ldieif(tmpvar.var==0x00,"class is parent but conversion failed?");
      findMethod(getClasses().values(var->getClass()).parents[i]->methods.values(mname),args,bmethod,bscore);
      if (bscore==2)
        return((*bmethod)(var,args));
    }
  }
  if (bscore>=1)
    return((*bmethod)(var,args));
  lwarn("no matching method: \""+mname+"\" in object of class: "+getClass());
  return(evar());
}

#include "etimer.h"

void evar::call(evar& rvar,const estr& mname,const evararray& args) const
{
//  eprofile p("evar::call: "+mname);

  rvar.clear();
  if (var==0x00) { lwarn("evar is null"); return; }

  if (var->isTypeid(typeid(evarRemote))){
    rvar.set(evarRemoteMethod(var,mname,args));
    return;
  }

  estr strClass(getClass());
  estrhashof<eclassBase>& classes(getClasses());

  if (!classes.exists(strClass)) { lwarn("class: "+strClass+" not registered"); return; }

  eclassBase& c(classes.values(strClass));

  int bscore=0;
  eclassMethodBase *bmethod=0x00;

//  ldinfo(" args: (");
//  for (int j=0; j<args.size(); ++j)
//    ldinfo(getClassName(&args[j].getTypeid()));
//  ldinfo(")");


  if (c.methods.exists(mname)){
    findMethod(c.methods.values(mname),args,bmethod,bscore);
    if (bscore==2){
      rvar.set((*bmethod)(var,args));
      return;
    }
  }

  int i;
  for (i=0; i<c.parents.size(); ++i){
    if (c.parents[i]->methods.exists(mname)){
//      evar tmpvar(convert(c.parents[i]->getTypeid()));
//      ldieif(tmpvar.isNull(),"class is parent but conversion failed?");
      findMethod(c.parents[i]->methods.values(mname),args,bmethod,bscore);
      if (bscore==2){
        rvar.set((*bmethod)(var,args));
        return;
      }
    }
  }
  if (bscore>=1){
    rvar.set((*bmethod)(var,args));
    return;
  }
  lwarn("no matching method: \""+mname+"\" in object of class: "+strClass);
}

evar evar::property(const estr& pname) const
{
  if (var==0x00) { lwarn("evar is null"); return(evar()); }

  if (var->isTypeid(typeid(evarRemote)))
    return(evarRemoteProperty(var,pname));

  if (!getClasses().exists(var->getClass())) { lwarn("class: "+estr(var->getClass())+" not registered"); return(evar()); }

  if (getClasses().values(var->getClass()).properties.exists(pname))
    return(getClasses().values(var->getClass()).properties.values(pname)(var));

  int i;
  for (i=0; i<getClasses().values(var->getClass()).parents.size(); ++i){
    if (getClasses().values(var->getClass()).parents[i]->properties.exists(pname)){
      evar tmpvar;
      tmpvar=convert(getClasses().values(var->getClass()).parents[i]->getTypeid()).var;
      ldieif(tmpvar.var==0x00,"class is parent but conversion failed?");
      return(getClasses().values(var->getClass()).parents[i]->properties.values(pname)(tmpvar.var));
    }
  }

  if (getClasses().values(getClass()).methods.exists(pname))
    return(evarCopy(efunc(*this,getClasses().values(getClass()).methods.values(pname).at(0))));

  for (i=0; i<getClasses().values(var->getClass()).parents.size(); ++i){
    if (getClasses().values(var->getClass()).parents[i]->methods.exists(pname)){
      evar tmpvar;
      tmpvar=convert(getClasses().values(var->getClass()).parents[i]->getTypeid()).var;
      ldieif(tmpvar.var==0x00,"class is parent but conversion failed?");
      return(evarCopy(efunc(tmpvar,getClasses().values(var->getClass()).parents[i]->methods.values(pname).at(0))));
    }
  }

  lwarn("property: \""+pname+"\" does not exist in object of class: "+var->getClass());
  return(evar());
}






evar evar::operator()(const evararray& args)
{
  return(call("()",args));
}

evar& evar::operator-=(const evar& value)
{
  call("-=",value);
  return(*this);
}

evar& evar::operator+=(const evar& value)
{
  call("+=",value);
  return(*this);
}

evar evar::operator+(const evar& value)
{
  return(call("+",value));
}

evar evar::operator-(const evar& value)
{
  return(call("-",value));
}

evar evar::operator*(const evar& value)
{
  return(call("*",value));
}

evar evar::operator/(const evar& value)
{
  return(call("/",value));
}

evar evar::operator%(const evar& value)
{
  return(call("%",value));
}

evar evar::operator+(const evar& value) const
{
  return(call("+",value));
}

evar evar::operator-(const evar& value) const
{
  return(call("-",value));
}

evar evar::operator*(const evar& value) const
{
  return(call("*",value));
}

evar evar::operator/(const evar& value) const
{
  return(call("/",value));
}

evar evar::operator%(const evar& value) const
{
  return(call("%",value));
}



evar& evar::operator++()
{
  call("++",evararray()); 
  return(*this);
}

evar& evar::operator--()
{
  call("--",evararray());
  return(*this);
}

bool evar::operator==(const evar& value) const
{
  return(((evar*)this)->call("==",value).get<bool>());
}

bool evar::operator<(const evar& value) const
{
  return(((evar*)this)->call("<",value).get<bool>());
}
bool evar::operator<=(const evar& value) const
{
  return(((evar*)this)->call("<=",value).get<bool>());
}

bool evar::operator>(const evar& value) const
{
  return(((evar*)this)->call(">",value).get<bool>());
}
bool evar::operator>=(const evar& value) const
{
  return(((evar*)this)->call(">=",value).get<bool>());
}





evar evar::operator[](const evar& value)
{
  return(call("[]",value));
}

ostream& operator<<(ostream& stream,const evar& val)
{
  if (val.isNull()) { stream << "[null]"; return(stream); }

  if (!getClasses().exists(val.getClass())) { stream << "<"<<val.getClass()<<">"; return(stream); }

  if (getClasses().values(val.getClass()).hasCout())
    return(getClasses().values(val.getClass()).fcout(stream,val.var));

  int i;
  for (i=0; i<getClasses().values(val.getClass()).parents.size(); ++i){
    if (getClasses().values(val.getClass()).parents[i]->hasCout()){
      evar tmpvar(val.convert(getClasses().values(val.getClass()).parents[i]->getTypeid()));
      ldieif(tmpvar.var==0x00,"class is parent but conversion failed?");
      return(getClasses().values(val.getClass()).parents[i]->fcout(stream,tmpvar.var));
    }
  }
  stream << "<"<<val.getClass()<<">";
  return(stream);
}


