/* Copyright (C) 2012 Doubango Telecom <http://www.doubango.org>
*	
* This file is part of Open Source Doubango Framework.
*
* DOUBANGO 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 3 of the License, or
* (at your option) any later version.
*	
* DOUBANGO 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 General Public License for more details.
*	
* You should have received a copy of the GNU General Public License
* along with DOUBANGO.
*
*/
#include "tsk_plugin.h"
#include "tsk_string.h"
#include "tsk_memory.h"
#include "tsk_debug.h"

typedef int (*symbol_get_def_count)(void);
typedef tsk_plugin_def_type_t (*symbol_get_def_type_at)(int index);
typedef tsk_plugin_def_media_type_t (*symbol_get_def_media_type_at)(int index);
typedef tsk_plugin_def_ptr_const_t (*symbol_get_def_at)(int index);

#define TSK_PLUGIN_FUNC_NAME_DEF_COUNT				"__plugin_get_def_count"
#define TSK_PLUGIN_FUNC_NAME_DEF_TYPE_AT			"__plugin_get_def_type_at"
#define TSK_PLUGIN_FUNC_NAME_DEF_MEDIA_TYPE_AT		"__plugin_get_def_media_type_at"
#define TSK_PLUGIN_FUNC_NAME_DEF_AT					"__plugin_get_def_at"

#if TSK_UNDER_WINDOWS
#	include <windows.h>
#else
#	include <dlfcn.h>
#endif

static int _tsk_plugin_handle_destroy(tsk_plugin_handle_t** self);
static tsk_plugin_symbol_t* _tsk_plugin_handle_get_symbol(tsk_plugin_handle_t* handle, const char* symbol_name);

typedef struct tsk_plugin_s
{
	TSK_DECLARE_OBJECT;

	tsk_plugin_handle_t* handle;
	int def_count;
	char* path;
}
tsk_plugin_t;

static tsk_object_t* tsk_plugin_ctor(tsk_object_t * self, va_list * app)
{
	tsk_plugin_t *plugin = (tsk_plugin_t*)self;
	if(plugin){
		
	}
	return self;
}
static tsk_object_t* tsk_plugin_dtor(tsk_object_t * self)
{ 
	tsk_plugin_t *plugin = (tsk_plugin_t*)self;
	if(plugin){
		TSK_FREE(plugin->path);
		_tsk_plugin_handle_destroy(&plugin->handle);
	}

	return self;
}
static const tsk_object_def_t tsk_plugin_def_s = 
{
	sizeof(tsk_plugin_t),
	tsk_plugin_ctor, 
	tsk_plugin_dtor,
	tsk_null, 
};

tsk_plugin_t* tsk_plugin_create(const char* path)
{
	tsk_plugin_t* plugin;
	symbol_get_def_count funcptr_get_def_count;
	tsk_plugin_handle_t* handle;

#if TSK_UNDER_WINDOWS
#	if TSK_UNDER_WINDOWS_RT
	wchar_t* szPath = (wchar_t*)tsk_calloc(tsk_strlen(path) + 1, sizeof(wchar_t));
	static const wchar_t* szFormat = L"%hs";
	swprintf(szPath, tsk_strlen(path) * sizeof(wchar_t), szFormat, path);
	handle = LoadPackagedLibrary(szPath, 0x00000000);
	TSK_FREE(szPath);
#	else
	handle = LoadLibraryA(path);
#	endif
#else
	handle = dlopen(path, RTLD_NOW);
#endif

	if(!handle){
		TSK_DEBUG_ERROR("Failed to load library with path=%s", path);
		return tsk_null;
	}

	if(!(funcptr_get_def_count = (symbol_get_def_count)_tsk_plugin_handle_get_symbol(handle, TSK_PLUGIN_FUNC_NAME_DEF_COUNT))){
		TSK_DEBUG_ERROR("Cannot find function with name=%s", TSK_PLUGIN_FUNC_NAME_DEF_COUNT);
		_tsk_plugin_handle_destroy(&handle);
		return tsk_null;
	}

	if(!(plugin = (tsk_plugin_t*)tsk_object_new(&tsk_plugin_def_s))){
		TSK_DEBUG_ERROR("Failed to create plugin object");
		_tsk_plugin_handle_destroy(&handle);
		return tsk_null;
	}

	plugin->handle = handle;
	plugin->def_count = funcptr_get_def_count();
	plugin->path = tsk_strdup(path);

	TSK_DEBUG_INFO("Plugin with path=[%s] created with [%d] defs", plugin->path, plugin->def_count);

	return plugin;
}

tsk_plugin_def_ptr_const_t tsk_plugin_get_def(tsk_plugin_t* self, tsk_plugin_def_type_t type, tsk_plugin_def_media_type_t media_type)
{
	tsk_plugin_def_ptr_const_t def_ptr_const;
	symbol_get_def_type_at funcptr_get_def_type_at;
	symbol_get_def_media_type_at funcptr_get_def_media_type_at;
	symbol_get_def_at funcptr_get_def_at;
	int i;

	if(!self){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_null;
	}

	if(!(funcptr_get_def_type_at = (symbol_get_def_type_at)tsk_plugin_get_symbol(self, TSK_PLUGIN_FUNC_NAME_DEF_TYPE_AT))){
		TSK_DEBUG_ERROR("[%s] function not implemented in plugin with path=[%s]", TSK_PLUGIN_FUNC_NAME_DEF_TYPE_AT, self->path);
		return tsk_null;
	}
	if(!(funcptr_get_def_media_type_at = (symbol_get_def_media_type_at)tsk_plugin_get_symbol(self, TSK_PLUGIN_FUNC_NAME_DEF_MEDIA_TYPE_AT))){
		TSK_DEBUG_ERROR("[%s] function not implemented in plugin with path=[%s]", TSK_PLUGIN_FUNC_NAME_DEF_MEDIA_TYPE_AT, self->path);
		return tsk_null;
	}
	if(!(funcptr_get_def_at = (symbol_get_def_at)tsk_plugin_get_symbol(self, TSK_PLUGIN_FUNC_NAME_DEF_AT))){
		TSK_DEBUG_ERROR("[%s] function not implemented in plugin with path=[%s]", TSK_PLUGIN_FUNC_NAME_DEF_AT, self->path);
		return tsk_null;
	}

	for(i = 0; i < self->def_count; ++i){
		if((funcptr_get_def_type_at(i) & type) && (funcptr_get_def_media_type_at(i) & media_type)){
			if((def_ptr_const = funcptr_get_def_at(i))){
				return def_ptr_const;
			}
		}
	}
	return tsk_null;
}

tsk_plugin_symbol_t* tsk_plugin_get_symbol(tsk_plugin_t* self, const char* symbol_name)
{
	if(!self || !self->handle || !symbol_name){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_null;
	}
	return _tsk_plugin_handle_get_symbol(self->handle, symbol_name);
}

static tsk_plugin_symbol_t* _tsk_plugin_handle_get_symbol(tsk_plugin_handle_t* handle, const char* symbol_name)
{
	if(!handle || !symbol_name){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_null;
	}
#if TSK_UNDER_WINDOWS
	return (tsk_plugin_symbol_t*)GetProcAddress((HMODULE)handle, symbol_name);
#else
	return (tsk_plugin_symbol_t*)dlsym(handle, symbol_name);
#endif
}

static int _tsk_plugin_handle_destroy(tsk_plugin_handle_t** handle)
{
	if(handle && *handle){
#if TSK_UNDER_WINDOWS
		FreeLibrary((HMODULE)*handle);
#else
		dlclose(*handle);
#endif
		*handle = tsk_null;
	}
	return 0;
}
