/***************************************************************************
                          mpluginmanager.cpp  -  description
                             -------------------
    begin                : ? 10? 27 2003
    copyright            : (C) 2003 by Sheldon Lee Wen
    email                : leewsb@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "mpluginmanager.h"
#include "plugin_definitions.h"
#include "lineak_util_functions.h"
#include <map>
#include <iostream>
#include <algorithm>

//#ifdef HAVE_XOSD
//#include "xosdctrl.h"
//#endif
#include "displayctrl.h"
#include "lkbd.h"
#include "lconfig.h"

extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <dlfcn.h>
}

extern bool verbose;
extern bool global_enable;

MacroPluginManager::MacroPluginManager() {
//        cout << "Constructing a plugin manager" << endl;
}
MacroPluginManager::~MacroPluginManager(){
//        cout << "Destructing plugin manager!" << endl;
//	unloadAllPlugins();
   if (verbose) {
      if (!plugin_map.empty())
         cerr << "Destructing MacroPluginManager and the plugins have not been unloaded!" << endl;
   }
}
bool MacroPluginManager::loadPlugin(const string& plugin) {
        exec_t exec;
        initialize_display_t initialize_display;
        initialize_t initialize;
        cleanup_t cleanup;
        macrolist_t macro_list;
	identifier_t identifier;
	identifier_info*plugin_ident;

        void *handle;
        const char *errmsg;
        macro_plugin_info plinfo;

  	if (plugin == "" || plugin == snull) {
		cout << "No plugin to load!" << endl;
		return false;
	}

	if (verbose) cout << "Loading plugin: " << plugin << endl;
	handle = dlopen(plugin.c_str(),RTLD_NOW);
         // If we cannot dlopen the plugin return false;
	if (handle == NULL) {
		cerr << "Failed to load plugin " << plugin << ": " << dlerror() << endl;
		dlerror();
		//dlclose(handle);
		return false;
	}
	dlerror();
	/** Once we have dlopened the plugin. We must we able to get all of the requisite
	interfaces, or else we skip the plugin all together. The only non-requisite interface
	is the initialize_display interface */

	// Attempt to get the plugin identifier. This will prevent us from loading the same plugin more than once.
	identifier = (identifier_t)dlsym(handle, "identifier");
	if((errmsg = dlerror()) != NULL) {
		cerr << "Didn't find identifier() interface: " << errmsg << endl;
		exec = NULL;
		dlerror();
		dlclose(handle);
		return false;
	} else {
		plugin_ident = identifier();
		string name = string(plugin_ident->identifier);
		string type = string(plugin_ident->type);
		if (name == "" || name == snull || type != "MACRO" ) {
			cerr << "The plugin identifier is empty or is of the incorrect type. This is an invalid macro plugin" << endl;
			dlerror();
			dlclose(handle);
			return false;
		} else {
		// Check to see if that plugin is already registered. If so, skip to the next one.
			if (plugin_map.find((plugin_ident->identifier)) != plugin_map.end()) {
				cerr << "Plugin: " << plugin_ident->identifier << " is already registered!" << endl;
				dlerror();
				dlclose(handle);
				return false;
			}
		}
	}
	dlerror();

	// Attempt to open the exec interface.
	exec = (exec_t)dlsym(handle, "exec");
	if((errmsg = dlerror()) != NULL) {
		cerr << "Didn't find exec() interface: " << errmsg << endl;
		exec = NULL;
		dlerror();
		dlclose(handle);
		return false;
	}
	dlerror();
	// Attempt to open the macrolist interface
	macro_list = (macrolist_t)dlsym(handle,"macrolist");
	if((errmsg = dlerror()) != NULL) {
		cerr << "Didn't find macrolist() interface: " << errmsg << endl;
		macro_list = NULL;
		dlerror();
		dlclose(handle);
		return false;
	}
	dlerror();
	// Attempt to open the initialize interface. I have elected not to use _init b/c it may be borked
	// on certain versions of Linux and across platform.
	initialize = (initialize_t)dlsym(handle,"initialize");
	if((errmsg = dlerror()) != NULL) {
		cerr << "Didn't find initalize() interface: " << errmsg << endl;
		initialize = NULL;
		dlerror();
		dlclose(handle);
		return false;
	}
	dlerror();
/* #ifdef HAVE_XOSD
	// Attempt to initialize the xosd portion of the plugin.
	initialize_display = (initialize_display_t)dlsym(handle,"initialize_display");
	if((errmsg = dlerror()) != NULL) {
		cerr << "Didn't find initalize() interface: " << errmsg << endl;
		initialize_display = NULL;
	}
	dlerror();
#endif */
	// Attempt to initialize the xosd portion of the plugin.
	initialize_display = (initialize_display_t)dlsym(handle,"initialize_display");
	if((errmsg = dlerror()) != NULL) {
		cerr << "Didn't find initalize_display() interface: " << errmsg << endl;
		initialize_display = NULL;
	}
	dlerror();

	// Attempt to resolve the cleanup interface for the plugin.
	cleanup = (cleanup_t)dlsym(handle,"cleanup");
	if((errmsg = dlerror()) != NULL) {
		cerr << "Didn't find cleanup() interface: " << errmsg << endl;
		cleanup = NULL;
		dlerror();
		dlclose(handle);
		return false;
	}
	dlerror();

	/** Build the data structure and add it to the plugin_map */
	plinfo.info.filename = plugin;
	plinfo.info.handle = handle;
	plinfo.info.identifier = plugin_ident;
	plinfo.exec = exec;
	plinfo.macrolist = macro_list;
	plinfo.info.initialize = initialize;
	plinfo.info.initialize_display = initialize_display;
	plinfo.info.initialized_display = false;
	//plinfo.have_display = false;
	plinfo.info.cleanup = cleanup;
	plinfo.info.loaded = true;
	plinfo.info.initialized = false;
	plinfo.macros_defined = false;
	plugin_map[string(plugin_ident->identifier)] = plinfo;


	return true;
}
vector<string> MacroPluginManager::loadPlugins(vector<string> myplugins) {
	string plugin;
	vector<string> loadedplugins;
        unsigned int size = plugin_map.size();
        unsigned int numplugins = myplugins.size();
	//bool loadflag = true;

	if (myplugins.empty()) {
		if (verbose) cout << "No plugins to load!!" << endl;
                return loadedplugins;
        }
        // Attempt to load all of the plugins.
        vector<string>::iterator it =myplugins.begin();
        for (;it!= myplugins.end(); it++) {
		plugin = *it;
		if(!loadPlugin(plugin)) {
			cerr << "Plugin: " << plugin << " failed to load!" << endl;
		}
		else
			loadedplugins.push_back(plugin);
    	}
    	if (plugin_map.size() != (size + numplugins)) {
    		if (verbose) cerr << "Could not load all plugins" << endl;
        return loadedplugins;
    }

    return loadedplugins;
}

bool MacroPluginManager::exec(LKey* imyKey) {
         map<string,macro_plugin_info>::iterator it = plugin_map.begin();
 	 vector<string>::iterator is;
         vector<string>::iterator siter;
         exec_t exec;
 	 LCommand command = imyKey->getCommand();
         //cout << "plugin_manager global_enable = " << global_enable << endl;
 	/** Go through the plugin_map and for each plugin, look at their list of supported macros.
 	    If we find a match, then call that plugins exec */
	for (;it != plugin_map.end(); it++) {
                /** iter will either point to the end() or the element of the current plugin that
                    contains the macro. */

                siter = find((it->second).macros.begin(),(it->second).macros.end(),command.getMacroType());
                if (siter != (it->second).macros.end())
                        break;

	}
        if ( it != plugin_map.end() ) {
                if (verbose) cout << "Plugin: " << (it->first) << " to execute macro " << command.getMacroType() << endl;
                exec = (it->second).exec;
		if (exec != NULL) {
                        //cout << " Calling exec(imyKey)" << endl;
			exec(imyKey);
                        return true;
                }
	}
        // No plugins could be found.
        return false;

}
/** Unload a plugin */
void MacroPluginManager::unloadPlugin(const string& plugin) {
	void* handle;
	cleanup_t cleanup;
	if (verbose) cout << "MacroPluginManager: cleaning up plugin " << plugin << endl;
	handle = plugin_map[plugin].info.handle;
	cleanup = plugin_map[plugin].info.cleanup;
	if (cleanup != NULL)
                 cleanup();
        if (verbose) cout << "MacroPluginManager: closing plugin " << plugin << endl;
        if (handle != NULL) {
                cout << handle << endl;
                dlclose(handle);
        }
	plugin_map.erase(plugin);

}
/** Unloads all loaded plugins. */
void MacroPluginManager::unloadAllPlugins(){
   map<string,macro_plugin_info>::iterator it = plugin_map.begin();
   //void* handle;
   //cleanup_t cleanup;
        if (verbose) cout << "MacroPluginManager is unloading plugins" << endl;
	for (;it != plugin_map.end(); it++) {
		/*
		if (verbose) cout << "MacroPluginManager: cleaning up plugin " << (it->first) << endl;
		handle = (it->second).info.handle;
		cleanup = (it->second).info.cleanup;
		if (cleanup != NULL)
			cleanup();
		if (verbose) cout << "MacroPluginManager: closing plugin " << (it->first) << endl;
		if (handle != NULL) {
			cout << handle << endl;
		        dlclose(handle);
		}
		*/
		unloadPlugin(it->first);
	}
	if (verbose) cout << "MacroPluginManager finished unloading plugins" << endl;
        // Remove all plugin entries in the map.
	plugin_map.clear();
	if (verbose) cout << "MacroPluginManager unloading completed" << endl;

}
/** Initialize a particular plugin */
bool MacroPluginManager::initializePlugin(const string& plugin_ident, LKbd & imyKbd, LConfig & imyConfig) {
	/** Check to see that the plugin is loaded */
	if(plugin_ident != "" && plugin_ident != snull) {
		if (plugin_map[plugin_ident].info.loaded == true) {
			initialize_t initialize;
			if (verbose) cout << "Initializing Plugin:" << plugin_ident << endl;
			initialize = plugin_map[plugin_ident].info.initialize;
			// Insurance
			if (initialize != NULL) {
				// Get the initialization information
				init_info init;
				init.myKbd = &imyKbd;
				init.myConfig = &imyConfig;
				init.verbose = verbose;
				init.global_enable = global_enable;
			/*	#ifdef HAVE_XOSD
				init.have_display = true;
				#else
				init.have_display = false;
				#endif */
				// Call the plugins initialize interface
				if (initialize(init))
				   plugin_map[plugin_ident].info.initialized = true;
				else {
				   if (verbose) cerr << "Plugin: " << plugin_ident << " failed to initialize. Removing plugin." << endl;
				   unloadPlugin(plugin_ident);
				   /*
				   void* handle = plugin_map[plugin_ident].info.handle;
				   cleanup_t cleanup = plugin_map[plugin_ident].info.cleanup;
				   if (cleanup != NULL)
				   	cleanup();
				   if (handle != NULL)
				   	dlclose(handle);
				   plugin_map.erase(plugin_ident); */
				   return true;
				}
				return true;
			}
			else {
				cerr << "initializePlugin: Could not find interface initialize() for plugin: " << plugin_ident << endl;
				return false;
			}
		}
		else {
			cerr << "initializePlugin: " << plugin_ident << " has not been loaded" << endl;
			return false;
		}
	}
	else {
		cerr << "initializePlugin: Operating on an empty plugin." << endl;
		return false;
	}
	return true;
}
/** Call the initialize interface for all plugins that we know about internally. */
bool MacroPluginManager::initializePlugins(LKbd & imyKbd, LConfig & imyConfig) {
        map<string,macro_plugin_info>::iterator it = plugin_map.begin();
	bool initflag = true;
	/** For all plugins, initialize them. If any does not initialize, return false */
        for (;it != plugin_map.end(); it++) {
                if (!initializePlugin((it->first),imyKbd, imyConfig))
			initflag = false;
        }
    	return initflag;
}

//#ifdef HAVE_XOSD
bool MacroPluginManager::initializePluginDisplay(const string& plugin, displayCtrl & imyDisplay) {
	/** Check to see that the plugin is loaded */
	if(plugin != "" && plugin != snull) {
		if (plugin_map[plugin].info.loaded == true ) {
			if (verbose) cout << "Initializing OSD for plugin: " << plugin << endl;
        		initialize_display_t initialize_display;
			initialize_display = plugin_map[plugin].info.initialize_display;
	        	if (initialize_display != NULL) {
        	        	if (!initialize_display(&imyDisplay))
					return false;
				plugin_map[plugin].info.initialized_display = true;
				return true;
			}
			else {
				if (verbose) cerr << "initializePluginDisplay: Could not find interface initialize_display() for plugin: " << plugin << endl;
				return false;
			}
		}
		else {
			if (verbose) cerr << "initializePluginDisplay: " << plugin << " has not been loaded" << endl;
			return false;
		}
	}
	else {
		if (verbose) cerr << "initializePluginDisplay: Operating on an empty plugin." << endl;
		return false;
	}
	return true;
}
/** Call the initialize interface for all plugins. */
bool MacroPluginManager::initializePluginsDisplay(displayCtrl & imyDisplay) {
        map<string,macro_plugin_info>::iterator it = plugin_map.begin();
	bool initflag = true;
        for (;it != plugin_map.end(); it++) {
        	if(!initializePluginDisplay((it->first), imyDisplay))
			initflag = false;
        }
    	return initflag;
}
//#endif
/** Scan the plugins directory for plugins. */
vector<string> MacroPluginManager::scanForPlugins(const string& directory){
	DIR *dp;
	struct dirent *entry;
	//struct stat statbuf;
        vector<string> plugins;
	string fullpath = directory + '/';
	if ((dp = opendir(directory.c_str())) == NULL) {
		if (verbose) cerr << "Cannot open plugin directory: " << directory << endl;
		return plugins;
	}
	//chdir(dir);
	while ((entry = readdir(dp)) != NULL) {
		char* file = entry->d_name;
		struct stat statbuf;
		lstat(file, &statbuf);
		if (strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0 ) {
			continue;
		}
		//if(S_ISLNK(statbuf.st_mode) || S_ISREG(statbuf.st_mode)) {
		if(!S_ISLNK(statbuf.st_mode)) {
			plugins.push_back(fullpath + string(entry->d_name));
                }
	}
	closedir(dp);
	return plugins;
}
/** Process a keycode by looking for the plugin that will handle it. */
//void MacroPluginManager::processKeycode(int keycode){
//	/** Determine the plugin that handles this Macro key command, and execute it. */
//
//}
bool MacroPluginManager::defineMacroList(const string& plugin) {
	/** Check to see that the plugin is loaded */
	if(plugin != "" && plugin != snull) {
		macro_plugin_info info = plugin_map[plugin];
		if (info.info.loaded == true ) {
			macrolist_t macrolistfn;
			macro_info* macinfo;
			if (verbose) cout << "Defining Macro Lists for Plugin:" << plugin << endl;
			macrolistfn = info.macrolist;
			// Insurance
			if (macrolistfn != NULL) {
				char **array;
				string tmp;
				macinfo = macrolistfn();
				if (macinfo == NULL) {
					if (verbose) cout << "Couldn't get macinfo for plugin: " << plugin << endl;
					return false;
				}
				array = macinfo->macro_list;
				// This should really just unload the plugin and return.
				if (macinfo->num_macros == 0) {
					unloadPlugin(plugin);
					return false;
				}
       		                for (int i=0; i < macinfo->num_macros; i++) {
       			                tmp = array[i];
			                // Get the vector of macros for this plugin, and add this macro to the end
        		                // Add this plugins macros to the list of all supported macros.
					if (verbose) cout << "Adding macro: " << tmp << endl;
                                        (plugin_map[plugin].macros).push_back(tmp);
					macrolist.push_back(tmp);
       		                }
				plugin_map[plugin].macros_defined = true;
				return true;
			}
			else {
				if (verbose) cerr << "Macrolist for plugin: " << plugin << " is empty" << endl;
				return false;
			}
		}
		else {
			if (verbose) cerr << "defineMacroList:" << plugin << " has not been loaded or initialized" << endl;
			return false;
		}
	}
	else {
		if (verbose) cerr << "defineMacroList: Operating on an empty plugin." << endl;
		return false;
	}
	return true;
}
bool MacroPluginManager::defineMacroLists() {
	if (!plugin_map.empty()) {
		map<string,macro_plugin_info>::iterator it = plugin_map.begin();
		/** For each plugin */
		bool macroflag = true;
		for (;it != plugin_map.end(); it++) {
			if (!defineMacroList(string(it->first))) {
				macroflag = false;
			}
		}
		return macroflag;
	}
	return false;
}

