#include <precomp.h>

#include <bfc/wasabi_std.h>

#include "compon.h"
//#include <api/wac/main.h> // CUT!

#ifndef WASABINOMAINAPI
#include <api/metadb/metadb.h>
#include <api/wac/papi.h>
#endif
#include <api/script/objects/compoobj.h>
#include <api/wndmgr/container.h>
#include <api/skin/skinparse.h>
#include <api/wac/wac.h>
#include <api/script/objects/wacobj.h>
#include <api/wnd/wndtrack.h>
#include <api/script/objecttable.h>

#include <api/config/items/cfgitemi.h>

#include <bfc/loadlib.h>
//#include <bfc/util/profiler.h>
#include <api/locales/xlatstr.h>
#include <bfc/file/recursedir.h>

#include <api/service/svc_enum.h>
#include <api/service/services.h>
#include <api/service/servicei.h>
#include <api/skin/skin.h>
#include <api/script/scriptmgr.h>
#include <bfc/parse/pathparse.h>

#ifndef WASABINOMAINAPI
#include <api/api1.h>
#endif

#include <api/application/wkc.h>
#include <api/wndmgr/skinembed.h>
#include <api/script/objects/compoobj.h>

#include <api/wnd/usermsg.h>
#include <bfc/util/inifile.h>

class CShutdownCallback {
  public:
  virtual void rl_onShutdown()=0;
};


extern GUID baseGUID;

static TList<GUID> loadlist, banlist;

// compon.cpp : maintains the list of installed components, and installs
// each one

// keep a list of component pointers as well as instances
class component_slot 
{
public:
#ifndef WASABINOMAINAPI
   component_slot(Library *_dll, WaComponent *_wac, ComponentAPI *_api, GUID g, const wchar_t *orig_path) 
     : dll(_dll), wac(_wac), path(orig_path), guid(g), componentapi(_api) {
#else
   component_slot(Library *_dll, WaComponent *_wac, GUID g, const wchar_t *orig_path) 
     : dll(_dll), wac(_wac), path(orig_path), guid(g) {
#endif
    int fnlen = wcslen(Wasabi::Std::filename(orig_path));
    path.trunc(-fnlen);
    postonregsvcs = 0;
    postoncreate = 0;
    loadearly = 0;	// will be filled in later
  }
  ~component_slot() {
#ifndef WASABINOMAINAPI
    if (dll != NULL) PAPI::destroyAPI(componentapi);
#endif
    delete dll;
  }
  void registerServices() {
#ifndef WASABINOMAINAPI
    if (!postonregsvcs) wac->registerServices(componentapi);
#else
    if (!postonregsvcs) wac->registerServices(WASABI_API_SVC);
#endif
    postonregsvcs = 1;
  }
  void onCreate() {
    if (!postoncreate) wac->onCreate();
    postoncreate = 1;
  }

  Library *dll;
  WaComponent *wac; 	// we don't delete this
  StringW path;
  GUID guid;		// prevent spoofing
#ifndef WASABINOMAINAPI
  ComponentAPI *componentapi;
#endif
  int postoncreate;
  int postonregsvcs;
  int loadearly;
};

static PtrList<component_slot> components;

/*static PtrList<cd_entry> cd_list;
static PtrList<ComponentObject> co_list;*/

// the minimum SDK version # we accept
const int WA_COMPONENT_VER_MIN = WA_COMPONENT_VERSION;

  static int postComponentCommand(GUID guid, const wchar_t *command, int p1, int p2, void *ptr, int ptrlen, int waitforanswer);

class ComponPostEntry {
public:
  ComponPostEntry(GUID pguid, const wchar_t *pcommand, int pp1, int pp2, void *pptr, int pptrlen, int pwait) :
    guid(pguid), command(pcommand), p1(pp1), p2(pp2), ptr(pptr), ptrlen(pptrlen), waitforanswer(pwait),
      posted(0), result(0) { }
  GUID guid;
  StringW command;
  int p1, p2;
  void *ptr;
  int ptrlen;
  int waitforanswer;
  int posted;
  int result;
};

static PtrList<StringW> preloads;

void ComponentManager::addStaticComponent(WaComponent *component) {
  GUID guid = component->getGUID();
  if (!checkGUID(guid)) return;	// guid check (banlist etc.)

#ifndef WASABINOMAINAPI
  // reuse main api *
  components.addItem(new component_slot(NULL, component, api, guid, NULL));
#else
  components.addItem(new component_slot(NULL, component, guid, NULL));
#endif
}

void ComponentManager::addPreloadComponent(const wchar_t *filename)
{
  foreach(preloads)
    if (PATHEQL(filename, preloads.getfor()->getValue())) return;// no dups
  endfor
  preloads.addItem(new StringW(filename));
}

void ComponentManager::loadPreloads() {
  foreach(preloads)
    load(preloads.getfor()->getValue());
  endfor
  preloads.deleteAll();
}

void ComponentManager::load(const wchar_t *filename) {
  // ensure no duplicate filenames
  foreach(components)
    if (PATHEQL(filename, components.getfor()->dll->getName())) return;
  endfor

  #ifdef WA3COMPATIBILITY
  // let kernel controller test the file
  WasabiKernelController *wkc = Main::getKernelController();
  if (wkc && !wkc->testComponent(filename)) return;
  #endif

  // check if they have an ini (to get guid faster)
  StringW inifile = filename;
  const wchar_t *ext = Wasabi::Std::extension(inifile);
	int len = wcslen(ext);
  inifile.trunc(-len);
  inifile.cat(L"ini");
  IniFile ini(inifile);
  GUID ini_guid = ini.getGuid(L"component", L"guid");
  if (!checkGUID(ini_guid, TRUE)) return;

//  PR_ENTER2("load component", filename);

  // attach the DLL
  Library *dll = new Library(filename);
  if (!dll->load()) {
    delete dll;
    return;
  }

  // check the version of SDK it was compiled with
  WACGETVERSION wac_get_version = (WACGETVERSION)dll->getProcAddress("WAC_getVersion");
  if (wac_get_version == NULL) {
    delete dll;
    return;
  }

  int version = (*wac_get_version)();
  if (version < WA_COMPONENT_VER_MIN ||	// defined above
      version > WA_COMPONENT_VERSION) {	// from wac.h
    delete dll;
    return;
  }

  // init the dll itself
  WACINIT wacinit = (WACINIT)dll->getProcAddress("WAC_init");
  if (wacinit != NULL) (*wacinit)(dll->getHandle());

  WACENUMCOMPONENT wec = (WACENUMCOMPONENT)dll->getProcAddress("WAC_enumComponent");
  if (wec == NULL) {
    delete dll;
    return;
  }

  // fetch the pointer
  WaComponent *wac = (*wec)(0);

  GUID guid = wac->getGUID();

  if (ini_guid != INVALID_GUID && guid != ini_guid) {
    delete dll;
    DebugString("guids didn't match! %s", filename);
    return;
  }

  // check if we want to load this GUID
  if (!checkGUID(guid)) {
    delete dll;
    return;
  }

#ifndef WASABINOMAINAPI
  // allocate an api pointer bound to their GUID
  ComponentAPI *newapi = PAPI::createAPI(wac, guid);
  if (newapi == NULL) {
    delete dll;
    return;
  }
#endif

  PathParserW pp(filename);
  StringW path;
  for (int i=0;i<pp.getNumStrings()-1;i++) 
	{
		path.AppendFolder(pp.enumString(i));
  }

  wac->setComponentPath(path);

  // keep track of dll handles for shutdown
  components.addItem(new component_slot(dll, wac, 
#ifndef WASABINOMAINAPI
  newapi, 
#endif
  guid, filename));

//  PR_LEAVE();
}

const wchar_t *ComponentManager::getComponentPath(GUID g) {
  foreach(components)
    if (g == components.getfor()->guid) {
      return components.getfor()->path;
    }
  endfor
  return NULL;
}

void ComponentManager::startupDBs() {
/*  for (int i=0;i<components.getNumItems();i++)
    MetaDB::addComponentDB(components.enumItem(i)->wac);*/
}

void ComponentManager::shutdownDBs() {
#ifndef WASABINOMAINAPI
  for (int i = 0; i < components.getNumItems(); i++) {
    //MetaDB::getBaseDB()->removeComponentDB(components[i]->wac);
    (static_cast<ComponentAPI1 *>(components[i]->componentapi))->shutdownDB();
  }
#else
//MULTIAPI-FIXME: solve the shutdownDB puzzle
#endif
}

void ComponentManager::unloadAll() {
  // cable out! let 'er go!
//  deleteAllCD(); // deletes compwnds
  foreach(components)
    components.getfor()->wac->deregisterServices();
  endfor
  foreach(components)
    components.getfor()->wac->onDestroy();
  endfor
  components.deleteAll();	// free the DLLs, and kill their API *'s
}

int ComponentManager::checkGUID(GUID &g, int invalid_ok) {
  // no invalid guid
  if (!invalid_ok && g == INVALID_GUID) return FALSE;

  // check against banlist
  if (banlist.haveItem(g)) return FALSE;

  // check against load-only list
  if (loadlist.getNumItems() && !loadlist.haveItem(g)) return FALSE;

  // ensure no duplicate GUIDs
  foreach(components)
    if (g == components.getfor()->guid) {
//CUT      StringPrintf s("%s and %s", components.getfor()->dll->getName(), filename);
//CUT      Std::messageBox(s, "Duplicate component guid", MB_OK);
      return FALSE;
    }
  endfor

  // ok
  return TRUE;
}

WaComponent *ComponentManager::enumComponent(int component) {
  if (component < 0 || component >= components.getNumItems()) return NULL;
  return components[component]->wac;
}

void ComponentManager::loadAll(const wchar_t *path) {

#if 0//CUT
  static const char *loadorder[] = {
    "wasabi.system/pngload.wac",
    "wasabi.player/core.wac",
    "metrics.wac", // so metrics dialog appears before the splash screen
    "winamp/winamp.wac", // so splash screen displays right after startup
    "winamp/pledit.wac",
    "winamp/library.wac",
    "preferences.wac", // so prefs has the system groups at the top
    "skinswitch.wac", // so skinswitch is the first non internal prefs screen, ignored if not present. fucko: need to modify prefs system so we don't need to load in any particular order
    NULL
  };

  for (int i = 0; loadorder[i] != NULL; i++) {
    StringPrintf fn("%s%s%s", WACDIR, DIRCHARSTR, loadorder[i]);
    ComponentManager::load(fn);
  }
#endif
	RecurseDir dir(path, L"*.*");// have to do *.* to get subdirs
  while (dir.next()) {
		StringPathCombine fn(dir.getPath(), dir.getFilename());
    const wchar_t *ext = Wasabi::Std::extension(fn);
    if (!WCSICMP(ext, L"wac"))
      ComponentManager::load(fn);
  }
}

void ComponentManager::postLoad(int f) { // two-level startup procedure
  // note we're calling the slot, not the component directly

  // allow punk-ass bitches to load early if need be
  foreach(components)
    if ((components.getfor()->loadearly = !!components.getfor()->wac->onNotify(WAC_NOTIFY_LOADEARLY))) {
      components.getfor()->registerServices();
    }
  endfor

  foreach(components)
    if (!components.getfor()->loadearly)
      components.getfor()->registerServices();
  endfor
  foreach(components)
    components.getfor()->onCreate();
  endfor
  if (f) ObjectTable::loadExternalClasses();
}

void ComponentManager::broadcastNotify(int cmd, int param1, int param2) {
  foreach(components)
    if ((components.getfor()->loadearly = !!components.getfor()->wac->onNotify(WAC_NOTIFY_LOADEARLY, cmd, param1, param2)))
      components.getfor()->wac->onNotify(cmd, param1, param2);
  endfor
  foreach(components)
    if (!components.getfor()->loadearly)
      components.getfor()->wac->onNotify(cmd, param1, param2);
  endfor
}

void ComponentManager::sendNotify(GUID guid, int cmd, int param1, int param2) {
  WaComponent *wac = getComponentFromGuid(guid);
  if (wac)
    wac->onNotify(cmd, param1, param2);
}

int ComponentManager::sendCommand(GUID guid, const wchar_t *command, int p1, int p2, void *ptr, int ptrlen) {
  if (command == NULL) return 0;
  WaComponent *wac = getComponentFromGuid(guid);
  if (wac) return wac->onCommand(command, p1, p2, ptr, ptrlen);
  return 0;
}

int ComponentManager::postCommand(GUID guid, const wchar_t *command, int p1, int p2, void *ptr, int ptrlen, int waitforanswer) {
#ifdef WA3COMPATIBILITY // todo: make thread id part of application api
  if(Std::getCurrentThreadId()==Main::getThreadId() && waitforanswer) {
    // if it is already the main thread calling, just pass the command to sendCommand
    return sendCommand(guid,command,p1,p2,ptr,ptrlen);
  }
#endif
  ComponPostEntry *cpe=new ComponPostEntry(guid,command,p1,p2,ptr,ptrlen,waitforanswer);
  componPostEntries.addItem(cpe);
#ifdef WIN32
#ifdef WA3COMPATIBILITY // todo: make this a call to application api
  PostMessage(Main::gethWnd(),UMSG_COMPON_POSTMESSAGE,0,0); // ask the main thread to call mainthreadpostCommands();
#endif
#else
  PostMessage(None,UMSG_COMPON_POSTMESSAGE,0,0); // ask the main thread to call mainthreadpostCommands();
#endif
  if(waitforanswer) {
    while(!cpe->posted) Sleep(1);
    int res=cpe->result;
    componPostEntries.removeItem(cpe);
    delete(cpe);
    return res;
  }
  return 0; // cpe will get deleted by mainthreadpostCommands();
}


void ComponentManager::broadcastCommand(const wchar_t *command, int p1, int p2, void *ptr, int ptrlen) {
  if (command == NULL) return;
  for (int i = 0; i < components.getNumItems(); i++) {
    components[i]->wac->onCommand(command, p1, p2, ptr, ptrlen);
  }
}

int ComponentManager::getNumComponents() {
  return components.getNumItems();
}

GUID ComponentManager::getComponentGUID(int c) {
  if (c >= components.getNumItems()) return INVALID_GUID;
  return components[c]->guid;
}

const wchar_t *ComponentManager::getComponentName(GUID g)
{
  WaComponent *wac = getComponentFromGuid(g);
  if (wac)
    return wac->getName();
  return NULL;
}

CfgItem *ComponentManager::getCfgInterface(GUID g) {
  WaComponent *wac = getComponentFromGuid(g);
  if (wac == NULL) return NULL;
  return wac->getCfgInterface(0);
}

WaComponent *ComponentManager::getComponentFromGuid(GUID g) {
  if (g == INVALID_GUID) return NULL;
  for (int i=0;i<components.getNumItems();i++) {
    if (g == components[i]->guid)
      return components[i]->wac;
  }
  return NULL;
}

void ComponentManager::mainThread_handlePostCommands() {
  // critical section
  for(int i=0;i<componPostEntries.getNumItems();i++) {
    ComponPostEntry *cpe=componPostEntries[i];
    if(!cpe->posted) {
      sendCommand(cpe->guid,cpe->command.getValue(),cpe->p1,cpe->p2,cpe->ptr,cpe->ptrlen);
      if(cpe->waitforanswer) cpe->posted=1;
      else {
        delete cpe;
        componPostEntries.removeByPos(i);
        i--;
      }
    }
  }
}

PtrList<ComponPostEntry> ComponentManager::componPostEntries;