/*
 * Parser for 8BIMdesc section in .abr v6 Photoshop brushes
 *
 * Use following command to build:
 * gcc -o parse_8bimdesc `pkg-config --cflags --libs gtk+-2.0` parse_8bimdesc.c
 *
 *  v1.0 : Eric Lamarque (May 2007)
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <glib.h>

void parse_any(FILE *abr, gint32 end_offset, const gchar* type, gint level);
gchar *parse_type(FILE *abr, gint32 end_offset, gchar *type);

gchar
abr_read_char(FILE *abr)
{
	return fgetc(abr);
}

gshort
abr_read_short(FILE *abr)
{
	gshort val;

	fread(&val, sizeof(val), 1, abr);
	
	return GUINT16_FROM_BE(val);
}

gint32
abr_read_long(FILE *abr)
{
	gint32 val;

	fread(&val, sizeof(val), 1, abr);

	return GUINT32_FROM_BE(val);
}

gboolean
abr_reach_8BIM_section(FILE* abr, const gchar *name)
{
	gchar tag[4];
	gchar tagname[5];
	gint32 section_size;
	gint r;

	/* find 8BIMname section */
	while(!feof(abr)) {
		r = fread(&tag, 1, 4, abr);
		if (r != 4) {
			g_print("Error: Cannot read 8BIM tag %s\n", g_strerror(errno));
			return FALSE;
		}
		if (strncmp(tag, "8BIM", 4)) {
			g_print("Error: Start tag not 8BIM\n");
			return FALSE;
		}
		r = fread(&tagname, 1, 4, abr);
		if (r != 4) {
			g_print("Error: Cannot read 8BIM tag name %s\n",
					g_strerror(errno));
			return FALSE;
		}
		tagname[4] = '\0';

		if (!strncmp(tagname, name, 4))
			return TRUE;
		
		section_size = abr_read_long(abr);
		fseek(abr, section_size, SEEK_CUR);
	}
	return FALSE;
}

void spc(gint32 level)
{
	gint i;
	for(i = 0; i < level; i++)
		g_print(" ");
}

gchar *parse_unicode(FILE *abr, gint32 end_offset)
{
	gint32 text_len;
	gchar *text;
	gint i;
	
	text_len = abr_read_long(abr);
	/* UCS-2 encoding - 2-bytes char */
	text = g_malloc(text_len * 2);
	fread(text, 1, text_len * 2, abr);
	
	for (i=0; i < text_len; i++)
		text[i] = text[i*2+1];

	return text;
}

void parse_item(FILE *abr, gint32 end_offset, gint level)
{
	gchar type[5];

	parse_type(abr, end_offset, type);

	parse_any(abr, end_offset, type, level);
}

void parse_list(FILE *abr, gint32 end_offset, gint level)
{
	gint32 n_items;
	gint32 i;

	n_items = abr_read_long(abr);
	spc(level);
	g_print (" %d items in list\n", n_items);

	for (i = 0; i< n_items; i++) {
		spc(level);
		g_print (" item %d\n", i+1);
		parse_item(abr, end_offset, level + 1);
	}
}

gchar *parse_type(FILE *abr, gint32 end_offset, gchar *type)
{

	fread(type, 1, 4, abr);
	type[4] = '\0';
}

gchar *parse_key(FILE *abr, gint32 end_offset)
{
	gint32 length;
	gchar *id;

	length = abr_read_long(abr);
	if (length == 0)
		length = 4;
	id = g_malloc(length + 1);
	fread(id, 1 , length, abr);

	id[length] = '\0';

	return id;
}

void parse_objc(FILE *abr, gint32 end_offset, gint level)
{
	gint32 length;
	gchar * classid;
	gchar * classid_unicode;
	gchar * key;
	gchar type[5];
	gint32 n_item;
	gint32 i;

	spc(level);
	g_print("Objc\n");
	classid_unicode = parse_unicode(abr, end_offset);
	if (classid_unicode[0]) {
		spc(level);
		g_print(" Unicode classid: %s\n", classid_unicode);
	}
	g_free(classid_unicode);

	length = abr_read_long(abr);
	if (length == 0)
		length = 4;
	classid = g_malloc(length + 1);

	fread(classid, 1, length, abr);
	classid[length] = '\0';
	spc(level);
	g_print(" Classid: %s\n", classid);

	n_item = abr_read_long(abr);

	for (i=0; i < n_item; i++) {
		key = parse_key(abr, end_offset);
		spc(level + 2);
		g_print("%s.%s :\n", classid, key);
		g_free(key);
		fread(type, 1, 4, abr);
		type[4] = '\0';
		spc(level + 2);
		g_print("  type %s\n ", type);
		parse_any(abr, end_offset, type, level + 4);
		
	}

	g_free (classid);
}


void parse_text(FILE *abr, gint32 end_offset, gint level)
{
	gchar *text;

	text = parse_unicode(abr, end_offset);

	spc(level);
	g_print("TEXT value %s\n", text);

	g_free(text);

}

void parse_bool(FILE *abr, gint32 end_offset, gint level)
{
	gboolean val;

	val = abr_read_char(abr);
	spc(level);
	if (val)
		g_print(" bool = TRUE\n");
	else
		g_print(" bool = FALSE\n");
}

void parse_untf(FILE *abr, gint32 end_offset, gint level)
{
	gchar unit[5];
	double val = 0;
	guint64 *p, buf;

	fread(unit, 1, 4, abr);
	unit[4] = '\0';
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
	fread(&buf, 1, sizeof(buf), abr);
	p = (guint64 *) &val;
	*p = GUINT64_FROM_BE(buf);
#else
	fread(&val, 1, sizeof(val), abr);
#endif
	spc(level);
	g_print("unit %s : val %lf\n", unit, val);
}

void parse_any(FILE *abr, gint32 end_offset, const gchar* type, gint level)
{
	if (!strncmp(type, "VlLs", 4)) {
		parse_list(abr, end_offset, level);
	} else if (!strncmp(type, "Objc", 4)) {
		parse_objc(abr, end_offset, level);
	} else if (!strncmp(type, "TEXT", 4)) {
		parse_text(abr, end_offset, level);
	} else if (!strncmp(type, "bool", 4)) {
		parse_bool(abr, end_offset, level);
	} else if (!strncmp(type, "UntF", 4)) {
		parse_untf(abr, end_offset, level);
	} else {
		g_print("Unknown type %s\n", type);
		exit (1);
	}
}


void parse_variable(FILE *abr, gint32 end_offset, gint level)
{
	gint32 length;
	gchar type[5];
	gchar *id;
	gint32 n_item;
	gint32 i;

	id = parse_key(abr, end_offset);

	parse_type(abr, end_offset, type);

	g_print("Variable id: %s type %s\n", id, type);

	g_free(id);

	parse_any(abr, end_offset, type, level);
}

void
parse_desc(FILE *abr)
{
	gint32 section_size;
	gint32 end_offset;

	section_size = abr_read_long(abr);
	end_offset = section_size + ftell(abr);
	
	fseek(abr, 22, SEEK_CUR);

	parse_variable (abr, end_offset, 0);
}

gboolean
abr_desc(const gchar *filename)
{
	FILE* abr;
	gshort version;
	gshort subversion;

	abr = fopen(filename, "rb");
	if (abr == NULL) {
		g_print("Cannot open %s\n", filename);
		return FALSE;
	}

	version = abr_read_short(abr);
	if (version != 6) {
		g_print("Not a version 6 abr file\n");
		return FALSE;
	}

	subversion = abr_read_short(abr);
	g_print("Abr version 6 subver %d\n", subversion);

	if (!abr_reach_8BIM_section(abr, "desc")) {
		g_print("Cannot find 8BIMdesc section\n");
		return FALSE;
	}

	parse_desc(abr);

	fclose(abr);

	return TRUE;
}

gint main(gint argc, gchar **argv)
{
	if (argc < 2) {
		g_print("usage: %s file.abr\n", argv[0]);
		return 1;
	}

	if (abr_desc(argv[1]))
		return 0;

	return 1;
}
