/* MDB Tools - A library for reading MS Access database file
 * Copyright (C) 2000-2011 Brian Bruns and others
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "mdbtools.h"

static GPtrArray *
mdb_read_props_list(MdbHandle *mdb, gchar *kkd, int len)
{
	guint32 record_len;
	int pos = 0;
	gchar *name;
	GPtrArray *names = NULL;
	int i=0;

	names = g_ptr_array_new();
#if MDB_DEBUG
	mdb_buffer_dump(kkd, 0, len);
#endif
	pos = 0;
	while (pos < len) {
		record_len = mdb_get_int16(kkd, pos);
		pos += 2;
		if (mdb_get_option(MDB_DEBUG_PROPS)) {
			fprintf(stderr, "%02d ",i++);
			mdb_buffer_dump(kkd, pos - 2, record_len + 2);
		}
		name = g_malloc(3*record_len + 1); /* worst case scenario is 3 bytes out per byte in */
		mdb_unicode2ascii(mdb, &kkd[pos], record_len, name, 3*record_len + 1);

		pos += record_len;
		g_ptr_array_add(names, name);
#if MDB_DEBUG
		printf("new len = %d\n", names->len);
#endif
	}
	return names;
}
static void
free_hash_entry(gpointer key, gpointer value, gpointer user_data)
{
	g_free(key);
	g_free(value);
}
void
mdb_free_props(MdbProperties *props)
{
	if (!props) return;

	if (props->name) g_free(props->name);
	if (props->hash) {
		g_hash_table_foreach(props->hash, free_hash_entry, 0);
		g_hash_table_destroy(props->hash);
	}
	g_free(props);
}

static void
do_g_free(gpointer ptr, gpointer user_data) {
	g_free(ptr);
}

static void
free_names(GPtrArray *names) {
	g_ptr_array_foreach(names, do_g_free, NULL);
	g_ptr_array_free(names, TRUE);
}
MdbProperties *
mdb_alloc_props(void)
{
	MdbProperties *props;

	props = g_malloc0(sizeof(MdbProperties));

	return props;
}
static MdbProperties *
mdb_read_props(MdbHandle *mdb, GPtrArray *names, gchar *kkd, int len)
{
	guint32 record_len, name_len;
	int pos = 0;
	guint elem;
	int dtype, dsize;
	gchar *name, *value;
	MdbProperties *props;
	int i=0;

#if MDB_DEBUG
	mdb_buffer_dump(kkd, 0, len);
#endif
	pos = 0;

	record_len = mdb_get_int16(kkd, pos);
	pos += 4;
	name_len = mdb_get_int16(kkd, pos);
	pos += 2;
	props = mdb_alloc_props();
	if (name_len) {
		props->name = g_malloc(3*name_len + 1);
		mdb_unicode2ascii(mdb, kkd+pos, name_len, props->name, 3*name_len + 1);
		mdb_debug(MDB_DEBUG_PROPS,"prop block named: %s", props->name);
	}
	pos += name_len;

	props->hash = g_hash_table_new(g_str_hash, g_str_equal);

	while (pos < len) {
		record_len = mdb_get_int16(kkd, pos);
		dtype = kkd[pos + 3];
		elem = mdb_get_int16(kkd, pos + 4);
		if (elem >= names->len)
			break;
		dsize = mdb_get_int16(kkd, pos + 6);
		if (dsize < 0 || pos + 8 + dsize > len)
			break;
		value = g_strdup_printf("%.*s", dsize, &kkd[pos+8]);
		name = g_ptr_array_index(names,elem);
		if (mdb_get_option(MDB_DEBUG_PROPS)) {
			fprintf(stderr, "%02d ",i++);
			mdb_debug(MDB_DEBUG_PROPS,"elem %d (%s) dsize %d dtype %d", elem, name, dsize, dtype);
			mdb_buffer_dump(value, 0, dsize);
		}
		if (dtype == MDB_MEMO) {
			dtype = MDB_TEXT;
		} else if (dtype == MDB_BINARY && dsize == 16 && strcmp(name, "GUID") == 0) {
			dtype = MDB_REPID;
		}
		if (dtype == MDB_BOOL) {
			g_hash_table_insert(props->hash, g_strdup(name),
				g_strdup(kkd[pos + 8] ? "yes" : "no"));
		} else if (dtype == MDB_BINARY || dtype == MDB_OLE) {
			g_hash_table_insert(props->hash, g_strdup(name),
				g_strdup_printf("(binary data of length %d)", dsize));
		} else {
			g_hash_table_insert(props->hash, g_strdup(name),
			  mdb_col_to_string(mdb, kkd, pos + 8, dtype, dsize));
		}
		g_free(value);
		pos += record_len;
	}
	return props;
	
}

static void
print_keyvalue(gpointer key, gpointer value, gpointer outfile)
{
		fprintf((FILE*)outfile,"\t%s: %s\n", (gchar *)key, (gchar *)value);
}
void
mdb_dump_props(MdbProperties *props, FILE *outfile, int show_name) {
	if (show_name)
		fprintf(outfile,"name: %s\n", props->name ? props->name : "(none)");
	g_hash_table_foreach(props->hash, print_keyvalue, outfile);
	if (show_name)
		fputc('\n', outfile);
}

/*
 * That function takes a raw KKD/MR2 binary buffer,
 * typically read from LvProp in table MSysbjects
 * and returns a GPtrArray of MdbProps*
 */
GPtrArray*
mdb_kkd_to_props(MdbHandle *mdb, void *buffer, size_t len) {
	guint32 record_len;
	guint16 record_type;
	size_t pos;
	GPtrArray *names = NULL;
	MdbProperties *props;
	GPtrArray *result;

#if MDB_DEBUG
	mdb_buffer_dump(buffer, 0, len);
#endif
	mdb_debug(MDB_DEBUG_PROPS,"starting prop parsing of type %s", buffer);

	if (strcmp("KKD", buffer) && strcmp("MR2", buffer)) {
		fprintf(stderr, "Unrecognized format.\n");
		mdb_buffer_dump(buffer, 0, len);
		return NULL;
	}

	result = g_ptr_array_new();

	pos = 4;
	while (pos < len) {
		record_len = mdb_get_int32(buffer, pos);
		record_type = mdb_get_int16(buffer, pos + 4);
		mdb_debug(MDB_DEBUG_PROPS,"prop chunk type:0x%04x len:%d", record_type, record_len);
		//mdb_buffer_dump(buffer, pos+4, record_len);
		switch (record_type) {
			case 0x80:
				if (names) free_names(names);
				names = mdb_read_props_list(mdb, (char*)buffer+pos+6, record_len - 6);
				break;
			case 0x00:
			case 0x01:
			case 0x02:
				if (!names) {
					fprintf(stderr,"sequence error!\n");
					break;
				}
				props = mdb_read_props(mdb, names, (char*)buffer+pos+6, record_len - 6);
				g_ptr_array_add(result, props);
				//mdb_dump_props(props, stderr, 1);
				break;
			default:
				fprintf(stderr,"Unknown record type %d\n", record_type);
				break;
		}
		pos += record_len;
	}
	if (names) free_names(names);
	return result;
}
