/*******************************************************************************
 * libmodman - A library for extending applications
 * Copyright (C) 2009 Nathaniel McCallum <nathaniel@natemccallum.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 ******************************************************************************/

#include <algorithm>  // For sort()
#include <sys/stat.h> // For stat()
#include <iostream>

#ifdef WIN32
#include <windows.h>
#else
#include <dlfcn.h>  // For dlopen(), etc...
#include <dirent.h> // For opendir(), readdir(), closedir()
#endif

#include "module_manager.hpp"
using namespace libmodman;

#include <cstdio>

#define _LOAD_FAIL -1
#define _LOAD_LAZY  0
#define _LOAD_SUCC  1

#ifdef WIN32
#define pdlmtype HMODULE
#define pdlopenl(filename) LoadLibraryEx(filename, NULL, DONT_RESOLVE_DLL_REFERENCES)
#define pdlclose(module) FreeLibrary((pdlmtype) module)
static void* pdlsym(pdlmtype mod, string sym) {
	return GetProcAddress(mod, sym.c_str());
}

static pdlmtype pdlreopen(const char* filename, pdlmtype module) {
	pdlclose(module);
	return LoadLibrary(filename);
}

static string pdlerror() {
	std::string e;
	LPTSTR msg;

	FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
			(LPTSTR) &msg,
			0,
			NULL);
	e = std::string((const char*) msg);
    LocalFree(msg);
    return e;
}

static bool pdlsymlinked(const char* modn, const char* symb) {
	return (GetProcAddress(GetModuleHandle(modn), symb) != NULL || \
		    GetProcAddress(GetModuleHandle(NULL), symb) != NULL);
}

static string prep_type_name(string name) {
	string prefix = "<class ";
	string suffix = ",";
	if (name.find(prefix) != name.npos)
		name = name.substr(name.find(prefix) + prefix.size());
	if (name.find(suffix) != name.npos)
		name = name.substr(0, name.find(suffix));
	return name;
}
#else
#define pdlmtype void*
#define pdlopenl(filename) dlopen(filename, RTLD_LAZY | RTLD_LOCAL)
#define pdlclose(module) dlclose((pdlmtype) module)
#define pdlreopen(filename, module) module
static void* pdlsym(pdlmtype mod, string sym) {
	return dlsym(mod, sym.c_str());
}

static string pdlerror() {
	return dlerror();
}

bool pdlsymlinked(const char* modn, const char* symb) {
	void* mod = dlopen(NULL, RTLD_LAZY | RTLD_LOCAL);
	if (mod) {
		void* sym = dlsym(mod, symb);
		dlclose(mod);
		return sym != NULL;
	}
	return false;
}

#define prep_type_name(name) name
#endif

#define _str(s) #s
#define __str(s) _str(s)

#ifndef _MOD_SUFFIX
#ifdef WIN32
#define _MOD_SUFFIX "dll"
#define CR ""
#else
#define _MOD_SUFFIX "so"
#define CR "\r"
#endif
#endif

module_manager::~module_manager() {
	// Free all extensions
	for (map<string, vector<base_extension*> >::iterator i=this->extensions.begin() ; i != this->extensions.end() ; i++) {
		for (vector<base_extension*>::iterator j=i->second.begin() ; j != i->second.end() ; j++)
			delete *j;
		i->second.clear();
	}
	this->extensions.clear();

	// Free all modules
	for (set<void*>::iterator i=this->modules.begin() ; i != this->modules.end() ; i++)
		pdlclose(*i);
	this->modules.clear();
}

static int load(map<string, vector<base_extension*> >& extensions,
                             set<string>&              singletons,
                             mm_module                *mod,
                             bool                      lazy,
                             bool                      symbreq) {
	const char* debug = getenv("_MM_DEBUG");

	if (!mod || mod->vers != __MM_MODULE_VERSION || !mod->type || !mod->init) {
		if (debug)
			cerr << "failed!" << endl
			     << "\tUnable to find basic module info!" << endl;
		return _LOAD_FAIL;
	}

	// Get the module type
	string types = mod->type();

	// Make sure the type is registered
	if (extensions.find(types) == extensions.end()) {
		if (debug)
			cerr << "failed!" << endl
				 << "\tUnknown extension type: " << prep_type_name(types) << endl;
		return _LOAD_FAIL;
	}

	// If this is a singleton and we already have an instance, don't instantiate
	if (singletons.find(types) != singletons.end() &&
		extensions[types].size() > 0) {
		if (debug)
			cerr << "failed!" << endl
			     << "\tNot loading subsequent singleton for: " << prep_type_name(types) << endl;
		return _LOAD_FAIL;
	}

	// If a symbol is defined, we'll search for it in the main process
	if (mod->symb && mod->smod && !pdlsymlinked(mod->smod, mod->symb)) {
		// If the symbol is not found and the symbol is required, error
		if (symbreq) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tUnable to find required symbol: "
					 << mod->symb << endl;
			return _LOAD_FAIL;
		}

		// If the symbol is not found and not required, we'll load only
		// if there are no other modules of this type
		else if (extensions[types].size() > 0) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tUnable to find required symbol: "
					 << mod->symb << endl;
			return _LOAD_FAIL;
		}
	}

	// We've passed all the tests this far, do it again in non-lazy mode
	if (lazy) return _LOAD_LAZY;

	// If our execution test succeeds, call init()
	if ((mod->test && mod->test()) || !mod->test) {
		base_extension** exts = mod->init();
		if (!exts) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tinit() returned no extensions!" << endl;
			return _LOAD_FAIL;
		}

		if (debug)
			cerr << "success" << endl;

		// init() returned extensions we need to register
		for (unsigned int i=0 ; exts[i] ; i++) {
			if (debug)
				cerr << "\tRegistering "
					 << typeid(*exts[i]).name() << "("
					 << prep_type_name(exts[i]->get_base_type()) << ")" << endl;
			extensions[exts[i]->get_base_type()].push_back(exts[i]);
		}
		delete[] exts;
		return _LOAD_SUCC;
	}

	if (debug)
		cerr << "failed!" << endl
			 << "\tTest execution failed." << endl;
	return _LOAD_FAIL;
}

bool module_manager::load_builtin(mm_module *mod) {
	const char* debug = getenv("_MM_DEBUG");
	if (debug)
			cerr << "loading : builtin module " << mod->name << CR;

	// Do the load with the specified prefix
	int status = load(this->extensions, this->singletons, mod, false, false);
	return status == _LOAD_SUCC;
}

bool module_manager::load_file(string filename, bool symbreq) {
	const char* debug = getenv("_MM_DEBUG");

	// Stat the file to make sure it is a file
	struct stat st;
	if (stat(filename.c_str(), &st) != 0) return false;
	if ((st.st_mode & S_IFMT) != S_IFREG) return false;

	if (debug)
		cerr << "loading : " << filename << CR;

	// Open the module
	pdlmtype dlobj = pdlopenl(filename.c_str());
	if (!dlobj) {
		if (debug)
			cerr << "failed!" << endl
			     << "\t" << pdlerror() << endl;
		return false;
	}

	// If we have already loaded this module, return true
	if (this->modules.find((void*) dlobj) != this->modules.end()) {
		if (debug)
			cerr << "preload" << endl;
		pdlclose(dlobj);
		return true;
	}

	// Try and finish the load
	struct mm_module *mod_info = (mm_module*) pdlsym(dlobj, __str(__MM_MODULE_VARNAME(info)));
	int status = load(this->extensions, this->singletons, mod_info, true, symbreq);
	if (status == _LOAD_LAZY) { // Reload the module in non-lazy mode
		dlobj = pdlreopen(filename.c_str(), dlobj);
		if (!dlobj) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tUnable to reload module: " << pdlerror() << endl;
			return false;
		}
		mod_info = (mm_module*) pdlsym(dlobj, __str(__MM_MODULE_VARNAME(info)));
		status = load(this->extensions, this->singletons, mod_info, false, symbreq);
	}
	if (status == _LOAD_FAIL) {
		pdlclose(dlobj);
		return false;
	}

	// Add the dlobject to our known modules
	this->modules.insert((void*) dlobj);

	// Yay, we did it!
	return true;
}

bool module_manager::load_dir(string dirname, bool symbreq) {
	vector<string> files;

#ifdef WIN32
	WIN32_FIND_DATA fd;
	HANDLE search;

	string srch = dirname + "\\*." + _MOD_SUFFIX;
	search = FindFirstFile(srch.c_str(), &fd);
	if (search != INVALID_HANDLE_VALUE) {
		do {
			files.push_back(dirname + "\\" + fd.cFileName);
		} while (FindNextFile(search, &fd));
		FindClose(search);
	}
#else
	struct dirent *ent;

	DIR *moduledir = opendir(dirname.c_str());
	if (moduledir) {
		while((ent = readdir(moduledir))) {
			string tmp = ent->d_name;
			if (tmp.find(_MOD_SUFFIX, tmp.size() - string(_MOD_SUFFIX).size()) != tmp.npos)
				files.push_back(dirname + "/" + tmp);
		}
		closedir(moduledir);
	}
#endif

	// Perform our load alphabetically
	sort(files.begin(), files.end());

	// Try to do the load
	bool loaded = false;
	for (vector<string>::iterator it = files.begin() ; it != files.end() ; it++)
		loaded = this->load_file(*it, symbreq) || loaded;
	return loaded;
}
