#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <gmodule.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include "entity.h"

static char *codedir;
static int link_in_so (ENode * node, gchar * lafile);
static void import_in (GModule * module, gchar * function);

static char *stdheaders = "                                        \
#include <stdio.h>\n                                               \
#include <stdlib.h>\n                                              \
#include <glib.h>\n                                                \
#include <entity.h>\n						   \
";

static GHashTable *c_functions_ht = NULL;

static gint
check_checksum (char *obj, char *code)
{
    gchar *checksumfile = g_strconcat (codedir, "/.sum/", obj, NULL);
    unsigned int old_hash = 0;
    unsigned int new_hash = x31_hash (code);
    FILE *file_handle = NULL;

    /* if there is a hash, read it in and check agains the new hash */
    file_handle = fopen (checksumfile, "r");

    if (file_handle) {
	fscanf (file_handle, "%u", &old_hash);
	fclose (file_handle);
    }
	
    g_free (checksumfile);

    /* if they are the same ...do nothing but return true */
    if (old_hash == new_hash)
	return (TRUE);

    return (FALSE);
}

static void
write_checksum (char *obj, char *code)
{
    gchar *checksumfile = g_strconcat (codedir, "/.sum/", obj, NULL);
    unsigned int new_hash = x31_hash (code);
    FILE *file_handle = NULL;
    
    file_handle = fopen (checksumfile, "w+");
    if (file_handle) {
	fprintf (file_handle, "%u", new_hash);
	fclose (file_handle);
    }
    g_free (checksumfile);
}

gchar *
c_compile_str_get (ENode * node, char *tag)
{
    char *str = NULL;
    char *temp;
    GSList *list;
    ENode *child;

    /* check current node for the tag first */
    str = enode_attrib_str (node, tag, NULL);

    /* Tag was not found, now check for children */
    if (!str) {
	for (list = node->children; list; list = list->next) {
	    child = (ENode *) list->data;
	    if (strcmp (child->element->str, tag) != 0 || !child->data) {
		continue;
	    }
	    temp = g_strconcat (child->data->str, str, NULL);
	    if (str) {
		g_free (str);
	    }
	    str = temp;
	}
    }

    /* Make sure the string has "something" */
    if (!str) {
	str = "";
    }

    return (str);
}

/* Render all the c-code tags. * Code that has been modified will be
 * recompiled and relinked into * .so files.   These .so are then dlopen'ed
 * and functions that have * been exported to entity using the <c-function>
 * tag will then * added to the c_functions_ht so that they can be called
 * from entity. */
static void
c_node_render (ENode * node)
{
    gchar *obj;			/* Object name, append .so for the lib name. */
    gchar *includes;		/* Text inside of <c-includes>. */
    gchar *libs;		/* Text inside of <c-libs>. */

    gchar *obj_cmd;		/* Commands that are ran to compile the c. */
    gchar *so_cmd;

    gchar *lafile;		/* Path to the outputed .la library info file 
				 */
    gchar *data;

    FILE *fp = NULL;

    /* Check to make sure we have a valid node, with data */
    if (!node || !node->data) {
	return;
    }

    obj = enode_attrib_str (node, "object", NULL);

    if (!obj) {
	obj = "libdefault";	/* This will be really SLOW!!! because each
				 * new */
    } /* will have to be compiled. FIXME */
    else {
	obj = g_strconcat ("lib", obj, NULL);	/* libtool needs the object
						 * to start with a lib */
    }

    includes = c_compile_str_get (node, "c-includes");

    libs = c_compile_str_get (node, "c-libs");


    /* This is the data that is checksummed, we need all od this so that *
     * the relink and recompile happen if only the <c-[libs|includes] * are
     * changed in the xml. */
    data = g_strconcat (includes, libs, node->data->str, NULL);
    lafile = g_strconcat (codedir, "/", obj, ".la", NULL);

    if (check_checksum (obj, data) == FALSE) {
	char *tempfile = NULL;	/* File created to hold the c code. */
	int compile_ok = TRUE;
	
	tempfile = g_strconcat (codedir, "/entity.c", NULL);

	/* Open temp file. */
	if (tempfile) {
	    fp = fopen (tempfile, "w");
	}
	if (!fp) {
	    g_warning ("Unable to open temp file '%s' for writing: %s",
		       tempfile, g_strerror (errno));
	    return;
	}

	fprintf (fp, "%s", stdheaders);
	fprintf (fp, "%s", node->data->str);
	fclose (fp);

	/* Build the object compile string */
	obj_cmd = g_strconcat (DATADIR, "/libtool --mode=compile ", COMPILER, " ",
			       includes, " `entity-config --cflags` ", tempfile,
			       " -c -o ",
			       codedir, "/.objects/", obj, ".lo", NULL);

	/* Build the so linking string */
	so_cmd = g_strconcat (DATADIR, "/libtool --mode=link ", COMPILER, " ",
			      libs, " ", "-avoid-version -module ",
			      codedir, "/.objects/", obj, ".lo",
			      " -rpath /usr/lib  -o ", lafile, NULL);

	/* Rebuild if checksum is different */
	EDEBUG (("c-embed", "Executing libtool command: %s", obj_cmd));
	if (system (obj_cmd)) {
	    compile_ok = FALSE;
	    g_warning ("C-code was not recompiled! %s\n", obj_cmd);
	} else {

	    /* on to link stage */
	    EDEBUG (("c-embed", "Executing libtool command: %s", so_cmd));
	    if (system (so_cmd)) {
		compile_ok = FALSE;
		g_warning ("C-code was not relinked! %s\n", so_cmd);
	    }
	}

	/* Only write out checksum if the compile was ok */
	if (compile_ok == TRUE)
	    write_checksum (obj, data);

	g_free (obj_cmd);
	g_free (so_cmd);
	g_free (tempfile);
    }
    g_free (data);

    link_in_so (node, lafile);
    g_free (lafile);
}


static int
link_in_so (ENode * node, gchar * lafile)
{
    GModule *module;
    void (*init)(void);                    /* The init function. */
    gchar buf[2048];
    FILE *file_handle = NULL;
    int err = FALSE;
    gchar *modpath;
    gchar *dlname;
    gchar *command;
    gchar **segments = NULL;

    dlname = eutils_module_dlname (lafile);
    if (!dlname) {
	g_warning ("Unable to deduce shared object file to load, giving up!");
	return (1);
    }
    modpath = g_strconcat (codedir, "/.libs/", dlname, NULL);

    g_free (dlname);

    EDEBUG (("c-embed", "Loading object '%s' from path '%s'", dlname, modpath));

    module = g_module_open (modpath, G_MODULE_BIND_LAZY);

    if (!module) {
	g_warning
	    ("Error loading dynamic library '%s': %s\n",
	     modpath, g_module_error());
	return (1);
    }

    /* do a popen and hand it a nm command to generate function names */
    command = g_strconcat ("nm -p ", modpath, NULL);
    EDEBUG (("c-embed", "Executing command: '%s'", command));
    file_handle = popen (command, "r");
    g_free (command);
    g_free (modpath);

    /* check the file handle */
    if (file_handle) {
	while (FALSE == err) {
	    if (NULL != fgets (buf, 2048, file_handle)) {
		
		/* g_strchomp operates on string in-place */
		g_strchomp (buf);
		segments = g_strsplit (buf, " ", 0);

		if (segments && segments[1] && (0 == strcmp (segments[1], "T")) &&
		    segments[2] && segments[2] != '\0') {
		    import_in (module, segments[2]);	/* import the module
							 * from the .so */
		}
		
		if (segments)
		    g_strfreev (segments);

	    } else {
		err = TRUE;
	    }
	}

	(void) pclose (file_handle);
    }

    /* Call the entity_init function in the library if it's there. */
    if (g_module_symbol (module, "entity_c_init", (gpointer *) &init) ) {
        EDEBUG (("c-embed", "running 'entity_c_init' in C-code.\n"));
	enode_call_reference_push (node);
	init ();
	enode_call_reference_pop ();
    }

    return (0);
}

/* Imports in a module from a .so */
void
import_in (GModule * module, gchar * function)
{
    void (*func) (GSList *);

    if (function) {
	EDEBUG (("c-embed", "importing in function in '%s'", function));
	/* On some systems, all symbols start with _ */
	if (function[0] == '_')
	    function++;

	g_module_symbol (module, function, (void **) &func);

	if (func) {
	    g_hash_table_insert (c_functions_ht, g_strdup (function), func);
	} else {
	    g_warning ("nm returned function %s, but theres no such symbol",
		       function);
	}
    }
}

EBuf *
c_function_execute (ENode * calling_node, gchar * function, GSList * args)
{
    GSList *tmp;
    EBuf *retval = NULL;

    EBuf *(*funct) (ENode *, GSList *);

    funct = g_hash_table_lookup (c_functions_ht, function);

    if (funct) {
	enode_call_reference_push (calling_node);
	retval = funct (calling_node, args);
	enode_call_reference_pop ();
    } else {
	g_warning ("While trying to execute C function %s: %s not defined.\n", function, function);
    }

    /* Free argument list */
    tmp = args;
    while (tmp) {
	LangArg *arg = tmp->data;
	enode_call_free_arg (arg);
	tmp = tmp->next;
    }

    return (retval);
}

#ifdef STATIC_C
void
c_init (RendererFlags flags)
#else
void
renderer_init (RendererFlags flags)
#endif
{
    Element *element;
    char *sumdir;
    char *libdir;

    c_functions_ht = g_hash_table_new (g_str_hash, g_str_equal);

    if (flags & RENDERER_INIT) {
	codedir = g_strconcat (g_get_home_dir (), "/.entity/c-code", NULL);
	sumdir = g_strconcat (codedir, "/.sum", NULL);
	libdir = g_strconcat (codedir, "/.objects", NULL);

	if (mkdir (codedir, 0750) == -1 && errno != EEXIST) {
	    g_warning ("Cant create %s, no c-code tags can be rendered!\n",
		       codedir);
	}

	if (mkdir (sumdir, 0750) == -1 && errno != EEXIST) {
	    g_warning ("Cant create %s, no c-code tags can be rendered!\n",
		       sumdir);
	}

	if (mkdir (libdir, 0750) == -1 && errno != EEXIST) {
	    g_warning ("Cant create %s, no c-code tags can be rendered!\n",
		       libdir);
	}

	g_free (sumdir);
	g_free (libdir);
    }

    if (flags & RENDERER_REGISTER) {
	element = g_new0 (Element, 1);
	element->render_func = c_node_render;
	element->destroy_func = NULL;
	element->description =
	    "Embed C code directly into an Entity application.";
	element->tag = "c-code";
	element_register (element);

	language_register ("c-code", c_function_execute);
	language_register ("C", c_function_execute);
	language_register ("c", c_function_execute);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1