#include "logger.h"
#include "eparser.h"
#include "edir.h"

#include "emodule.h"

#ifndef _WIN32
#include <dlfcn.h>
#endif

emodules *pModules=0x00;

emodules& getModules()
{
  if (!pModules) pModules=new emodules;
  return(*pModules);
}

void setModules(emodules& _pModules)
{
  pModules=&_pModules;
}

void emoduleInit(emoduleInfo* pmInfo)
{
  setParser(*pmInfo->parser);
  setLogger(*pmInfo->logger);
  setClasses(*pmInfo->classes);
  setClassNames(*pmInfo->classNames);
  setConverters(*pmInfo->classConverters);
}


typedef void (*emoduleRegister_fp)(emoduleInfo*);

emodule::emodule(): hModule(0x00) {}
emodule::emodule(const estr& _name): name(_name),hModule(0x00) {}

bool emodule::load()
{
  estr filename;
  if (efile(name+".so").exists())
    filename="./"+name+".so";  // "./" needed because linux only considers the filename a (relative) path if an "/" is found
  else if (efile(name+".dl").exists())
    filename=name+".dl";
  else if (efile(name+".dll").exists())
    filename=name+".dll";
  else if (efile(estr(MODULEPATH)+"/"+name+".so").exists())
    filename=estr(MODULEPATH)+"/"+name+".so";
  else if (efile(estr(MODULEPATH)+"/"+name+".dl").exists())
    filename=estr(MODULEPATH)+"/"+name+".dl";
  else if (efile(estr(MODULEPATH)+"/"+name+".dll").exists())
    filename=estr(MODULEPATH)+"/"+name+".dll";
  else {
    estrarray env_vars(env());
    if (env_vars.findkey("EUTILS_PATH")!=-1){
      estrarray paths=env_vars["EUTILS_PATH"].explode(":");
      for (int i=0; i<paths.size(); ++i){
        if (efile(paths[i]+"/"+name+".so").exists())
          { filename=paths[i]+"/"+name+".so"; break; }
        else if (efile(paths[i]+"/"+name+".dl").exists())
          { filename=paths[i]+"/"+name+".dl"; break; }
        else if (efile(paths[i]+"/"+name+".dll").exists())
          { filename=paths[i]+"/"+name+".dll"; break; }
      }
    }
    if (filename.len()==0){
      lerror("module not found: "+name);
      return(false);
    }
  }

#ifndef _WIN32
  hModule=dlopen(filename._str,RTLD_NOW | RTLD_GLOBAL);
  if (hModule==0x00){ lerror("unable to open module: "+name+" filename: "+filename+" error: "+estr(dlerror())); return(false); }

  emoduleRegister_fp moduleRegister=(emoduleRegister_fp)dlsym(hModule,"emoduleRegister");
  if (moduleRegister==0x00) { lerror("emoduleRegister function not found in module: "+name); dlclose(hModule); return(false); }
#else
  wchar_t *lpcwstr=0x00;
  wstr(&lpcwstr, filename);
  hModule = LoadLibrary(lpcwstr);
  free(lpcwstr);

  if (hModule == 0x00) { lerror("unable to open module: " + name + " filename: " + filename); return(false); }

  emoduleRegister_fp moduleRegister = (emoduleRegister_fp)GetProcAddress(hModule, "emoduleRegister");
  if (moduleRegister == 0x00) { lerror("emoduleRegister function not found in module: " + name); FreeLibrary(hModule); return(false); }
#endif

  emoduleInfo mInfo;
  mInfo.parser=&getParser();
  mInfo.logger=&getLogger();
  mInfo.classes=&getClasses();
  mInfo.classNames=&getClassNames();
  mInfo.classConverters=&getConverters();
  mInfo.module=this;
  
  (*moduleRegister)(&mInfo);
  linfo("successfully registered module: "+name);
  return(true);
}

bool emodule::unload()
{
  for (int i=0; i<funcs.size(); ++i){
    ldieif(!getParser().funcs.exists(funcs.keys(i)),"registered module function not found: "+funcs.keys(i));
    int fi=getParser().funcs[funcs.keys(i)].find(funcs.values(i));
    ldieif(fi==-1,"registered module function not found: "+funcs.keys(i));
    getParser().funcs[funcs.keys(i)].erase(fi);
    if (getParser().funcs[funcs.keys(i)].size()==0)
      getParser().funcs.remove(funcs.keys(i));
  }
  funcs.clear();
  ldie("not implemented");
#ifndef _WIN32
  dlclose(hModule);
#else
  FreeLibrary(hModule);
#endif

  return(true);
}

bool emodule::reload()
{
  return(unload() && load());
}


void emodule::registerFunc(const efunc& f,const estr& fname)
{
  funcs.add(fname,f);
}

bool emodules::load(const estr& mname)
{
  emodule newModule(mname);
  if (newModule.load()){
    modules.add(mname,newModule);
    return(true);
  }
  return(false);
}

bool emodules::unload(const estr& mname)
{
  if (!modules.exists(mname)) { lerror("module not loaded: "+mname); return(false); }
  emodule &m(modules[mname]);
  if (m.unload()){
    modules.remove(mname);
    return(true);
  }
  return(false);
}

bool emodules::reload(const estr& mname)
{
  if (!modules.exists(mname)) return(false);
  return(modules[mname].reload());
}

