#ifndef EVAR_H
#define EVAR_H

#include "eutils.h"
#include <typeinfo>

#include "evar_dec.h"
#include "ebasicarray_dec.h"
#include "earray_dec.h"
#include "earrayof_dec.h"
#include "ehashmap_dec.h"
#include "efunc_dec.h"
#include "evarclass_dec.h"

#include "ebasicarray.h"
#include "earray.h"
#include "earrayof.h"
#include "ehashmap.h"
#include "efunc.h"
#include "evarclass.h"

#include "evarbase.h"
#include "evartype.h"

#include "logger.h"

#include "estr.h"
#include "efile.h"

//#include "estrarray.h"


#if __clang_major__ >= 5

template <typename R, typename ...Args>
evar::evar(R(*f)(Args...))
{
//  var=new evarTypeClean<efunc>(new efunc(f.target)); // stopped using efunc because it cannot be easily used to pass function pointers back when calling functions or methods
//  var=new evarType<efunc>(f.target);
  var=new evarType<R(Args...)>(f);
  retain();
}

/*
template <typename T>
evar::evar(std::function<T> f)
{
//  var=new evarTypeClean<efunc>(new efunc(f.target)); // stopped using efunc because it cannot be easily used to pass function pointers back when calling functions or methods
//  var=new evarType<efunc>(f.target);
  var=new evarType(f.target);
  retain();
}
*/

#else /* if not clang version == 5 */

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

template <typename T1,typename T2>
evar::evar(void f(T1,T2))
{
  var=new evarType<void (T1,T2)>(f);
  retain();
}

template <typename T1,typename T2,typename T3>
evar::evar(void f(T1,T2,T3))
{
  var=new evarType<void (T1,T2,T3)>(f);
  retain();
}

template <typename T1,typename T2,typename T3,typename T4>
evar::evar(void f(T1,T2,T3,T4))
{
  var=new evarType<void (T1,T2,T3,T4)>(f);
  retain();
}

template <typename T1,typename T2,typename T3,typename T4,typename T5>
evar::evar(void f(T1,T2,T3,T4,T5))
{
  var=new evarType<void (T1,T2,T3,T4,T5)>(f);
  retain();
}

template <typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
evar::evar(void f(T1,T2,T3,T4,T5,T6))
{
  var=new evarType<void (T1,T2,T3,T4,T5,T6)>(f);
  retain();
}


template <typename R>
evar::evar(R f())
{
  var=new evarType<R ()>(f);
  retain();
}

template <typename R,typename T1>
evar::evar(R f(T1))
{
  var=new evarType<R (T1)>(f);
  retain();
}

template <typename R,typename T1,typename T2>
evar::evar(R f(T1,T2))
{
  var=new evarType<R (T1,T2)>(f);
  retain();
}

template <typename R,typename T1,typename T2,typename T3>
evar::evar(R f(T1,T2,T3))
{
  var=new evarType<R (T1,T2,T3)>(f);
  retain();
}

template <typename R,typename T1,typename T2,typename T3,typename T4>
evar::evar(R f(T1,T2,T3,T4))
{
  var=new evarType<R (T1,T2,T3,T4)>(f);
  retain();
}

template <typename R,typename T1,typename T2,typename T3,typename T4,typename T5>
evar::evar(R f(T1,T2,T3,T4,T5))
{
  var=new evarType<R (T1,T2,T3,T4,T5)>(f);
  retain();
}

template <typename R,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
evar::evar(R f(T1,T2,T3,T4,T5,T6))
{
  var=new evarType<R (T1,T2,T3,T4,T5,T6)>(f);
  retain();
}
#endif /* else if clang version == 5 */

#include "evarclassconverter.h"


template <typename T>
evar::evar(T* value)
{
  if (typeid(*value)!=typeid(T) && getClasses().exists(typeid(T).name()) && getClasses().exists(typeid(*value).name())){
    eclassConverterBase *converter=getConverter(typeid(*value),typeid(T));
    if (converter) {
      evarType<T> tmpvar(value);
      var=(*converter)( &tmpvar ); 
      retain();
      return;
    }
  }
  var=new evarType<T>(value);
  retain();
}
template <typename T>
evar::evar(const T* value)
{
  if (typeid(*value)!=typeid(T) && getClasses().exists(typeid(T).name()) && getClasses().exists(typeid(*value).name())){
    eclassConverterBase *converter=getConverter(typeid(*value),typeid(T));
    if (converter) {
      evarType<T> tmpvar((T*)value);
      var=(*converter)( &tmpvar ); 
      retain();
      return;
    }
  }
   
  var=new evarType<T>((T*)value);
  retain();
}
template <typename T>
evar::evar(const T& value)
{
  if (typeid(value)!=typeid(T) && getClasses().exists(typeid(T).name()) && getClasses().exists(typeid(value).name())){
    eclassConverterBase *converter=getConverter(typeid(value),typeid(T));
    if (converter) {
      evarType<T> tmpvar((T&)value);
      var=(*converter)( &tmpvar ); 
      retain();
      return;
    }
  }
  var=new evarTypeClean<T>(new T(value));
//  var=new evarType<T>((T&)value);

//  if (getClasses().exists(typeid(T).name()))
//    var=getClasses()[typeid(T).name()].fcopy(var);
  retain();
}
template <typename T>
evar::evar(T& value)
{
  // check for polymorphic types and store value with right polymorphic class
  if (typeid(value)!=typeid(T) && getClasses().exists(typeid(T).name()) && getClasses().exists(typeid(value).name())){
    eclassConverterBase *converter=getConverter(typeid(value),typeid(T));
    if (converter) {
      evarType<T> tmpvar(value);
      var=(*converter)( &tmpvar ); 
      retain();
      return;
    }
  }
  var=new evarTypeClean<T>(new T(value));
//  var=new evarType<T>((T&)value);

//  if (getClasses().exists(typeid(T).name()))
//    var=getClasses()[typeid(T).name()].fcopy(var);
  retain();
}




template <typename T>
T& evar::get() const
{
  if (var==0x00) { lerror("empty var"); throw("trying to get from empty envar"); }

  if (var->getTypeid() == typeid(T))
    return(*dynamic_cast<evarType<T>* >(var)->object);

  lwarn("unable to get variable, requested: "+estr(typeid(T).name())+" var: "+estr(var->getTypeid().name()));
  throw("unable to get variable");
}

template <typename T>
T* evar::getarg(void (*pfunc)(T*)) const
{
  if (var==0x00) { lwarn("empty var"); throw("trying to get from empty envar"); }
  if (typeid(T)==var->getTypeid())
    return(dynamic_cast< evarType<T>* >(var)->object);
  lwarn("unable to get variable, requested: "+estr(typeid(T).name())+" var: "+estr(var->getTypeid().name()));
  throw("unable to get variable");
}
template <typename T>
T& evar::getarg(void (*pfunc)(T)) const
{
  if (var==0x00) { lwarn("empty var"); throw("trying to get from empty envar"); }
  if (typeid(T)==var->getTypeid())
    return(*dynamic_cast< evarType<T>* >(var)->object);
  lwarn("unable to get variable, requested: "+estr(typeid(T).name())+" var: "+estr(var->getTypeid().name()));
  throw("unable to get variable");
}
template <typename T>
const T& evar::getarg(void (*pfunc)(const T&)) const
{
  if (var==0x00) { lwarn("empty var"); throw("trying to get from empty envar"); }
  if (typeid(T)==var->getTypeid())
    return(*dynamic_cast< evarType<T>* >(var)->object);
  lwarn("unable to get variable, requested: "+estr(typeid(T).name())+" var: "+estr(var->getTypeid().name()));
  throw("unable to get variable");
}
template <typename T>
T& evar::getarg(void (*pfunc)(T&)) const
{
  if (var==0x00) { lwarn("empty var"); throw("trying to get from empty envar"); }
  if (typeid(T)==var->getTypeid())
    return(*dynamic_cast< evarType<T>* >(var)->object);
  lwarn("unable to get variable, requested: "+estr(typeid(T).name())+" var: "+estr(var->getTypeid().name()));
  throw("unable to get variable");
}

/*
template <typename T>
T& evar::getarg(void (*pfunc)(T&&)) const
{
  if (var==0x00) { lwarn("empty var"); throw("trying to get from empty envar"); }
  if (typeid(T)==var->getTypeid())
    return(*dynamic_cast< evarType<T>* >(var)->object);
  lwarn("unable to get variable, requested: "+estr(typeid(T).name())+" var: "+estr(var->getTypeid().name()));
  throw("unable to get variable");
}
*/



#include "evarclassconverter.h"
#include "evarclass.h"

template <typename T>
void json_serial(const T& value,estr& data)
{
  evar var(value);
//  var.var->cleanup=false;
  if (!getClasses().exists(var.getClass())) { lerror(estr("class not registered: ")+var.getClass()); return; }

  getClasses().values(var.getClass()).fjson_serial(var.var,data);
}

template <typename T>
void serial(const T& value,estr& data)
{
  evar var(value);
//  var.var->cleanup=false;
  if (!getClasses().exists(var.getClass())) { lerror(estr("class not registered: ")+var.getClass()); return; }

  getClasses().values(var.getClass()).fserial(var.var,data);
}

template <typename T>
size_t unserial(T& value,const estr& data,size_t i)
{
  evar var(value);
//  var.var->cleanup=false;
  if (!getClasses().exists(var.getClass())) { lerror(estr("class not registered: ")+var.getClass()); return(i); }
  return(getClasses().values(var.getClass()).funserial(var.var,data,i));
}

template <typename T>
evar::operator T() const
{
  if (var==0x00) return(T());

  if (getTypeid() == typeid(T))
    return(getarg((void (*)(T))0x00));
  if (getPTypeid() == typeid(T))
    return(getarg((void (*)(T))0x00));

  if (isConvertible(typeid(T))){
    evar nvar(convert(typeid(T)));
    return(nvar.getarg((void(*)(T))0x00));
  }
  lerror(estr("unable to convert evar type: ")+getClass()+" to: "+typeid(T).name());
  return(T());
}

template <typename T>
evar evarCopy(const T& obj)
{
/*
  if (typeid(obj)!=typeid(T) && getClasses().exists(typeid(T).name()) && getClasses().exists(typeid(obj).name())){
    eclassConverterBase *converter=getConverter(typeid(obj),typeid(T));
    if (converter) {
      evarType<T> tmpvar((T&)obj);
      return(evar((*converter)( &tmpvar ))); 
    }
  }
*/
  return(static_cast<evarBase*>(new evarTypeClean<T>(new T(obj))));
}

/*
template <typename T>
evar evarCopy(T* const obj)
{
  cout << "evarCopy pointer" << endl;
  return(static_cast<evarBase*>(new evarType<T>(obj)));
}
*/

template <typename T>
evar evarRef(T* obj)
{
  if (typeid(*obj)!=typeid(T) && getClasses().exists(typeid(T).name()) && getClasses().exists(typeid(*obj).name())){
    eclassConverterBase *converter=getConverter(typeid(*obj),typeid(T));
    if (converter) {
      evarType<T> tmpvar((T*)obj);
      return(evar((*converter)( &tmpvar ))); 
    }
  }
  return(static_cast<evarBase*>(new evarType<T>((T*)obj)));
}

template <typename T>
evar evarRef(T& obj)
{
  if (typeid(obj)!=typeid(T) && getClasses().exists(typeid(T).name()) && getClasses().exists(typeid(obj).name())){
    eclassConverterBase *converter=getConverter(typeid(obj),typeid(T));
    if (converter) {
      evarType<T> tmpvar((T&)obj);
      return(evar((*converter)( &tmpvar ))); 
    }
  }
  return(static_cast<evarBase*>(new evarType<T>((T&)obj)));
}

#endif

