/*
 * File      : module.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006 - 2013, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2013-02-26     prife        first version for win32
 */

#include <rthw.h>
#include <rtthread.h>
#include <rtm.h>

#ifdef RT_USING_MODULE
void rt_module_init_object_container(struct rt_module *module)
{
    RT_ASSERT(module != RT_NULL);

    /* initialize object container - thread */
    rt_list_init(&(module->module_object[RT_Object_Class_Thread].object_list));
    module->module_object[RT_Object_Class_Thread].object_size = sizeof(struct rt_thread);
    module->module_object[RT_Object_Class_Thread].type = RT_Object_Class_Thread;

#ifdef RT_USING_SEMAPHORE
    /* initialize object container - semaphore */
    rt_list_init(&(module->module_object[RT_Object_Class_Semaphore].object_list));
    module->module_object[RT_Object_Class_Semaphore].object_size = sizeof(struct rt_semaphore);
    module->module_object[RT_Object_Class_Semaphore].type = RT_Object_Class_Semaphore;
#endif

#ifdef RT_USING_MUTEX
    /* initialize object container - mutex */
    rt_list_init(&(module->module_object[RT_Object_Class_Mutex].object_list));
    module->module_object[RT_Object_Class_Mutex].object_size = sizeof(struct rt_mutex);
    module->module_object[RT_Object_Class_Mutex].type = RT_Object_Class_Mutex;
#endif

#ifdef RT_USING_EVENT
    /* initialize object container - event */
    rt_list_init(&(module->module_object[RT_Object_Class_Event].object_list));
    module->module_object[RT_Object_Class_Event].object_size = sizeof(struct rt_event);
    module->module_object[RT_Object_Class_Event].type = RT_Object_Class_Event;
#endif

#ifdef RT_USING_MAILBOX
    /* initialize object container - mailbox */
    rt_list_init(&(module->module_object[RT_Object_Class_MailBox].object_list));
    module->module_object[RT_Object_Class_MailBox].object_size = sizeof(struct rt_mailbox);
    module->module_object[RT_Object_Class_MailBox].type = RT_Object_Class_MailBox;
#endif

#ifdef RT_USING_MESSAGEQUEUE
    /* initialize object container - message queue */
    rt_list_init(&(module->module_object[RT_Object_Class_MessageQueue].object_list));
    module->module_object[RT_Object_Class_MessageQueue].object_size = sizeof(struct rt_messagequeue);
    module->module_object[RT_Object_Class_MessageQueue].type = RT_Object_Class_MessageQueue;
#endif

#ifdef RT_USING_MEMHEAP
    /* initialize object container - memory heap */
    rt_list_init(&(module->module_object[RT_Object_Class_MemHeap].object_list));
    module->module_object[RT_Object_Class_MemHeap].object_size = sizeof(struct rt_memheap);
    module->module_object[RT_Object_Class_MemHeap].type = RT_Object_Class_MemHeap;
#endif

#ifdef RT_USING_MEMPOOL
    /* initialize object container - memory pool */
    rt_list_init(&(module->module_object[RT_Object_Class_MemPool].object_list));
    module->module_object[RT_Object_Class_MemPool].object_size = sizeof(struct rt_mempool);
    module->module_object[RT_Object_Class_MemPool].type = RT_Object_Class_MemPool;
#endif

#ifdef RT_USING_DEVICE
    /* initialize object container - device */
    rt_list_init(&(module->module_object[RT_Object_Class_Device].object_list));
    module->module_object[RT_Object_Class_Device].object_size = sizeof(struct rt_device);
    module->module_object[RT_Object_Class_Device].type = RT_Object_Class_Device;
#endif

    /* initialize object container - timer */
    rt_list_init(&(module->module_object[RT_Object_Class_Timer].object_list));
    module->module_object[RT_Object_Class_Timer].object_size = sizeof(struct rt_timer);
    module->module_object[RT_Object_Class_Timer].type = RT_Object_Class_Timer;
}

#ifdef RT_USING_HOOK
static void (*rt_module_load_hook)(rt_module_t module);
static void (*rt_module_unload_hook)(rt_module_t module);

/**
 * @addtogroup Hook
 */

/*@{*/

/**
 * This function will set a hook function, which will be invoked when module
 * be loaded to system.
 *
 * @param hook the hook function
 */
void rt_module_load_sethook(void (*hook)(rt_module_t module))
{
    rt_module_load_hook = hook;
}

/**
 * This function will set a hook function, which will be invoked when module
 * be unloaded from system.
 *
 * @param hook the hook function
 */
void rt_module_unload_sethook(void (*hook)(rt_module_t module))
{
    rt_module_unload_hook = hook;
}

/*@}*/
#endif

/**
 * @ingroup SystemInit
 *
 * This function will initialize system module
 */
int rt_system_module_init(void)
{
	return 0;
}

/**
 * This function will return self module object
 *
 * @return the self module object
 */
rt_module_t rt_module_self(void)
{
    rt_thread_t tid;

    tid = rt_thread_self();
    if (tid == RT_NULL)
        return RT_NULL;

    /* return current module */
    return (rt_module_t)tid->module_id;
}
RTM_EXPORT(rt_module_self);

/**
 * This function will find the specified module.
 *
 * @param name the name of module finding
 *
 * @return the module
 */
rt_module_t rt_module_find(const char *name)
{
    struct rt_object_information *information;
    struct rt_object *object;
    struct rt_list_node *node;

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* enter critical */
    rt_enter_critical();

    /* try to find device object */
    information = rt_object_get_information(RT_Object_Class_Module);
    RT_ASSERT(information != RT_NULL);

    for (node = information->object_list.next;
        node != &(information->object_list);
        node = node->next)
    {
        object = rt_list_entry(node, struct rt_object, list);
        if (rt_strncmp(object->name, name, RT_NAME_MAX) == 0)
        {
            /* leave critical */
            rt_exit_critical();

            return (rt_module_t)object;
        }
    }

    /* leave critical */
    rt_exit_critical();

    /* not found */
    return RT_NULL;
}
RTM_EXPORT(rt_module_find);

#ifdef RT_USING_DFS
#include <windows.h>
#include <dfs_posix.h>
extern char * dfs_win32_dirdup(char * path);
static char* _module_name(const char *path)
{
    const char *first, *end, *ptr;
    char *name;
    int size;

    ptr   = (char *)path;
    first = ptr;
    end   = path + rt_strlen(path);

    while (*ptr != '\0')
    {
        if (*ptr == '/')
            first = ptr + 1;
        if (*ptr == '.')
            end = ptr - 1;

        ptr ++;
    }

    size = end - first + 1;
    name = rt_malloc(size);
    rt_strncpy(name, first, size);
    name[size] = '\0';

    return name;
}

typedef int (*appentry_t)(void);
/**
 * This function will load a module from a file
 *
 * @param path the full path of application module
 *
 * @return the module object
 */
rt_module_t rt_module_open(const char *path)
{
    struct dfs_filesystem *fs;
    appentry_t fptr;
    HINSTANCE hinstlib;
	rt_module_t module;

	char * winpath = RT_NULL;
    char * name = RT_NULL;

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* check parameters */
    RT_ASSERT(path != RT_NULL);

    /* app module should only in DFS_WIN32 */
    fs = dfs_filesystem_lookup(path);
    if ((fs == RT_NULL) || (strcmp(fs->ops->name,"wdir") != 0))
    {
        rt_kprintf("invalid path: %s\n", path);
        return RT_NULL;
    }

    /* change path */
    // len = strlen(path+1);
    if ((winpath = dfs_win32_dirdup((char *)path)) == RT_NULL)
    {
        rt_kprintf("out of memory, exit");
        return RT_NULL;
    }

    hinstlib = LoadLibrary(winpath);
    if (hinstlib == NULL)
    {
        rt_kprintf("error: unable to open %s\n", winpath);
        return RT_NULL;
    }

    fptr = (appentry_t)GetProcAddress(hinstlib, "main");
    if (fptr == NULL)
    {
        rt_kprintf("error: unable to find function in %s\n", winpath);
        FreeLibrary(hinstlib);
        return RT_NULL;
    }

    /* get the name of the module */
    name = _module_name(path);

    /* allocate module */
    module = (struct rt_module *)rt_object_allocate(RT_Object_Class_Module, name);
    if (!module) return RT_NULL;

    module->nref = 0;
    module->module_entry = fptr;

    /* init module object container */
    rt_module_init_object_container(module);

    /* increase module reference count */
    module->nref ++;

    if (module->module_entry != 0)
    {
#ifdef RT_USING_SLAB
        /* init module memory allocator */
        module->mem_list = RT_NULL;

        /* create page array */
        module->page_array =
            (void *)rt_malloc(PAGE_COUNT_MAX * sizeof(struct rt_page_info));
        module->page_cnt = 0;
#endif

        /* create module thread */
        module->module_thread =
            rt_thread_create(name,
            (void(*)(void *))module->module_entry, RT_NULL,
            2048, RT_THREAD_PRIORITY_MAX - 2, 10);

        RT_DEBUG_LOG(RT_DEBUG_MODULE, ("thread entry 0x%x\n",
            module->module_entry));

        /* set module id */
        module->module_thread->module_id = (void *)module;
        module->parent.flag = RT_MODULE_FLAG_WITHENTRY;

        /* startup module thread */
        rt_thread_startup(module->module_thread);
    }
    else
    {
        /* without entry point */
        module->parent.flag |= RT_MODULE_FLAG_WITHOUTENTRY;
    }

#ifdef RT_USING_HOOK
    if (rt_module_load_hook != RT_NULL)
    {
        rt_module_load_hook(module);
    }
#endif

    rt_free(name);
    return module;
    /* FreeLibrary(hinstlib); */
}

#if defined(RT_USING_FINSH)
#include <finsh.h>
FINSH_FUNCTION_EXPORT_ALIAS(rt_module_open, exec, exec module from a file);
#endif

#endif

#define RT_MODULE_ARG_MAX    8
static int _rt_module_split_arg(char* cmd, rt_size_t length, char* argv[])
{
	int argc = 0;
	char *ptr = cmd;

	while ((ptr - cmd) < length)
	{
		/* strip bank and tab */
		while ((*ptr == ' ' || *ptr == '\t') && (ptr -cmd)< length)
			*ptr++ = '\0';
		/* check whether it's the end of line */
		if ((ptr - cmd)>= length) break;

		/* handle string with quote */
		if (*ptr == '"')
		{
			argv[argc++] = ++ptr;

			/* skip this string */
			while (*ptr != '"' && (ptr-cmd) < length)
				if (*ptr ++ == '\\')  ptr ++;
			if ((ptr - cmd) >= length) break;

			/* skip '"' */
			*ptr ++ = '\0';
		}
		else
		{
			argv[argc++] = ptr;
			while ((*ptr != ' ' && *ptr != '\t') && (ptr - cmd) < length)
				ptr ++;
		}

		if (argc >= RT_MODULE_ARG_MAX) break;
	}

	return argc;
}

/* module main thread entry */
static void module_main_entry(void* parameter)
{
	int argc;
	char *argv[RT_MODULE_ARG_MAX];
	typedef int (*main_func_t)(int argc, char** argv);

	rt_module_t module = (rt_module_t) parameter;
	if (module == RT_NULL || module->module_cmd_line == RT_NULL) return;

	rt_memset(argv, 0x00, sizeof(argv));
	argc = _rt_module_split_arg((char*)module->module_cmd_line, module->module_cmd_size, argv);
	if (argc == 0) return ;

	/* do the main function */
	((main_func_t)module->module_entry)(argc, argv);
	return;
}

/**
 * This function will do a executable program with main function and parameters.
 *
 * @param path the full path of application module
 * @param cmd_line the command line of program
 * @param size the size of command line of program
 *
 * @return the module object
 */
rt_module_t rt_module_exec_cmd(const char *path, const char* cmd_line, int line_size)
{
    struct dfs_filesystem *fs;
    appentry_t fptr;
    HINSTANCE hinstlib;
	rt_module_t module;

	char * winpath = RT_NULL;
    char * name = RT_NULL;
	char *full_path = RT_NULL;

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* check parameters */
    RT_ASSERT(path != RT_NULL);

	if (*path != '/')
	{
		full_path = dfs_normalize_path(RT_NULL, path);
	}
	else
	{
		full_path = (const char*)path;
	}

    /* app module should only in DFS_WIN32 */
    fs = dfs_filesystem_lookup(full_path);
    if ((fs == RT_NULL) || (strcmp(fs->ops->name,"wdir") != 0))
    {
        rt_kprintf("invalid path: %s\n", path);
        goto __exit;
    }

    /* change path */
    // len = strlen(full_path + 1);
    if ((winpath = dfs_win32_dirdup((char *)full_path)) == RT_NULL)
    {
        rt_kprintf("out of memory, exit", path);
		goto __exit;
    }

    hinstlib = LoadLibrary(winpath);
    if (hinstlib == NULL)
    {
        rt_kprintf("error: unable to open %s\n", winpath);
		goto __exit;
    }

    fptr = (appentry_t)GetProcAddress(hinstlib, "main");
    if (fptr == NULL)
    {
        rt_kprintf("error: unable to find function in %s\n", winpath);
        FreeLibrary(hinstlib);
		goto __exit;
    }

	/* release winpath */
	rt_free(winpath);

    /* get the name of the module */
    name = _module_name(path);

    /* allocate module */
    module = (struct rt_module *)rt_object_allocate(RT_Object_Class_Module, name);
    if (!module) 
	{
		goto __exit;
	}

    module->nref = 0;
    module->module_entry = fptr;

    /* init module object container */
    rt_module_init_object_container(module);

    /* increase module reference count */
    module->nref ++;

    if (module->module_entry != 0)
    {
		/* set module argument */
		module->module_cmd_line = (rt_uint8_t*)rt_malloc(line_size + 1);
		rt_memcpy(module->module_cmd_line, cmd_line, line_size);
		module->module_cmd_line[line_size] = '\0';
		module->module_cmd_size = line_size;

#ifdef RT_USING_SLAB
        /* init module memory allocator */
        module->mem_list = RT_NULL;

        /* create page array */
        module->page_array =
            (void *)rt_malloc(PAGE_COUNT_MAX * sizeof(struct rt_page_info));
        module->page_cnt = 0;
#endif

		/* create module thread */
		module->module_thread =	rt_thread_create(name,
			module_main_entry, module,
			2048, RT_THREAD_PRIORITY_MAX - 2, 10);

        /* set module id */
        module->module_thread->module_id = (void *)module;
        module->parent.flag = RT_MODULE_FLAG_WITHENTRY;

        /* startup module thread */
        rt_thread_startup(module->module_thread);
    }
    else
    {
        /* without entry point */
        module->parent.flag |= RT_MODULE_FLAG_WITHOUTENTRY;
    }

#ifdef RT_USING_HOOK
    if (rt_module_load_hook != RT_NULL)
    {
        rt_module_load_hook(module);
    }
#endif

    rt_free(name);
    return module;

__exit:
	if (full_path != path) rt_free(full_path);
	if (name != RT_NULL)   rt_free(full_path);
	if (winpath != RT_NULL)rt_free(winpath);

	return RT_NULL;
    /* FreeLibrary(hinstlib); */
}

rt_err_t rt_module_destroy(rt_module_t module)
{
    return 0;
}

rt_err_t rt_module_unload(rt_module_t module)
{
    return 0;
}

#endif