#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#elif defined __GNUC__
# define alloca __builtin_alloca
#elif defined _AIX
# define alloca __alloca
#elif defined _MSC_VER
# include <malloc.h>
# define alloca _alloca
#else
# include <stddef.h>
# ifdef  __cplusplus
extern "C"
# endif
void *alloca (size_t);
#endif

#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>

#include <Ecore_Evas.h>

#include "edje_cc.h"
#include "edje_convert.h"

#include <lua.h>
#include <lauxlib.h>

typedef struct _External_Lookup External_Lookup;
typedef struct _Part_Lookup Part_Lookup;
typedef struct _Program_Lookup Program_Lookup;
typedef struct _Group_Lookup Group_Lookup;
typedef struct _Image_Lookup Image_Lookup;
typedef struct _Slave_Lookup Slave_Lookup;
typedef struct _Code_Lookup Code_Lookup;


struct _External_Lookup
{
   char *name;
};

struct _Part_Lookup
{
   Edje_Part_Collection *pc;
   char *name;
   int *dest;
};

struct _Program_Lookup
{
   Edje_Part_Collection *pc;

   union
   {
      char *name;
      Edje_Program *ep;
   } u;

   int *dest;

   Eina_Bool anonymous : 1;
};

struct _Group_Lookup
{
   char *name;
};

struct _String_Lookup
{
   char *name;
   int *dest;
};

struct _Image_Lookup
{
   char *name;
   int *dest;
   Eina_Bool *set;
};

struct _Slave_Lookup
{
   int *master;
   int *slave;
};

struct _Code_Lookup
{
   char *ptr;
   int   len;
   int   val;
   Eina_Bool set;
};

static void data_process_string(Edje_Part_Collection *pc, const char *prefix, char *s, void (*func)(Edje_Part_Collection *pc, char *name, char *ptr, int len));

Edje_File *edje_file = NULL;
Eina_List *edje_collections = NULL;
Eina_List *externals = NULL;
Eina_List *fonts = NULL;
Eina_List *codes = NULL;
Eina_List *code_lookups = NULL;
Eina_List *aliases = NULL;

static Eet_Data_Descriptor *edd_edje_file = NULL;
static Eet_Data_Descriptor *edd_edje_part_collection = NULL;

static Eina_List *part_lookups = NULL;
static Eina_List *program_lookups = NULL;
static Eina_List *group_lookups = NULL;
static Eina_List *image_lookups = NULL;
static Eina_List *part_slave_lookups = NULL;
static Eina_List *image_slave_lookups= NULL;

#define ABORT_WRITE(eet_file, file) \
   eet_close(eet_file); \
   unlink(file); \
   exit(-1);

void
error_and_abort(Eet_File *ef, const char *fmt, ...)
{
   va_list ap;

   fprintf(stderr, "%s: Error. ", progname);

   va_start(ap, fmt);
   vfprintf(stderr, fmt, ap);
   va_end(ap);
   ABORT_WRITE(ef, file_out);
}

void
data_setup(void)
{
   edd_edje_file = _edje_edd_edje_file;
   edd_edje_part_collection = _edje_edd_edje_part_collection;
}

static void
check_image_part_desc (Edje_Part_Collection *pc, Edje_Part *ep,
                       Edje_Part_Description_Image *epd, Eet_File *ef)
{
   unsigned int i;

#if 0 /* FIXME: This check sounds like not a useful one */
   if (epd->image.id == -1)
     ERR(ef, "Collection %s(%i): image attributes missing for "
	 "part \"%s\", description \"%s\" %f\n",
	 pc->part, pc->id, ep->name, epd->common.state.name, epd->common.state.value);
#endif

   for (i = 0; i < epd->image.tweens_count; ++i)
     {
	if (epd->image.tweens[i]->id == -1)
	  error_and_abort(ef, "Collection %i: tween image id missing for "
			  "part \"%s\", description \"%s\" %f\n",
			  pc->id, ep->name, epd->common.state.name, epd->common.state.value);
    }
}

static void
check_packed_items(Edje_Part_Collection *pc, Edje_Part *ep, Eet_File *ef)
{
   unsigned int i;

   for (i = 0; i < ep->items_count; ++i)
     {
	if (ep->items[i]->type == EDJE_PART_TYPE_GROUP && !ep->items[i]->source)
	  error_and_abort(ef, "Collection %i: missing source on packed item "
			  "of type GROUP in part \"%s\"\n",
			  pc->id, ep->name);
	if (ep->type == EDJE_PART_TYPE_TABLE && (ep->items[i]->col < 0 || ep->items[i]->row < 0))
	  error_and_abort(ef, "Collection %i: missing col/row on packed item "
			  "for part \"%s\" of type TABLE\n",
			  pc->id, ep->name);
     }
}

static void
check_nameless_state(Edje_Part_Collection *pc, Edje_Part *ep, Edje_Part_Description_Common *ed, Eet_File *ef)
{
   if (!ed->state.name)
      error_and_abort(ef, "Collection %i: description with state missing on part \"%s\"\n",
                      pc->id, ep->name);
}

static void
check_part (Edje_Part_Collection *pc, Edje_Part *ep, Eet_File *ef)
{
   unsigned int i;
   /* FIXME: check image set and sort them. */
   if (!ep->default_desc)
     error_and_abort(ef, "Collection %i: default description missing "
		     "for part \"%s\"\n", pc->id, ep->name);

   for (i = 0; i < ep->other.desc_count; ++i)
     check_nameless_state(pc, ep, ep->other.desc[i], ef);

   if (ep->type == EDJE_PART_TYPE_IMAGE)
     {
	check_image_part_desc(pc, ep, (Edje_Part_Description_Image*) ep->default_desc, ef);

	for (i = 0; i < ep->other.desc_count; ++i)
	  check_image_part_desc (pc, ep, (Edje_Part_Description_Image*) ep->other.desc[i], ef);
     }
   else if ((ep->type == EDJE_PART_TYPE_BOX) ||
	    (ep->type == EDJE_PART_TYPE_TABLE))
     check_packed_items(pc, ep, ef);
}

static void
check_program (Edje_Part_Collection *pc, Edje_Program *ep, Eet_File *ef)
{
   switch (ep->action)
     {
      case EDJE_ACTION_TYPE_STATE_SET:
      case EDJE_ACTION_TYPE_ACTION_STOP:
      case EDJE_ACTION_TYPE_DRAG_VAL_SET:
      case EDJE_ACTION_TYPE_DRAG_VAL_STEP:
      case EDJE_ACTION_TYPE_DRAG_VAL_PAGE:
	 if (!ep->targets)
	   error_and_abort(ef, "Collection %i: target missing in program "
			   "\"%s\"\n", pc->id, ep->name);
	 break;
      default:
	 break;
     }
}

static int
data_write_header(Eet_File *ef)
{
   int bytes = 0;

   if (edje_file)
     {
	if (edje_file->collection)
	  {
	     Edje_Part_Collection_Directory_Entry *ce;

	     /* copy aliases into collection directory */
	     EINA_LIST_FREE(aliases, ce)
	       {
		  Edje_Part_Collection_Directory_Entry *sce;
		  Eina_Iterator *it;

		  if (!ce->entry)
		    error_and_abort(ef, "Collection %i: name missing.\n", ce->id);

		  it = eina_hash_iterator_data_new(edje_file->collection);

		  EINA_ITERATOR_FOREACH(it, sce)
		    if (ce->id == sce->id)
		      {
			 memcpy(&ce->count, &sce->count, sizeof (ce->count));
			 break;
		      }

		  if (!sce)
		    error_and_abort(ef, "Collection %s (%i) can't find an correct alias.\n", ce->entry, ce->id);

		  eina_iterator_free(it);

		  eina_hash_direct_add(edje_file->collection, ce->entry, ce);
	       }
	  }
	bytes = eet_data_write(ef, edd_edje_file, "edje/file", edje_file, 1);
	if (bytes <= 0)
	  error_and_abort(ef, "Unable to write \"edje_file\" entry to \"%s\" \n",
			  file_out);
     }

   if (verbose)
     {
	printf("%s: Wrote %9i bytes (%4iKb) for \"edje_file\" header\n",
	       progname, bytes, (bytes + 512) / 1024);
     }

   return bytes;
}

static int
data_write_fonts(Eet_File *ef, int *font_num, int *input_bytes, int *input_raw_bytes)
{
   Eina_Iterator *it;
   int bytes = 0;
   int total_bytes = 0;
   Font *fn;

   if (!edje_file->fonts)
     return 0;

   it = eina_hash_iterator_data_new(edje_file->fonts);
   EINA_ITERATOR_FOREACH(it, fn)
     {
	void *fdata = NULL;
	int fsize = 0;
	Eina_List *ll;
	FILE *f;

	f = fopen(fn->file, "rb");
	if (f)
	  {
	     long pos;

	     fseek(f, 0, SEEK_END);
	     pos = ftell(f);
	     rewind(f);
	     fdata = malloc(pos);
	     if (fdata)
	       {
		  if (fread(fdata, pos, 1, f) != 1)
		    error_and_abort(ef, "Unable to read all of font "
				    "file \"%s\"\n", fn->file);
		  fsize = pos;
	       }
	     fclose(f);
	  }
	else
	  {
	     char *data;

	     EINA_LIST_FOREACH(fnt_dirs, ll, data)
	       {
		  char buf[4096];

		  snprintf(buf, sizeof(buf), "%s/%s", data, fn->file);
		  f = fopen(buf, "rb");
		  if (f)
		    {
		       long pos;

		       fseek(f, 0, SEEK_END);
		       pos = ftell(f);
		       rewind(f);
		       fdata = malloc(pos);
		       if (fdata)
			 {
			    if (fread(fdata, pos, 1, f) != 1)
			      error_and_abort(ef, "Unable to read all of font "
					      "file \"%s\"\n", buf);
			    fsize = pos;
			 }
		       fclose(f);
		       if (fdata) break;
		    }
	       }
	  }
	if (!fdata)
	  {
	     error_and_abort(ef, "Unable to load font part \"%s\" entry "
			     "to %s \n", fn->file, file_out);
	  }
	else
	  {
	     char buf[4096];

	     snprintf(buf, sizeof(buf), "edje/fonts/%s", fn->name);
	     bytes = eet_write(ef, buf, fdata, fsize, 1);
	     if (bytes <= 0)
	       error_and_abort(ef, "Unable to write font part \"%s\" as \"%s\" "
			       "part entry to %s \n", fn->file, buf, file_out);

	     *font_num += 1;
	     total_bytes += bytes;
	     *input_bytes += fsize;
	     *input_raw_bytes += fsize;

	     if (verbose)
	       {
		  printf("%s: Wrote %9i bytes (%4iKb) for \"%s\" font entry \"%s\" compress: [real: %2.1f%%]\n",
			 progname, bytes, (bytes + 512) / 1024, buf, fn->file,
			 100 - (100 * (double)bytes) / ((double)(fsize))
			 );
	       }
	     free(fdata);
	  }
     }
   eina_iterator_free(it);

   return total_bytes;
}

static void
error_and_abort_image_load_error(Eet_File *ef, const char *file, int error)
{
   const char *errmsg = evas_load_error_str(error);
   char hint[1024] = "";

   if (error == EVAS_LOAD_ERROR_DOES_NOT_EXIST)
     {
	snprintf
	  (hint, sizeof(hint),
	   " Check if path to file \"%s\" is correct "
	   "(both directory and file name).",
	   file);
     }
   else if (error == EVAS_LOAD_ERROR_CORRUPT_FILE)
     {
	snprintf
	  (hint, sizeof(hint),
	   " Check if file \"%s\" is consistent.",
	   file);
     }
   else if (error == EVAS_LOAD_ERROR_UNKNOWN_FORMAT)
     {
	const char *ext = strrchr(file, '.');
	const char **itr, *known_loaders[] = {
	  /* list from evas_image_load.c */
	  "png",
	  "jpg",
	  "jpeg",
	  "jfif",
	  "eet",
	  "edj",
	  "eap",
	  "edb",
	  "xpm",
	  "tiff",
	  "tif",
	  "svg",
	  "svgz",
	  "gif",
	  "pbm",
	  "pgm",
	  "ppm",
	  "pnm",
	  NULL
	};

	if (!ext)
	  {
	     snprintf
	       (hint, sizeof(hint),
		" File \"%s\" does not have an extension, "
		"maybe it should?",
		file);
	     goto show_err;
	  }

	ext++;
	for (itr = known_loaders; *itr; itr++)
	  {
	     if (strcasecmp(ext, *itr) == 0)
	       {
		  snprintf
		    (hint, sizeof(hint),
		     " Check if Evas was compiled with %s module enabled and "
		     "all required dependencies exist.",
		     ext);
		  goto show_err;
	       }
	  }

	snprintf(hint, sizeof(hint),
		 " Check if Evas supports loading files of type \"%s\" (%s) "
		 "and this module was compiled and all its dependencies exist.",
		 ext, file);
     }
 show_err:
   error_and_abort
     (ef, "Unable to load image \"%s\" used by file \"%s\": %s.%s\n",
      file, file_out, errmsg, hint);
}

static int
data_write_images(Eet_File *ef, int *image_num, int *input_bytes, int *input_raw_bytes)
{
   unsigned int i;
   int bytes = 0;
   int total_bytes = 0;

   if ((edje_file) && (edje_file->image_dir))
     {
	Ecore_Evas *ee;
	Evas *evas;
	Edje_Image_Directory_Entry *img;

	ecore_init();
	ecore_evas_init();

	ee = ecore_evas_buffer_new(1, 1);
	if (!ee)
	  error_and_abort(ef, "Cannot create buffer engine canvas for image "
			  "load.\n");

	evas = ecore_evas_get(ee);
	for (i = 0; i < edje_file->image_dir->entries_count; i++)
	  {
	     img = &edje_file->image_dir->entries[i];

	     if (img->source_type == EDJE_IMAGE_SOURCE_TYPE_EXTERNAL)
	       {
	       }
	     else
	       {
		  Evas_Object *im;
		  Eina_List *ll;
		  char *data;
		  int load_err = EVAS_LOAD_ERROR_NONE;

		  im = NULL;
		  EINA_LIST_FOREACH(img_dirs, ll, data)
		    {
		       char buf[4096];

		       snprintf(buf, sizeof(buf), "%s/%s",
				data, img->entry);
		       im = evas_object_image_add(evas);
		       if (im)
			 {
			    evas_object_image_file_set(im, buf, NULL);
			    load_err = evas_object_image_load_error_get(im);
			    if (load_err == EVAS_LOAD_ERROR_NONE)
			      break;
			    evas_object_del(im);
			    im = NULL;
			    if (load_err != EVAS_LOAD_ERROR_DOES_NOT_EXIST)
			      break;
			 }
		    }
		  if ((!im) && (load_err == EVAS_LOAD_ERROR_DOES_NOT_EXIST))
		    {
		       im = evas_object_image_add(evas);
		       if (im)
			 {
			    evas_object_image_file_set(im, img->entry, NULL);
			    load_err = evas_object_image_load_error_get(im);
			    if (load_err != EVAS_LOAD_ERROR_NONE)
			      {
				 evas_object_del(im);
				 im = NULL;
			      }
			 }
		    }
		  if (im)
		    {
		       void *im_data;
		       int  im_w, im_h;
		       int  im_alpha;
		       char buf[256];

		       evas_object_image_size_get(im, &im_w, &im_h);
		       im_alpha = evas_object_image_alpha_get(im);
		       im_data = evas_object_image_data_get(im, 0);
		       if ((im_data) && (im_w > 0) && (im_h > 0))
			 {
			    int mode, qual;

			    snprintf(buf, sizeof(buf), "edje/images/%i", img->id);
			    qual = 80;
			    if ((img->source_type == EDJE_IMAGE_SOURCE_TYPE_INLINE_PERFECT) &&
				(img->source_param == 0))
			      mode = 0; /* RAW */
			    else if ((img->source_type == EDJE_IMAGE_SOURCE_TYPE_INLINE_PERFECT) &&
				     (img->source_param == 1))
			      mode = 1; /* COMPRESS */
			    else
			      mode = 2; /* LOSSY */
			    if ((mode == 0) && (no_raw))
			      {
				 mode = 1; /* promote compression */
				 img->source_param = 95;
			      }
			    if ((mode == 2) && (no_lossy)) mode = 1; /* demote compression */
			    if ((mode == 1) && (no_comp))
			      {
				 if (no_lossy) mode = 0; /* demote compression */
				 else if (no_raw)
				   {
				      img->source_param = 90;
				      mode = 2; /* no choice. lossy */
				   }
			      }
			    if (mode == 2)
			      {
				 qual = img->source_param;
				 if (qual < min_quality) qual = min_quality;
				 if (qual > max_quality) qual = max_quality;
			      }
			    if (mode == 0)
			      bytes = eet_data_image_write(ef, buf,
							   im_data, im_w, im_h,
							   im_alpha,
							   0, 0, 0);
			    else if (mode == 1)
			      bytes = eet_data_image_write(ef, buf,
							   im_data, im_w, im_h,
							   im_alpha,
							   1, 0, 0);
			    else if (mode == 2)
			      bytes = eet_data_image_write(ef, buf,
							   im_data, im_w, im_h,
							   im_alpha,
							   0, qual, 1);
			    if (bytes <= 0)
			      error_and_abort(ef, "Unable to write image part "
					      "\"%s\" as \"%s\" part entry to "
					      "%s\n", img->entry, buf,
					      file_out);

			    *image_num += 1;
			    total_bytes += bytes;
			 }
		       else
			 {
			    error_and_abort_image_load_error
			      (ef, img->entry, load_err);
			 }

		       if (verbose)
			 {
			    struct stat st;
			    const char *file = NULL;

			    evas_object_image_file_get(im, &file, NULL);
			    if (!file || (stat(file, &st) != 0))
			      st.st_size = 0;
			    *input_bytes += st.st_size;
			    *input_raw_bytes += im_w * im_h * 4;
			    printf("%s: Wrote %9i bytes (%4iKb) for \"%s\" image entry \"%s\" compress: [raw: %2.1f%%] [real: %2.1f%%]\n",
				   progname, bytes, (bytes + 512) / 1024, buf, img->entry,
				   100 - (100 * (double)bytes) / ((double)(im_w * im_h * 4)),
				   100 - (100 * (double)bytes) / ((double)(st.st_size))
				   );
			 }
		       evas_object_del(im);
		    }
		  else
		    {
		       error_and_abort_image_load_error
			 (ef, img->entry, load_err);
		    }
	       }
	  }
	ecore_evas_free(ee);
	ecore_evas_shutdown();
	ecore_shutdown();
     }

   return total_bytes;
}

static void
check_groups(Eet_File *ef)
{
   Edje_Part_Collection *pc;
   Eina_List *l;

   /* sanity checks for parts and programs */
   EINA_LIST_FOREACH(edje_collections, l, pc)
     {
	unsigned int i;

	for (i = 0; i < pc->parts_count; ++i)
	  check_part(pc, pc->parts[i], ef);

#define CHECK_PROGRAM(Type, Pc, It)				\
	for (It = 0; It < Pc->programs.Type ## _count; ++It)	\
	  check_program(Pc, Pc->programs.Type[i], ef);		\

	CHECK_PROGRAM(fnmatch, pc, i);
	CHECK_PROGRAM(strcmp, pc, i);
	CHECK_PROGRAM(strncmp, pc, i);
	CHECK_PROGRAM(strrncmp, pc, i);
	CHECK_PROGRAM(nocmp, pc, i);
     }
}

static int
data_write_groups(Eet_File *ef, int *collection_num)
{
   Eina_List *l;
   Edje_Part_Collection *pc;
   int bytes = 0;
   int total_bytes = 0;

   EINA_LIST_FOREACH(edje_collections, l, pc)
     {
	char buf[4096];

	snprintf(buf, sizeof(buf), "edje/collections/%i", pc->id);
	bytes = eet_data_write(ef, edd_edje_part_collection, buf, pc, 1);
	if (bytes <= 0)
	  error_and_abort(ef, "Error. Unable to write \"%s\" part entry "
			  "to %s\n", buf, file_out);

	*collection_num += 1;
	total_bytes += bytes;

	if (verbose)
	  {
	     printf("%s: Wrote %9i bytes (%4iKb) for \"%s\" collection entry\n",
		    progname, bytes, (bytes + 512) / 1024, buf);
	  }
     }

   return total_bytes;
}

static void
create_script_file(Eet_File *ef, const char *filename, const Code *cd)
{
   FILE *f = fopen(filename, "wb");
   if (!f)
     error_and_abort(ef, "Unable to open temp file \"%s\" for script "
		     "compilation.\n", filename);

   Eina_List *ll;
   Code_Program *cp;

   fprintf(f, "#include <edje>\n");
   int ln = 2;

   if (cd->shared)
     {
	while (ln < (cd->l1 - 1))
	  {
	     fprintf(f, " \n");
	     ln++;
	  }
	{
	   char *sp;
	   int hash = 0;
	   int newlined = 0;

	   for (sp = cd->shared; *sp; sp++)
	     {
		if ((sp[0] == '#') && (newlined))
		  {
		     hash = 1;
		  }
		newlined = 0;
		if (sp[0] == '\n') newlined = 1;
		if (!hash) fputc(sp[0], f);
		else if (sp[0] == '\n') hash = 0;
	     }
	   fputc('\n', f);
	}
	ln += cd->l2 - cd->l1 + 1;
     }
   EINA_LIST_FOREACH(cd->programs, ll, cp)
     {
	if (cp->script)
	  {
	     while (ln < (cp->l1 - 1))
	       {
		  fprintf(f, " \n");
		  ln++;
	       }
	     /* FIXME: this prototype needs to be */
	     /* formalised and set in stone */
	     fprintf(f, "public _p%i(sig[], src[]) {", cp->id);
	     {
		char *sp;
		int hash = 0;
		int newlined = 0;

		for (sp = cp->script; *sp; sp++)
		  {
		     if ((sp[0] == '#') && (newlined))
		       {
			  hash = 1;
		       }
		     newlined = 0;
		     if (sp[0] == '\n') newlined = 1;
		     if (!hash) fputc(sp[0], f);
		     else if (sp[0] == '\n') hash = 0;
		  }
	     }
	     fprintf(f, "}\n");
	     ln += cp->l2 - cp->l1 + 1;
	  }
     }

   fclose(f);
}

static void
compile_script_file(Eet_File *ef, const char *source, const char *output,
		    int script_num)
{
   FILE *f;
   char buf[4096];
   int ret;

   snprintf(buf, sizeof(buf),
	    "embryo_cc -i %s/include -o %s %s",
	    eina_prefix_data_get(pfx), output, source);
   ret = system(buf);

   /* accept warnings in the embryo code */
   if (ret < 0 || ret > 1)
     error_and_abort(ef, "Compiling script code not clean.\n");

   f = fopen(output, "rb");
   if (!f)
     error_and_abort(ef, "Unable to open script object \"%s\" for reading.\n",
		     output);

   fseek(f, 0, SEEK_END);
   int size = ftell(f);
   rewind(f);

   if (size > 0)
     {
	void *data = malloc(size);

	if (data)
	  {
	     if (fread(data, size, 1, f) != 1)
	       error_and_abort(ef, "Unable to read all of script object "
			       "\"%s\"\n", output);

	     snprintf(buf, sizeof(buf), "edje/scripts/embryo/compiled/%i", script_num);
	     eet_write(ef, buf, data, size, 1);
	     free(data);
	  }
     }

   fclose(f);
}

static void
data_write_scripts(Eet_File *ef)
{
   Eina_List *l;
   int i;

   if (!tmp_dir)
#ifdef HAVE_EVIL
     tmp_dir = (char *)evil_tmpdir_get();
#else
     tmp_dir = "/tmp";
#endif

   for (i = 0, l = codes; l; l = eina_list_next(l), i++)
     {
	char tmpn[PATH_MAX];
	char tmpo[PATH_MAX];
        char buf[PATH_MAX];
	int fd;
	Code *cd = eina_list_data_get(l);
        Eina_List *ll;
        Code_Program *cp;
	
	if (cd->is_lua)
	  continue;
	if ((!cd->shared) && (!cd->programs))
	  continue;

	snprintf(tmpn, PATH_MAX, "%s/edje_cc.sma-tmp-XXXXXX", tmp_dir);
	fd = mkstemp(tmpn);
	if (fd < 0)
	  error_and_abort(ef, "Unable to open temp file \"%s\" for script "
			  "compilation.\n", tmpn);

	create_script_file(ef, tmpn, cd);
	close(fd);

	snprintf(tmpo, PATH_MAX, "%s/edje_cc.amx-tmp-XXXXXX", tmp_dir);
	fd = mkstemp(tmpo);
	if (fd < 0)
	  {
	     unlink(tmpn);
	     error_and_abort(ef, "Unable to open temp file \"%s\" for script "
			     "compilation.\n", tmpn);
	  }

	compile_script_file(ef, tmpn, tmpo, i);
	close(fd);

	unlink(tmpn);
	unlink(tmpo);

        if (cd->original)
          {
             snprintf(buf, PATH_MAX, "edje/scripts/embryo/source/%i", i);
             eet_write(ef, buf, cd->original, strlen(cd->original) + 1, 1);
          }
        EINA_LIST_FOREACH(cd->programs, ll, cp)
          {
             if (!cp->original)
               continue;
             snprintf(buf, PATH_MAX, "edje/scripts/embryo/source/%i/%i", i,
                      cp->id);
             eet_write(ef, buf, cp->original, strlen(cp->original) + 1, 1);
          }
     }
}

typedef struct _Edje_Lua_Script_Writer_Struct Edje_Lua_Script_Writer_Struct;

struct _Edje_Lua_Script_Writer_Struct
{
   char *buf;
   int size;
};

#ifdef LUA_BINARY
static int
_edje_lua_script_writer(lua_State *L __UNUSED__, const void *chunk_buf, size_t chunk_size, void *_data)
{
   Edje_Lua_Script_Writer_Struct *data;
   void *old;

   data = (Edje_Lua_Script_Writer_Struct *)_data;
   old = data->buf;
   data->buf = malloc (data->size + chunk_size);
   memcpy (data->buf, old, data->size);
   memcpy (&((data->buf)[data->size]), chunk_buf, chunk_size);
   if (old)
     free (old);
   data->size += chunk_size;

   return 0;
}
#endif

void
_edje_lua_error_and_abort(lua_State * L, int err_code, Eet_File *ef)
{
   char *err_type;

   switch (err_code)
     {
     case LUA_ERRRUN:
	err_type = "runtime";
	break;
     case LUA_ERRSYNTAX:
	err_type = "syntax";
	break;
     case LUA_ERRMEM:
	err_type = "memory allocation";
	break;
     case LUA_ERRERR:
	err_type = "error handler";
	break;
     default:
	err_type = "unknown";
	break;
     }
   error_and_abort(ef, "Lua %s error: %s\n", err_type, lua_tostring(L, -1));
}


static void
data_write_lua_scripts(Eet_File *ef)
{
   Eina_List *l;
   Eina_List *ll;
   Code_Program *cp;
   int i;

   for (i = 0, l = codes; l; l = eina_list_next(l), i++)
     {
	char buf[4096];
	Code *cd;
	lua_State *L;
	int ln = 1;
	luaL_Buffer b;
	Edje_Lua_Script_Writer_Struct data;
#ifdef LUA_BINARY
	int err_code;
#endif

	cd = (Code *)eina_list_data_get(l);
	if (!cd->is_lua)
	  continue;
	if ((!cd->shared) && (!cd->programs))
	  continue;
	
	L = luaL_newstate();
	if (!L)
	  error_and_abort(ef, "Lua error: Lua state could not be initialized\n");

	luaL_buffinit(L, &b);

	data.buf = NULL;
	data.size = 0;
	if (cd->shared)
	  {
	     while (ln < (cd->l1 - 1))
	       {
		  luaL_addchar(&b, '\n');
		  ln++;
	       }
	     luaL_addstring(&b, cd->shared);
	     ln += cd->l2 - cd->l1;
	  }

	EINA_LIST_FOREACH(cd->programs, ll, cp)
	  {
	     if (cp->script)
	       {
		  while (ln < (cp->l1 - 1))
		    {
		       luaL_addchar(&b, '\n');
		       ln++;
		    }
		  luaL_addstring(&b, "_G[");
		  lua_pushnumber(L, cp->id);
		  luaL_addvalue(&b);
		  luaL_addstring(&b, "] = function (ed, signal, source)");
		  luaL_addstring(&b, cp->script);
		  luaL_addstring(&b, "end\n");
		  ln += cp->l2 - cp->l1 + 1;
	       }
	  }
	luaL_pushresult(&b);
#ifdef LUA_BINARY
	if (err_code = luaL_loadstring(L, lua_tostring (L, -1)))
	  _edje_lua_error_and_abort(L, err_code, ef);
	lua_dump(L, _edje_lua_script_writer, &data);
#else // LUA_PLAIN_TEXT
	data.buf = (char *)lua_tostring(L, -1);
	data.size = strlen(data.buf);
#endif
	//printf("lua chunk size: %d\n", data.size);

	/* 
	 * TODO load and test Lua chunk
	 */

	/*
	   if (luaL_loadbuffer(L, globbuf, globbufsize, "edje_lua_script"))
	   printf("lua load error: %s\n", lua_tostring (L, -1));
	   if (lua_pcall(L, 0, 0, 0))
	   printf("lua call error: %s\n", lua_tostring (L, -1));
	 */
	
	snprintf(buf, sizeof(buf), "edje/scripts/lua/%i", i);
	eet_write(ef, buf, data.buf, data.size, 1);
#ifdef LUA_BINARY
	free(data.buf);
#endif
	lua_close(L);
     }
}

void
data_write(void)
{
   Eet_File *ef;
   int input_bytes = 0;
   int total_bytes = 0;
   int src_bytes = 0;
   int fmap_bytes = 0;
   int input_raw_bytes = 0;
   int image_num = 0;
   int font_num = 0;
   int collection_num = 0;

   if (!edje_file)
     {
	ERR("%s: Error. No data to put in \"%s\"",
	    progname, file_out);
	exit(-1);
     }

   ef = eet_open(file_out, EET_FILE_MODE_WRITE);
   if (!ef)
     {
	ERR("%s: Error. Unable to open \"%s\" for writing output",
	    progname, file_out);
	exit(-1);
     }

   check_groups(ef);

   total_bytes += data_write_header(ef);
   total_bytes += data_write_fonts(ef, &font_num, &input_bytes,
				   &input_raw_bytes);
   total_bytes += data_write_images(ef, &image_num, &input_bytes,
				    &input_raw_bytes);

   total_bytes += data_write_groups(ef, &collection_num);
   data_write_scripts(ef);
   data_write_lua_scripts(ef);

   src_bytes = source_append(ef);
   total_bytes += src_bytes;
   fmap_bytes = source_fontmap_save(ef, fonts);
   total_bytes += fmap_bytes;

   eet_close(ef);

   if (verbose)
     {
	struct stat st;

	if (stat(file_in, &st) != 0)
	  st.st_size = 0;
	input_bytes += st.st_size;
	input_raw_bytes += st.st_size;
	printf("Summary:\n"
	       "  Wrote %i collections\n"
	       "  Wrote %i images\n"
	       "  Wrote %i fonts\n"
	       "  Wrote %i bytes (%iKb) of original source data\n"
	       "  Wrote %i bytes (%iKb) of original source font map\n"
	       "Conservative compression summary:\n"
	       "  Wrote total %i bytes (%iKb) from %i (%iKb) input data\n"
	       "  Output file is %3.1f%% the size of the input data\n"
	       "  Saved %i bytes (%iKb)\n"
	       "Raw compression summary:\n"
	       "  Wrote total %i bytes (%iKb) from %i (%iKb) raw input data\n"
	       "  Output file is %3.1f%% the size of the raw input data\n"
	       "  Saved %i bytes (%iKb)\n"
	       ,
	       collection_num,
	       image_num,
	       font_num,
	       src_bytes, (src_bytes + 512) / 1024,
	       fmap_bytes, (fmap_bytes + 512) / 1024,
	       total_bytes, (total_bytes + 512) / 1024,
	       input_bytes, (input_bytes + 512) / 1024,
	       (100.0 * (double)total_bytes) / (double)input_bytes,
	       input_bytes - total_bytes,
	       (input_bytes - total_bytes + 512) / 1024,
	       total_bytes, (total_bytes + 512) / 1024,
	       input_raw_bytes, (input_raw_bytes + 512) / 1024,
	       (100.0 * (double)total_bytes) / (double)input_raw_bytes,
	       input_raw_bytes - total_bytes,
	       (input_raw_bytes - total_bytes + 512) / 1024);
     }
}

void
data_queue_group_lookup(char *name)
{
   Group_Lookup *gl;

   gl = mem_alloc(SZ(Group_Lookup));
   group_lookups = eina_list_append(group_lookups, gl);
   gl->name = mem_strdup(name);
}

void
data_queue_part_lookup(Edje_Part_Collection *pc, const char *name, int *dest)
{
   Part_Lookup *pl;

   pl = mem_alloc(SZ(Part_Lookup));
   part_lookups = eina_list_append(part_lookups, pl);
   pl->pc = pc;
   pl->name = mem_strdup(name);
   pl->dest = dest;
}

void
data_queue_anonymous_lookup(Edje_Part_Collection *pc, Edje_Program *ep, int *dest)
{
   Program_Lookup *pl;

   if (!ep) return ; /* FIXME: should we stop compiling ? */

   pl = mem_alloc(SZ(Program_Lookup));
   program_lookups = eina_list_append(program_lookups, pl);
   pl->pc = pc;
   pl->u.ep = ep;
   pl->dest = dest;
   pl->anonymous = EINA_TRUE;
}

void
data_queue_program_lookup(Edje_Part_Collection *pc, const char *name, int *dest)
{
   Program_Lookup *pl;

   if (!name) return ; /* FIXME: should we stop compiling ? */

   pl = mem_alloc(SZ(Program_Lookup));
   program_lookups = eina_list_append(program_lookups, pl);
   pl->pc = pc;
   pl->u.name = mem_strdup(name);
   pl->dest = dest;
   pl->anonymous = EINA_FALSE;
}

void
data_queue_image_lookup(char *name, int *dest, Eina_Bool *set)
{
   Image_Lookup *il;

   il = mem_alloc(SZ(Image_Lookup));
   image_lookups = eina_list_append(image_lookups, il);
   il->name = mem_strdup(name);
   il->dest = dest;
   il->set = set;
}

void
data_queue_part_slave_lookup(int *master, int *slave)
{
   Slave_Lookup *sl;

   sl = mem_alloc(SZ(Slave_Lookup));
   part_slave_lookups = eina_list_append(part_slave_lookups, sl);
   sl->master = master;
   sl->slave = slave;
}

void
data_queue_image_slave_lookup(int *master, int *slave)
{
   Slave_Lookup *sl;

   sl = mem_alloc(SZ(Slave_Lookup));
   image_slave_lookups = eina_list_append(image_slave_lookups, sl);
   sl->master = master;
   sl->slave = slave;
}

void
handle_slave_lookup(Eina_List *list, int *master, int value)
{
   Eina_List *l;
   Slave_Lookup *sl;

   EINA_LIST_FOREACH(list, l, sl)
     if (sl->master == master)
       *sl->slave = value;
}

void
data_process_lookups(void)
{
   Edje_Part_Collection *pc;
   Part_Lookup *part;
   Program_Lookup *program;
   Group_Lookup *group;
   Image_Lookup *image;
   Eina_List *l;
   void *data;

   EINA_LIST_FOREACH(edje_collections, l, pc)
     {
	unsigned int count = 0;
	unsigned int i;

#define PROGRAM_ID_SET(Type, Pc, It, Count)				\
	for (It = 0; It < Pc->programs.Type ## _count; ++It)		\
	  {								\
	     Pc->programs.Type[It]->id = Count++;			\
	  }

	PROGRAM_ID_SET(fnmatch, pc, i, count);
	PROGRAM_ID_SET(strcmp, pc, i, count);
	PROGRAM_ID_SET(strncmp, pc, i, count);
	PROGRAM_ID_SET(strrncmp, pc, i, count);
	PROGRAM_ID_SET(nocmp, pc, i, count);

#undef PROGRAM_ID_SET
     }

   EINA_LIST_FREE(part_lookups, part)
     {
	Edje_Part *ep;
	unsigned int i;

	for (i = 0; i < part->pc->parts_count; ++i)
	  {
	     ep = part->pc->parts[i];

	     if ((ep->name) && (!strcmp(ep->name, part->name)))
	       {
		  handle_slave_lookup(part_slave_lookups, part->dest, ep->id);
		  *(part->dest) = ep->id;
		  break;
	       }
	  }

	if (i == part->pc->parts_count)
	  {
	     ERR("%s: Error. Unable to find part name \"%s\".",
		 progname, part->name);
	     exit(-1);
	  }

	free(part->name);
	free(part);
     }

   EINA_LIST_FREE(program_lookups, program)
     {
	unsigned int i;
	Eina_Bool find = EINA_FALSE;

#define PROGRAM_MATCH(Type, Pl, It)					\
	for (It = 0; It < Pl->pc->programs.Type ## _count; ++It)	\
	  {								\
	     Edje_Program *ep;						\
	     								\
	     ep = Pl->pc->programs.Type[It];				\
	     								\
	     if ((Pl->anonymous && ep == Pl->u.ep) ||			\
		 ((!Pl->anonymous) && (ep->name) && (!strcmp(ep->name, Pl->u.name)))) \
	       {							\
		  *(Pl->dest) = ep->id;					\
		  find = EINA_TRUE;					\
		  break;						\
	       }							\
	  }

	PROGRAM_MATCH(fnmatch, program, i);
	PROGRAM_MATCH(strcmp, program, i);
	PROGRAM_MATCH(strncmp, program, i);
	PROGRAM_MATCH(strrncmp, program, i);
	PROGRAM_MATCH(nocmp, program, i);

#undef PROGRAM_MATCH

	if (!find)
	  {
	     if (!program->anonymous)
	       ERR("%s: Error. Unable to find program name \"%s\".",
		   progname, program->u.name);
	     else
	       ERR("%s: Error. Unable to find anonymous program.",
		   progname);
	     exit(-1);
	  }

	if (!program->anonymous)
	  free(program->u.name);
	free(program);
     }

   EINA_LIST_FREE(group_lookups, group)
     {
	Edje_Part_Collection_Directory_Entry *de;

	de = eina_hash_find(edje_file->collection, group->name);

	if (!de)
          {
             ERR("%s: Error. Unable to find group name \"%s\".",
		 progname, group->name);
             exit(-1);
          }

        free(group->name);
        free(group);
     }

   EINA_LIST_FREE(image_lookups, image)
     {
	Edje_Image_Directory_Entry *de;
	Eina_Bool find = EINA_FALSE;

	if (edje_file->image_dir)
	  {
	     unsigned int i;

	     for (i = 0; i < edje_file->image_dir->entries_count; ++i)
	       {
		  de = edje_file->image_dir->entries + i;

		  if ((de->entry) && (!strcmp(de->entry, image->name)))
		    {
		       handle_slave_lookup(image_slave_lookups, image->dest, de->id);
		       if (de->source_type == EDJE_IMAGE_SOURCE_TYPE_EXTERNAL)
			 *(image->dest) = -de->id - 1;
		       else
			 *(image->dest) = de->id;
		       *(image->set) = EINA_FALSE;
		       find = EINA_TRUE;
		       break;
		    }
	       }

	     if (!find)
	       {
		 Edje_Image_Directory_Set *set;

		 for (i = 0; i < edje_file->image_dir->sets_count; ++i)
		   {
		      set = edje_file->image_dir->sets + i;

		      if ((set->name) && (!strcmp(set->name, image->name)))
			{
			   handle_slave_lookup(image_slave_lookups, image->dest, set->id);
			   *(image->dest) = set->id;
			   *(image->set) = EINA_TRUE;
			   find = EINA_TRUE;
			   break;
			}
		   }
	       }
	  }

	if (!find)
	  {
	     ERR("%s: Error. Unable to find image name \"%s\".",
		 progname, image->name);
	     exit(-1);
	  }

	free(image->name);
	free(image);
     }

   EINA_LIST_FREE(part_slave_lookups, data)
     free(data);

   EINA_LIST_FREE(image_slave_lookups, data)
     free(data);
}

static void
data_process_string(Edje_Part_Collection *pc, const char *prefix, char *s, void (*func)(Edje_Part_Collection *pc, char *name, char* ptr, int len))
{
   char *p;
   char *key;
   int keyl;
   int quote, escape;

   key = alloca(strlen(prefix) + 2 + 1);
   if (!key) return;
   strcpy(key, prefix);
   strcat(key, ":\"");
   keyl = strlen(key);
   quote = 0;
   escape = 0;
   for (p = s; (p) && (*p); p++)
     {
	if (!quote)
	  {
	     if (*p == '\"')
	       {
		  quote = 1;
		  p++;
	       }
	  }
	if (!quote)
	  {
	     if (!strncmp(p, key, keyl))
	       {
                  char *ptr;
                  int len;
                  int inesc = 0;
		  char *name;

                  ptr = p;
                  p += keyl;
		  while ((*p))
		    {
		       if (!inesc)
		         {
		  	    if (*p == '\\') inesc = 1;
			    else if (*p == '\"')
			      {
			         /* string concatenation, see below */
				 if (*(p + 1) != '\"')
				   break;
				 else
				   p++;
			      }
			 }
                       else
                            inesc = 0;
                       p++;
		    }
		  len = p - ptr + 1;
		  name = alloca(len);
		  if (name)
		    {
		       char *pp;
		       int i;

		       name[0] = 0;
		       pp = ptr + keyl;
		       inesc = 0;
		       i = 0;
		       while (*pp)
		         {
		    	    if (!inesc)
			      {
			         if (*pp == '\\') inesc = 1;
			         else if (*pp == '\"')
			    	   {
				      /* concat strings like "foo""bar" to "foobar" */
				      if (*(pp + 1) == '\"')
				        pp++;
				      else
				        {
				   	   name[i] = 0;
					   break;
					}
				   }
				 else
				   {
				      name[i] = *pp;
				      name[i + 1] = 0;
				      i++;
				   }
			      }
			    else
                              inesc = 0;
			    pp++;
			}
		      func(pc, name, ptr, len);
		   }
              }
	  }
	else
	  {
	     if (!escape)
	       {
		  if (*p == '\"') quote = 0;
		  else if (*p == '\\') escape = 1;
	       }
	     else if (escape)
	       {
		  escape = 0;
	       }
	  }
     }
}

static void
_data_queue_part_lookup(Edje_Part_Collection *pc, char *name, char *ptr, int len)
{
   Code_Lookup *cl;
   cl = mem_alloc(SZ(Code_Lookup));
   cl->ptr = ptr;
   cl->len = len;

   data_queue_part_lookup(pc, name, &(cl->val));

   code_lookups = eina_list_append(code_lookups, cl);
}
static void
_data_queue_program_lookup(Edje_Part_Collection *pc, char *name, char *ptr, int len)
{
   Code_Lookup *cl;

   cl = mem_alloc(SZ(Code_Lookup));
   cl->ptr = ptr;
   cl->len = len;

   data_queue_program_lookup(pc, name, &(cl->val));

   code_lookups = eina_list_append(code_lookups, cl);
}
static void
_data_queue_group_lookup(Edje_Part_Collection *pc __UNUSED__, char *name, char *ptr __UNUSED__, int len __UNUSED__)
{
   data_queue_group_lookup(name);
}
static void
_data_queue_image_pc_lookup(Edje_Part_Collection *pc __UNUSED__, char *name, char *ptr, int len)
{
   Code_Lookup *cl;

   cl = mem_alloc(SZ(Code_Lookup));
   cl->ptr = ptr;
   cl->len = len;

   data_queue_image_lookup(name, &(cl->val),  &(cl->set));

   code_lookups = eina_list_append(code_lookups, cl);
}

void
data_process_scripts(void)
{
   Eina_List *l, *l2;

   for (l = codes, l2 = edje_collections; (l) && (l2); l = eina_list_next(l), l2 = eina_list_next(l2))
     {
	Edje_Part_Collection *pc;
	Code *cd;

	cd = eina_list_data_get(l);
	pc = eina_list_data_get(l2);

	if ((cd->shared) && (!cd->is_lua))
	  {
	     data_process_string(pc, "PART",    cd->shared, _data_queue_part_lookup);
	     data_process_string(pc, "PROGRAM", cd->shared, _data_queue_program_lookup);
	     data_process_string(pc, "IMAGE",   cd->shared, _data_queue_image_pc_lookup);
	     data_process_string(pc, "GROUP",   cd->shared, _data_queue_group_lookup);
	  }

	if (cd->programs)
	  {
	     Code_Program *cp;
	     Eina_List *ll;

	     EINA_LIST_FOREACH(cd->programs, ll, cp)
	       {
		  if (cp->script)
		    {
		       data_process_string(pc, "PART",    cp->script, _data_queue_part_lookup);
		       data_process_string(pc, "PROGRAM", cp->script, _data_queue_program_lookup);
		       data_process_string(pc, "IMAGE",   cp->script, _data_queue_image_pc_lookup);
		       data_process_string(pc, "GROUP",   cp->script, _data_queue_group_lookup);
		    }
	       }
	  }
     }
}

void
data_process_script_lookups(void)
{
   Eina_List *l;
   Code_Lookup *cl;

   EINA_LIST_FOREACH(code_lookups, l, cl)
     {
	char buf[12];
	int n;

	/* FIXME !! Handle set in program */
	n = eina_convert_itoa(cl->val, buf);
	if (n > cl->len)
	  {
	     ERR("%s: Error. The unexpected happened. A numeric replacement string was larger than the original!",
		 progname);
	     exit(-1);
	  }
	memset(cl->ptr, ' ', cl->len);
	strncpy(cl->ptr, buf, n);
     }
}
