/**
 * \file main-stats.c
 * \brief Pseudo-UI for stats generation (borrows heavily from main-test.c)
 *
 * Copyright (c) 2010-11 Robert Au <myshkin+angband@durak.net>
 *
 * This work is free software; you can redistribute it and/or modify it
 * under the terms of either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation, version 2, or
 *
 * b) the "Angband licence":
 *    This software may be copied and distributed for educational, research,
 *    and not for profit purposes provided that this copyright and statement
 *    are included in all such copies.  Other copyrights may also apply.
 */

#include "angband.h"

#ifdef USE_STATS

#include "buildid.h"
#include "game-world.h"
#include "generate.h"
#include "init.h"
#include "main.h"
#include "mon-make.h"
#include "mon-predicate.h"
#include "mon-util.h"
#include "monster.h"
#include "obj-gear.h"
#include "obj-power.h"
#include "obj-randart.h"
#include "obj-tval.h"
#include "obj-util.h"
#include "object.h"
#include "player.h"
#include "player-birth.h"
#include "player-util.h"
#include "project.h"
#include "stats/db.h"
#include "stats/structs.h"
#include "store.h"
#include <stddef.h>
#include <time.h>

#define OBJ_FEEL_MAX	 11
#define MON_FEEL_MAX 	 10
#define LEVEL_MAX 		101
#define TOP_DICE		 21 /* highest catalogued values for wearables */
#define TOP_SIDES		 11
#define TOP_AC			146
#define TOP_PLUS		 56
#define TOP_POWER		999
#define TOP_MOD 		 25
#define RUNS_PER_CHECKPOINT	10000

/* For ref, e_max is 128, a_max is 136, r_max is ~650,
	ORIGIN_STATS is 14, OF_MAX is ~120 */

/**
 * There are (at last count) 416 kinds, of which about 150-200 are wearable
 * - here wearable includes ammo, which behaves similarly in having
 * variable power between individual items
 */

/**
 * Describe the contents of struct level_data and struct wearables_data so
 * they can be looped over and written to the database.
 */
struct structure_introspection {
	int (*inserter)(const struct structure_introspection*);
				/**< inserts the values in the array into the
					database */
	int (*n0_extractor)(void);
				/**< returns the number of elements in the
					fastest varying dimension; may be NULL
					if that is a compile-time constant
					specified by n0 */
	int (*n1_extractor)(void);
				/**< returns the number of elements in the
					second fastest varying dimension; may
					be NULL if that is a compile-time
					constant specified by n1 */
	uint32_t (*value_extractor)(const uint8_t *, int, int, int);
				/**< extracts the value from the array/pointer
					given the address (start of the array
					or address of pointer), fastest varying
					index in the array, number of elements,
					in the fastest varying dimension,
					and second fastest varying index */
	int (*index_converter)(int);
				/**< currently only used when inserter is
					stats_db_level_data_items(); if not
					NULL, is called with the fastest
					varying index to convert it to the
					index used for insertipn into the
					database */
	const char *name;	/**< name of the table if from struct
					level_data; the table name is
					"wearables_" prepended to this name if
					from struct wearables_data */
	const char *tbl_cmd;	/**< string to pass to stats_db_exec() to
					create the related table in the
					database */
	size_t offset;		/**< offset from the start of the structure for
					the member */
	int n0;			/**< number of elements in the fastest varying
					dimension; ignored if n0_extractor is
					not NULL */
	int n1;			/**< number of elements in the second fastest
					varying dimension; ignored if
					n0_extractor is not NULL */
};

static int stats_write_db_wearables_array(
		const struct structure_introspection* member_desc);
static int stats_write_db_wearables_2d_array(
		const struct structure_introspection* member_desc);
static int stats_write_db_level_data(
		const struct structure_introspection* member_desc);
static int stats_write_db_level_data_items(
		const struct structure_introspection* member_desc);
static int get_artifact_count(void);
static int get_consumables_count(void);
static int get_ego_count(void);
static int get_monster_race_count(void);
static uint32_t extract_arr_long_long(const uint8_t *p, int i0, int n0, int i1);
static uint32_t extract_arr_uint32(const uint8_t *p, int i0, int n0, int i1);
static uint32_t extract_arr_arr_uint32(const uint8_t *p, int i0, int n0,
		int i1);
static uint32_t extract_arr_ptr_uint32(const uint8_t *p, int i0, int n0,
		int i1);
static uint32_t extract_ptr_uint32(const uint8_t *p, int i0, int n0, int i1);
static int convert_to_consumables_index(int i0);

static int randarts = 0;
static int no_selling = 0;
static uint32_t num_runs = 1;
static bool quiet = false;
static int nextkey = 0;
static int running_stats = 0;
static char *ANGBAND_DIR_STATS;

static int *consumables_index;
static int *wearables_index;
static int wearable_count = 0;
static int consumable_count = 0;

struct wearables_data {
	uint32_t count;
	uint32_t dice[TOP_DICE][TOP_SIDES];
	uint32_t ac[TOP_AC];
	uint32_t hit[TOP_PLUS];
	uint32_t dam[TOP_PLUS];
/*	uint32_t power[TOP_POWER]; not enough memory - add it later in bands */
	uint32_t *egos;
	uint32_t flags[OF_MAX];
	uint32_t *modifiers[TOP_MOD];
};

/*
 * The elements here must match up with the array or pointer members of
 * struct wearables_data that will be written to the database.
 */
static struct structure_introspection wearables_introspection[] = {
	{
		stats_write_db_wearables_2d_array,
		NULL,
		NULL,
		extract_arr_arr_uint32,
		NULL,
		"dice",
		"CREATE TABLE wearables_dice(level INT, count INT, k_idx INT, origin INT, dd INT, ds INT, UNIQUE (level, k_idx, origin, dd, ds) ON CONFLICT REPLACE);",
		offsetof(struct wearables_data, dice),
		TOP_SIDES,
		TOP_DICE
	},
	{
		stats_write_db_wearables_array,
		NULL,
		NULL,
		extract_arr_uint32,
		NULL,
		"ac",
		"CREATE TABLE wearables_ac(level INT, count INT, k_idx INT, origin INT, ac INT, UNIQUE (level, k_idx, origin, ac) ON CONFLICT REPLACE);",
		offsetof(struct wearables_data, ac),
		TOP_AC,
		1
	},
	{
		stats_write_db_wearables_array,
		NULL,
		NULL,
		extract_arr_uint32,
		NULL,
		"hit",
		"CREATE TABLE wearables_hit(level INT, count INT, k_idx INT, origin INT, to_h INT, UNIQUE (level, k_idx, origin, to_h) ON CONFLICT REPLACE);",
		offsetof(struct wearables_data, hit),
		TOP_PLUS,
		1
	},
	{
		stats_write_db_wearables_array,
		NULL,
		NULL,
		extract_arr_uint32,
		NULL,
		"dam",
		"CREATE TABLE wearables_dam(level INT, count INT, k_idx INT, origin INT, to_d INT, UNIQUE (level, k_idx, origin, to_d) ON CONFLICT REPLACE);",
		offsetof(struct wearables_data, dam),
		TOP_PLUS,
		1
	},
	{
		stats_write_db_wearables_array,
		get_ego_count,
		NULL,
		extract_ptr_uint32,
		NULL,
		"egos",
		"CREATE TABLE wearables_egos(level INT, count INT, k_idx INT, origin INT, e_idx INT, UNIQUE (level, k_idx, origin, e_idx) ON CONFLICT REPLACE);",
		offsetof(struct wearables_data, egos),
		0,
		1
	},
	{
		stats_write_db_wearables_array,
		NULL,
		NULL,
		extract_arr_uint32,
		NULL,
		"flags",
		"CREATE TABLE wearables_flags(level INT, count INT, k_idx INT, origin INT, of_idx INT, UNIQUE (level, k_idx, origin, of_idx) ON CONFLICT REPLACE);",
		offsetof(struct wearables_data, flags),
		OF_MAX,
		1
	},
	{
		stats_write_db_wearables_2d_array,
		NULL,
		NULL,
		extract_arr_ptr_uint32,
		NULL,
		"mods",
		"CREATE TABLE wearables_mods(level INT, count INT, k_idx INT, origin INT, mod INT, mod_idx INT, UNIQUE (level, k_idx, origin, mod, mod_idx) ON CONFLICT REPLACE);",
		offsetof(struct wearables_data, modifiers),
		OBJ_MOD_MAX + 1,
		TOP_MOD
	},
};

static struct level_data {
	uint32_t *monsters;
	/* uint32_t *vaults;  Add these later - requires passing into generate.c
	uint32_t *pits; */
	uint32_t obj_feelings[OBJ_FEEL_MAX];
	uint32_t mon_feelings[MON_FEEL_MAX];
	long long gold[ORIGIN_STATS];
	uint32_t *artifacts[ORIGIN_STATS];
	uint32_t *consumables[ORIGIN_STATS];
	struct wearables_data *wearables[ORIGIN_STATS];
} level_data[LEVEL_MAX];

/*
 * The elements here must match up with the array or pointer members of
 * struct level_data that will be written to the database.  The wearables
 * member is handled as a special case and is not included here.
 */
static struct structure_introspection level_introspection[] = {
	{
		stats_write_db_level_data,
		get_monster_race_count,
		NULL,
		extract_ptr_uint32,
		NULL,
		"monsters",
		"CREATE TABLE monsters(level INT, count INT, k_idx INT, UNIQUE (level, k_idx) ON CONFLICT REPLACE);",
		offsetof(struct level_data, monsters),
		0,
		1
	},
	{
		stats_write_db_level_data,
		NULL,
		NULL,
		extract_arr_uint32,
		NULL,
		"obj_feelings",
		"CREATE TABLE obj_feelings(level INT, count INT, feeling INT, UNIQUE (level, feeling) ON CONFLICT REPLACE);",
		offsetof(struct level_data, obj_feelings),
		OBJ_FEEL_MAX,
		1
	},
	{
		stats_write_db_level_data,
		NULL,
		NULL,
		extract_arr_uint32,
		NULL,
		"mon_feelings",
		"CREATE TABLE mon_feelings(level INT, count INT, feeling INT, UNIQUE (level, feeling) ON CONFLICT REPLACE);",
		offsetof(struct level_data, mon_feelings),
		MON_FEEL_MAX,
		1
	},
	{
		stats_write_db_level_data,
		NULL,
		NULL,
		extract_arr_long_long,
		NULL,
		"gold",
		"CREATE TABLE gold(level INT, count INT, origin INT, UNIQUE (level, origin) ON CONFLICT REPLACE);",
		offsetof(struct level_data, gold),
		ORIGIN_STATS,
		1
	},
	{
		stats_write_db_level_data_items,
		get_artifact_count,
		NULL,
		extract_arr_ptr_uint32,
		NULL,
		"artifacts",
		"CREATE TABLE artifacts(level INT, count INT, a_idx INT, origin INT, UNIQUE (level, a_idx, origin) ON CONFLICT REPLACE);",
		offsetof(struct level_data, artifacts),
		0,
		ORIGIN_STATS
	},
	{
		stats_write_db_level_data_items,
		get_consumables_count,
		NULL,
		extract_arr_ptr_uint32,
		convert_to_consumables_index,
		"consumables",
		"CREATE TABLE consumables(level INT, count INT, k_idx INT, origin INT, UNIQUE (level, k_idx, origin) ON CONFLICT REPLACE);",
		offsetof(struct level_data, consumables),
		0,
		ORIGIN_STATS
	},
};

static void create_indices(void)
{
	int i;

	consumables_index = mem_zalloc(z_info->k_max * sizeof(int));
	wearables_index = mem_zalloc(z_info->k_max * sizeof(int));

	for (i = 0; i < z_info->k_max; i++) {

		struct object object_type_body = { 0 };
		struct object *obj = &object_type_body;
		struct object_kind *kind = &k_info[i];
		obj->tval = kind->tval;

		if (!kind->name) continue;

		if (tval_has_variable_power(obj))
			wearables_index[i] = ++wearable_count;
		else
			consumables_index[i] = ++consumable_count;
	}
}

static void alloc_memory(void)
{
	int i, j, k, l;

	for (i = 0; i < LEVEL_MAX; i++) {
		level_data[i].monsters = mem_zalloc(z_info->r_max * sizeof(uint32_t));
/*		level_data[i].vaults = mem_zalloc(z_info->v_max * sizeof(uint32_t));
		level_data[i].pits = mem_zalloc(z_info->pit_max * sizeof(uint32_t)); */

		for (j = 0; j < ORIGIN_STATS; j++) {
			level_data[i].artifacts[j] = mem_zalloc(z_info->a_max *
				sizeof(uint32_t));
			level_data[i].consumables[j] = mem_zalloc(
				(consumable_count + 1) * sizeof(uint32_t));
			level_data[i].wearables[j]
				= mem_zalloc((wearable_count + 1) *
							 sizeof(struct wearables_data));

			for (k = 0; k < wearable_count + 1; k++) {
				level_data[i].wearables[j][k].egos
					= mem_zalloc(z_info->e_max * sizeof(uint32_t));
				for (l = 0; l < TOP_MOD; l++)
					level_data[i].wearables[j][k].modifiers[l]
						= mem_zalloc((OBJ_MOD_MAX + 1) * sizeof(uint32_t));
			}
		}
	}
}

static void free_stats_memory(void)
{
	int i, j, k, l;
	for (i = 0; i < LEVEL_MAX; i++) {
		mem_free(level_data[i].monsters);
/*		mem_free(level_data[i].vaults);
 		mem_free(level_data[i].pits); */
		for (j = 0; j < ORIGIN_STATS; j++) {
			mem_free(level_data[i].artifacts[j]);
			mem_free(level_data[i].consumables[j]);
			for (k = 0; k < wearable_count + 1; k++) {
				for (l = 0; l < TOP_MOD; l++) {
					mem_free(level_data[i].wearables[j][k].modifiers[l]);
				}
				mem_free(level_data[i].wearables[j][k].egos);
			}
			mem_free(level_data[i].wearables[j]);
		}
	}
	mem_free(consumables_index);
	mem_free(wearables_index);
	string_free(ANGBAND_DIR_STATS);
}

/* Copied from birth.c:generate_player() */
static void generate_player_for_stats(void)
{
	char buf[80];
	int i;

	OPT(player, birth_randarts) = randarts;
	OPT(player, birth_no_selling) = no_selling;
	OPT(player, birth_stacking) = true;
	OPT(player, auto_more) = true;

	player->wizard = 1; /* Set wizard mode on */

	player->race = races;  /* Human   */
	player->class = classes; /* Warrior */

	/* Needs a body; duplicates logic from the private player_embody(). */
	memcpy(&player->body, &bodies[player->race->body],
		sizeof(player->body));
	my_strcpy(buf, bodies[player->race->body].name, sizeof(buf));
	player->body.name = string_make(buf);
	player->body.slots = mem_zalloc(player->body.count *
		sizeof(*(player->body.slots)));
	for (i = 0; i < player->body.count; ++i) {
		player->body.slots[i].type =
			bodies[player->race->body].slots[i].type;
		my_strcpy(buf, bodies[player->race->body].slots[i].name,
			sizeof(buf));
		player->body.slots[i].name = string_make(buf);
	}

	/* Level 1 */
	player->max_lev = player->lev = 1;

	/* Experience factor */
	player->expfact = player->race->r_exp + player->class->c_exp;

	/* Hitdice */
	player->hitdie = player->race->r_mhp + player->class->c_mhp;

	/* Initial hitpoints -- high just to be safe */
	player->mhp = player->chp = 2000;

	/* Pre-calculate level 1 hitdice */
	player->player_hp[0] = player->hitdie;

	/* Set age/height/weight */
	player->ht = player->ht_birth = 66;
	player->wt = player->wt_birth = 150;
	player->age = 14;

	/* Set social class and (null) history */
	player->history = get_history(player->race->history);
}

static void initialize_character(void)
{
	uint32_t seed;

	if (!quiet) {
		printf(" [I  ]\b\b\b\b\b\b");
		fflush(stdout);
	}

	seed = (time(NULL));
	Rand_quick = false;
	Rand_state_init(seed);

	player_init(player);
	generate_player_for_stats();

	seed_flavor = randint0(0x10000000);
	seed_randart = randint0(0x10000000);

	if (randarts) {
		do_randart(seed_randart, false);
	}

	store_reset();
	flavor_init();
	player->upkeep->playing = true;
	player->upkeep->autosave = false;
	prepare_next_level(player);
}

static void kill_all_monsters(int level)
{
	int i;

	for (i = cave_monster_max(cave) - 1; i >= 1; i--) {
		struct monster *mon = cave_monster(cave, i);

		/* Skip the ones that are already dead. */
		if (!mon->race) continue;

		level_data[level].monsters[mon->race->ridx]++;

		monster_death(mon, player, true);

		if (monster_is_unique(mon))
			mon->race->max_num = 0;
	}
}

static void unkill_uniques(void)
{
	int i;

	if (!quiet) {
		printf(" [U  ]\b\b\b\b\b\b");
		fflush(stdout);
	}

	for (i = 0; i < z_info->r_max; i++) {
		struct monster_race *race = &r_info[i];

		if (rf_has(race->flags, RF_UNIQUE))
			race->max_num = 1;
	}
}

static void reset_artifacts(void)
{
	int i;

	if (!quiet) {
		printf(" [R  ]\b\b\b\b\b\b");
		fflush(stdout);
	}

	for (i = 0; i < z_info->a_max; i++) {
		mark_artifact_created(&a_info[i], false);
	}
}

static void log_all_objects(int level)
{
	int x, y, i;

	for (y = 1; y < cave->height - 1; y++) {
		for (x = 1; x < cave->width - 1; x++) {
			struct loc grid = loc(x, y);
			struct monster *mon = square_monster(cave, grid);
			struct object *obj = square_object(cave, grid);

			while (1) {
				/* uint32_t o_power = 0; */

				if (!obj) {
					/*
					 * Look at the objects from the monster
					 * as well.
					 */
					if (!mon) {
						break;
					}
					obj = mon->held_obj;
					if (!obj) {
						break;
					}
					mon = NULL;
				}

/*				o_power = object_power(obj, false, NULL, true); */

				/*
				 * Skip those we're not tracking (since monsters
				 * aren't killed before collecting item
				 * statistics, mimicked objects are one that
				 * that'll be skipped.
				 */
				if (obj->origin >= ORIGIN_STATS) {
					obj = obj->next;
					continue;
				}

				/* Capture gold amounts */
				if (tval_is_money(obj))
					level_data[level].gold[obj->origin] += obj->pval;

				/* Capture artifact drops */
				if (obj->artifact)
					level_data[level].artifacts[obj->origin][obj->artifact->aidx]++;

				/* Capture kind details */
				if (tval_has_variable_power(obj)) {
					struct wearables_data *w
						= &level_data[level].wearables[obj->origin][wearables_index[obj->kind->kidx]];

					w->count++;
					w->dice[MIN(obj->dd, TOP_DICE - 1)][MIN(obj->ds, TOP_SIDES - 1)]++;
					w->ac[MIN(MAX(obj->ac + obj->to_a, 0), TOP_AC - 1)]++;
					w->hit[MIN(MAX(obj->to_h, 0), TOP_PLUS - 1)]++;
					w->dam[MIN(MAX(obj->to_d, 0), TOP_PLUS - 1)]++;

					/* Capture egos */
					if (obj->ego)
						w->egos[obj->ego->eidx]++;
					/* Capture object flags */
					for (i = of_next(obj->flags, FLAG_START); i != FLAG_END;
							i = of_next(obj->flags, i + 1))
						w->flags[i]++;
					/* Capture object modifiers */
					for (i = 0; i < OBJ_MOD_MAX; i++) {
						int p = obj->modifiers[i];
						w->modifiers[MIN(MAX(p, 0), TOP_MOD - 1)][i]++;
					}
				} else {
					level_data[level].consumables[obj->origin][consumables_index[obj->kind->kidx]]++;
				}

				obj = obj->next;
			}
		}
	}
}

static void descend_dungeon(void)
{
	int level;
	uint16_t obj_f, mon_f;

	clock_t last = 0;

	clock_t wait = CLOCKS_PER_SEC / 5;

	for (level = 1; level < LEVEL_MAX; level++)
	{
		if (!quiet) {
			clock_t now = clock();
			if (now - last > wait) {
				printf(" [%3d]\b\b\b\b\b\b", level);
				fflush(stdout);
				last = now;
			}
		}

		dungeon_change_level(player, level);
		prepare_next_level(player);

		/* Store level feelings */
		obj_f = cave->feeling / 10;
		mon_f = cave->feeling - (10 * obj_f);
		level_data[level].obj_feelings[MIN(obj_f, OBJ_FEEL_MAX - 1)]++;
		level_data[level].mon_feelings[MIN(mon_f, MON_FEEL_MAX - 1)]++;

		log_all_objects(level);
		/* Besides killing, also gathers counts. */
		kill_all_monsters(level);
	}
}

static void prep_output_dir(void)
{
	size_t size = strlen(ANGBAND_DIR_USER) + strlen(PATH_SEP) + 6;
	ANGBAND_DIR_STATS = mem_alloc(size);
	strnfmt(ANGBAND_DIR_STATS, size,
		"%s%sstats", ANGBAND_DIR_USER, PATH_SEP);

	if (dir_create(ANGBAND_DIR_STATS))
		return;
	else
		quit("Couldn't create stats directory!");
}

/**
 * Caller is responsible for prepping and finalizing flags_stmt, which
 * should have two parameters. 
 */
static int stats_dump_oflags(sqlite3_stmt *flags_stmt, int idx, 
		const bitflag flags[OF_SIZE])
{
	int err, flag;

	err = sqlite3_bind_int(flags_stmt, 1, idx);
	if (err) return err;
	for (flag = of_next(flags, FLAG_START); flag != FLAG_END;
		flag = of_next(flags, flag + 1)) {
		err = sqlite3_bind_int(flags_stmt, 2, flag);
		if (err) return err;
		STATS_DB_STEP_RESET(flags_stmt)
	}


	return SQLITE_OK;
}

static int stats_dump_artifacts(void)
{
	int err, idx, i;
	char sql_buf[256];
	sqlite3_stmt *info_stmt, *flags_stmt, *mods_stmt;

	strnfmt(sql_buf, 256, "INSERT INTO artifact_info VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);");
	err = stats_db_stmt_prep(&info_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO artifact_flags_map VALUES (?,?);");
	err = stats_db_stmt_prep(&flags_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO artifact_mods_map VALUES (?,?,?);");
	err = stats_db_stmt_prep(&mods_stmt, sql_buf);
	if (err) return err;

	for (idx = 0; idx < z_info->a_max; idx++) {
		const struct artifact *art = &a_info[idx];

		if (!art->name) continue;

		err = sqlite3_bind_int(info_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(info_stmt, 2, art->name, 
			strlen(art->name), SQLITE_STATIC);
		if (err) return err;
		err = stats_db_bind_ints(info_stmt, 13, 2, 
			art->tval, art->sval, art->weight,
			art->cost, art->alloc_prob, art->alloc_min,
			art->alloc_max, art->ac, art->dd,
			art->ds, art->to_h, art->to_d, art->to_a);
		STATS_DB_STEP_RESET(info_stmt)

		err = stats_dump_oflags(flags_stmt, idx, art->flags);
		if (err) return err;

		for (i = 0; i < OBJ_MOD_MAX; i++)
		{
			err = stats_db_bind_ints(mods_stmt, 3, 0, idx, i, 
									 art->modifiers[i]);
				if (err) return err;
				STATS_DB_STEP_RESET(mods_stmt)
		}
	}

	STATS_DB_FINALIZE(info_stmt)
	STATS_DB_FINALIZE(flags_stmt)
	STATS_DB_FINALIZE(mods_stmt)

	return SQLITE_OK;
}

static int stats_dump_egos(void)
{
	int err, idx, i;
	char sql_buf[256];
	sqlite3_stmt *info_stmt, *flags_stmt, *mods_stmt; //*type_stmt;

	strnfmt(sql_buf, 256, "INSERT INTO ego_info VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?);");
	err = stats_db_stmt_prep(&info_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO ego_flags_map VALUES (?,?);");
	err = stats_db_stmt_prep(&flags_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO ego_mods_map VALUES (?,?,?);");
	err = stats_db_stmt_prep(&mods_stmt, sql_buf);
	if (err) return err;

	for (idx = 0; idx < z_info->e_max; idx++) {
		struct ego_item *ego = &e_info[idx];

		if (!ego->name) continue;

		err = sqlite3_bind_int(info_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(info_stmt, 2, ego->name, 
			strlen(ego->name), SQLITE_STATIC);
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 3, ego->to_h); 
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 4, ego->to_d); 
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 5, ego->to_a); 
		if (err) return err;
		err = stats_db_bind_ints(info_stmt, 8, 5, 
			ego->cost, ego->alloc_min, ego->alloc_max,
			ego->alloc_prob, ego->rating, ego->min_to_h, 
			ego->min_to_d, ego->min_to_a);
		if (err) return err;
		STATS_DB_STEP_RESET(info_stmt)

		err = stats_dump_oflags(flags_stmt, idx, ego->flags);
		if (err) return err;

		for (i = 0; i < OBJ_MOD_MAX; i++) {
			err = stats_db_bind_ints(mods_stmt, 3, 0, idx, i, 
									 ego->min_modifiers[i]);
				if (err) return err;
				STATS_DB_STEP_RESET(mods_stmt)
		}
	}

	STATS_DB_FINALIZE(info_stmt)
	STATS_DB_FINALIZE(flags_stmt)
	STATS_DB_FINALIZE(mods_stmt)

	return SQLITE_OK;
}

static int stats_dump_objects(void)
{
	int err, idx, i, flag;
	char sql_buf[256];
	sqlite3_stmt *info_stmt, *flags_stmt, *mods_stmt;

	strnfmt(sql_buf, 256, "INSERT INTO object_info VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);");
	err = stats_db_stmt_prep(&info_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO object_flags_map VALUES (?,?);");
	err = stats_db_stmt_prep(&flags_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO object_mods_map VALUES (?,?,?);");
	err = stats_db_stmt_prep(&mods_stmt, sql_buf);
	if (err) return err;

	for (idx = 0; idx < z_info->k_max; idx++) {
		struct object_kind *kind = &k_info[idx];

		if (!kind->name) continue;

		err = sqlite3_bind_int(info_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(info_stmt, 2, kind->name,
			strlen(kind->name), SQLITE_STATIC);
		if (err) return err;
		err = stats_db_bind_ints(info_stmt, 13, 2,
			kind->tval, kind->sval, kind->level, kind->weight,
			kind->cost, kind->ac, kind->dd, kind->ds,
			kind->alloc_prob, kind->alloc_min,
			kind->alloc_max, kind->gen_mult_prob, kind->stack_size);
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 17, kind->to_h);
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 18, kind->to_d);
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 19, kind->to_a);
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 20, kind->charge);
		if (err) return err;
		err = stats_db_bind_rv(info_stmt, 21, kind->time);
		if (err) return err;
		STATS_DB_STEP_RESET(info_stmt)

		err = stats_dump_oflags(flags_stmt, idx, kind->flags);
		if (err) return err;

		for (i = 0; i < OBJ_MOD_MAX; i++) {
			err = stats_db_bind_ints(mods_stmt, 2, 0, idx, i);
				if (err) return err;
				err = stats_db_bind_rv(mods_stmt, 3, kind->modifiers[i]);
				if (err) return err;
				STATS_DB_STEP_RESET(mods_stmt)
		}
	}

	STATS_DB_FINALIZE(info_stmt)
	STATS_DB_FINALIZE(flags_stmt)
	STATS_DB_FINALIZE(mods_stmt)

	/* Handle object_base */
	strnfmt(sql_buf, 256, "INSERT INTO object_base_info VALUES (?,?);");
	err = stats_db_stmt_prep(&info_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO object_base_flags_map VALUES (?,?);");
	err = stats_db_stmt_prep(&flags_stmt, sql_buf);
	if (err) return err;

	idx = 0;
	for (idx = 0; idx < TV_MAX; idx++) {
		struct object_base *base = &kb_info[idx];

		if (!base->name) continue;

		err = sqlite3_bind_int(info_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(info_stmt, 2, base->name,
			strlen(base->name), SQLITE_STATIC);
		if (err) return err;
		STATS_DB_STEP_RESET(info_stmt)

		for (flag = of_next(base->flags, FLAG_START);
			flag != FLAG_END;
			flag = of_next(base->flags, flag + 1)) {
			err = stats_db_bind_ints(flags_stmt, 2, 0,
				idx, flag);
			if (err) return err;
			STATS_DB_STEP_RESET(flags_stmt)
		}
	}
	
	STATS_DB_FINALIZE(info_stmt)
	STATS_DB_FINALIZE(flags_stmt)

	return SQLITE_OK;
}

static int stats_dump_monsters(void)
{
	int err, idx, flag;
	char sql_buf[256];
	sqlite3_stmt *info_stmt, *flags_stmt, *spell_flags_stmt;
	struct monster_base *base;

	strnfmt(sql_buf, 256, "INSERT INTO monster_info VALUES (?,?,?,?,?,?,?,?,?,?,?,?);");
	err = stats_db_stmt_prep(&info_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO monster_flags_map VALUES (?,?);");
	err = stats_db_stmt_prep(&flags_stmt, sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO monster_spell_flags_map VALUES (?,?);");
	err = stats_db_stmt_prep(&spell_flags_stmt, sql_buf);
	if (err) return err;

	for (idx = 0; idx < z_info->r_max; idx++) {
		struct monster_race *race = &r_info[idx];

		/* Skip empty entries */
		if (!race->name) continue;

		err = stats_db_bind_ints(info_stmt, 10, 0, idx,
			race->ac, race->sleep, race->speed, race->mexp,
			race->avg_hp, race->freq_innate, race->freq_spell,
			race->level, race->rarity);
		if (err) return err;
		err = sqlite3_bind_text(info_stmt, 11, race->name,
			strlen(race->name), SQLITE_STATIC);
		if (err) return err;
		err = sqlite3_bind_text(info_stmt, 12, race->base->name,
			strlen(race->base->name), SQLITE_STATIC);
		if (err) return err;
		STATS_DB_STEP_RESET(info_stmt)

		for (flag = rf_next(race->flags, FLAG_START);
			flag != FLAG_END;
			flag = rf_next(race->flags, flag + 1))
		{
			err = stats_db_bind_ints(flags_stmt, 2, 0, idx, flag);
			if (err) return err;
			STATS_DB_STEP_RESET(flags_stmt)
		}

		for (flag = rsf_next(race->spell_flags, FLAG_START);
			flag != FLAG_END;
			flag = rsf_next(race->spell_flags, flag + 1))
		{
			err = stats_db_bind_ints(spell_flags_stmt, 2, 0, 
				idx, flag);
			if (err) return err;
			STATS_DB_STEP_RESET(spell_flags_stmt)
		}
	}

	STATS_DB_FINALIZE(info_stmt)
	STATS_DB_FINALIZE(flags_stmt)
	STATS_DB_FINALIZE(spell_flags_stmt)

	/* Handle monster bases */
	strnfmt(sql_buf, 256, "INSERT INTO monster_base_flags_map VALUES (?,?);");
	err = stats_db_stmt_prep(&flags_stmt, sql_buf);
	if (err) return err;

	for (base = rb_info, idx = 0; base; base = base->next, idx++) {
		for (flag = rf_next(base->flags, FLAG_START);
			flag != FLAG_END;
			flag = rf_next(base->flags, flag + 1)) {
			err = sqlite3_bind_text(flags_stmt, 1, base->name,
				strlen(base->name), SQLITE_STATIC);
			if (err) return err;
			err = sqlite3_bind_int(flags_stmt, 2, flag);
			if (err) return err;
			STATS_DB_STEP_RESET(flags_stmt)
		}

	}

	STATS_DB_FINALIZE(flags_stmt)

	return SQLITE_OK;
}

static int stats_dump_lists(void)
{
	int err, idx;
	sqlite3_stmt *sql_stmt;

	/* Note: these lists are sometimes different from the ones the core
	 * game uses, insofar as we put the name of the flag in a
	 * description field. */
	info_entry effects[] =
	{
		{ EF_NONE, false, NULL },
		#define F(x) effect_handler_##x
		#define EFFECT(x, a, b, c, d, e, f)    { EF_##x, a, #x },
		#include "list-effects.h"
		#undef EFFECT
		#undef F
		{ EF_MAX, false, NULL }
	};

	const char *r_info_flags[] =
	{
		#define RF(a, b, c) #a,
		#include "list-mon-race-flags.h"
		#undef RF
		NULL
	};

	/** Really want elements (at least) here - NRM **/

	const char *object_flag_names[] =
	{
		"NONE",
		#define OF(a, b) #a,
		#include "list-object-flags.h"
		#undef OF
	};

	const char *object_mods[] =
	{
		#define STAT(a) #a,
        #include "list-stats.h"
        #undef STAT
        #define OBJ_MOD(a) #a,
        #include "list-object-modifiers.h"
        #undef OBJ_MOD
		NULL
	};

	const char *origin_names[] = {
		#define ORIGIN(a, b, c) #a,
		#include "list-origins.h"
		#undef ORIGIN
		NULL
	};

	err = stats_db_stmt_prep(&sql_stmt, 
		"INSERT INTO effects_list VALUES(?,?,?,?);");
	if (err) return err;

	for (idx = 1; idx < EF_MAX; idx++) {
		if (! effects[idx].desc) continue;

		err = stats_db_bind_ints(sql_stmt, 2, 0, idx, 
			effects[idx].aim);
		if (err) return err;
		err = sqlite3_bind_text(sql_stmt, 2, effects[idx].desc,
			strlen(effects[idx].desc), SQLITE_STATIC);
		if (err) return err;
		STATS_DB_STEP_RESET(sql_stmt)
	}

	STATS_DB_FINALIZE(sql_stmt)

	err = stats_db_stmt_prep(&sql_stmt, 
		"INSERT INTO monster_flags_list VALUES(?,?);");
	if (err) return err;

	for (idx = 0; r_info_flags[idx] != NULL; idx++) {
		err = sqlite3_bind_int(sql_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(sql_stmt, 2, r_info_flags[idx],
			strlen(r_info_flags[idx]), SQLITE_STATIC);
		if (err) return err;
		STATS_DB_STEP_RESET(sql_stmt)
	}

	STATS_DB_FINALIZE(sql_stmt)

	err = stats_db_stmt_prep(&sql_stmt, 
		"INSERT INTO object_flags_list VALUES(?,?);");
	if (err) return err;

	for (idx = 0; idx < OF_MAX; idx++) {
		err = sqlite3_bind_int(sql_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(sql_stmt, 2, object_flag_names[idx],
			strlen(object_flag_names[idx]), SQLITE_STATIC);
		if (err) return err;
		STATS_DB_STEP_RESET(sql_stmt)
	}

	STATS_DB_FINALIZE(sql_stmt)

	err = stats_db_stmt_prep(&sql_stmt, 
		"INSERT INTO object_mods_list VALUES(?,?);");
	if (err) return err;

	for (idx = 0; object_mods[idx] != NULL; idx++) {
		err = sqlite3_bind_int(sql_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(sql_stmt, 2, object_mods[idx],
			strlen(object_mods[idx]), SQLITE_STATIC);
		if (err) return err;
		STATS_DB_STEP_RESET(sql_stmt)
	}

	STATS_DB_FINALIZE(sql_stmt)

	err = stats_db_stmt_prep(&sql_stmt,
		"INSERT INTO origin_flags_list VALUES(?,?);");
	if (err) return err;

	for (idx = 0; idx < ORIGIN_STATS && origin_names[idx]; idx++) {
		err = sqlite3_bind_int(sql_stmt, 1, idx);
		if (err) return err;
		err = sqlite3_bind_text(sql_stmt, 2, origin_names[idx],
			strlen(origin_names[idx]), SQLITE_STATIC);
		if (err) return err;
		STATS_DB_STEP_RESET(sql_stmt)
	}

	STATS_DB_FINALIZE(sql_stmt)

	return SQLITE_OK;
}

static int stats_dump_info(void)
{
	int err;
	char sql_buf[256];

	/* Wrap entire write into a transaction */
	err = stats_db_exec("BEGIN TRANSACTION;");
	if (err) return err;

	/* Metadata */
	strnfmt(sql_buf, 256, "INSERT INTO metadata VALUES('version','%s');",
		buildver);
	err = stats_db_exec(sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO metadata VALUES('randarts',%d);",
		randarts);
	err = stats_db_exec(sql_buf);
	if (err) return err;

	strnfmt(sql_buf, 256, "INSERT INTO metadata VALUES('no_selling',%d);",
		no_selling);
	err = stats_db_exec(sql_buf);
	if (err) return err;

	err = stats_dump_artifacts();
	if (err) return err;

	err = stats_dump_egos();
	if (err) return err;

	err = stats_dump_monsters();
	if (err) return err;

	err = stats_dump_objects();
	if (err) return err;

	err = stats_dump_lists();
	if (err) return err;

	/* Commit transaction */
	return stats_db_exec("COMMIT;");
}

/**
 * Open the database connection and create the database tables.
 * All count tables will contain a level column (INTEGER) and a
 * count column (INTEGER). Some tables will include other INTEGER columns
 * for object origin, feeling, attributes, indices, or object flags.
 * Note that random_value types are stored as either A+BdC+Md or A.
 * Tables:
 *     metadata -- key-value pairs describing the stats run
 *     artifact_info -- dump of artifact.txt
 *     artifact_flags_map -- map between artifacts and object flags
 *     artifact_mods_map -- map between artifacts and modifiers
 *     ego_info -- dump of ego_item.txt
 *     ego_flags_map -- map between egos and object flags
 *     ego_mods_map -- map between egos and modifiers, with minima
 *     ego_type_map -- map between egos and tvals/svals
 *     monster_base_flags_map -- map between monster bases and monster flags
 *     monster_info -- dump of monsters.txt
 *     monster_flags_map -- map between monsters and monster flags
 *     monster_spell_flags_map -- map between monsters and monster spell flags
 *     object_base_info -- dump of object_base.txt
 *     object_base_flags_map -- map between object templates and object flags
 *     object_info -- dump of objects.txt
 *     object_flags_map -- map between objects and object flags
 *     object_mods_map -- map between objects and modifiers
 *     effects_list -- dump of list-effects.h
 *     monster_flags_list -- dump of list-mon-race-flags.h
 *     object_flags_list -- dump of list-object-flags.h
 *     object_mods_list -- dump of list-object-modifiers.h
 *     origin_flags_list -- dump of origin enum
 * Count tables:
 *     monsters
 *     obj_feelings
 *     mon_feelings
 *     gold
 *     artifacts
 *     consumables
 *     wearables_count
 *     wearables_dice
 *     wearables_ac
 *     wearables_hit
 *     wearables_dam
 *     wearables_egos
 *     wearables_flags
 *     wearables_mods
 */
static bool stats_prep_db(void)
{
	bool status;
	int err, i;

	/* Open the database connection */
	status = stats_db_open();
	if (!status) return status;

	/* Create some tables */
	err = stats_db_exec("CREATE TABLE metadata(field TEXT UNIQUE NOT NULL, value TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE artifact_info(idx INT PRIMARY KEY, name TEXT, tval INT, sval INT, weight INT, cost INT, alloc_prob INT, alloc_min INT, alloc_max INT, ac INT, dd INT, ds INT, to_h INT, to_d INT, to_a INT, effect INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE artifact_flags_map(a_idx INT, o_flag INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE artifact_mods_map(a_idx INT, modifier_index INT, modifier INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE ego_info(idx INT PRIMARY KEY, name TEXT, to_h TEXT, to_d TEXT, to_a TEXT, cost INT, level INT, rarity INT, rating INT, min_to_h INT, min_to_d INT, min_to_a INT, xtra INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE ego_flags_map(e_idx INT, o_flag INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE ego_mods_map(e_idx INT, modifier_index INT, min_modifier INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE ego_type_map(e_idx INT, tval INT, min_sval INT, max_sval INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE monster_base_flags_map(rb_idx INT, r_flag INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE monster_info(idx INT PRIMARY KEY, ac INT, sleep INT, speed INT, mexp INT, hp INT, freq_innate INT, freq_spell INT, level INT, rarity INT, name TEXT, base TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE monster_flags_map(r_idx INT, r_flag INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE monster_spell_flags_map(r_idx INT, rs_flag INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE object_base_info(idx INT PRIMARY KEY, name TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE object_base_flags_map(kb_idx INT, o_flag INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE object_info(idx INT PRIMARY KEY, name TEXT, tval INT, sval INT, level INT, weight INT, cost INT, ac INT, dd INT, ds INT, alloc_prob INT, alloc_min INT, alloc_max INT, effect INT, gen_mult_prob INT, stack_size INT, to_h TEXT, to_d TEXT, to_a TEXT, charge TEXT, recharge_time TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE object_flags_map(k_idx INT, o_flag INT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE object_mods_map(k_idx INT, modifier_index INT, modifier TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE effects_list(idx INT PRIMARY KEY, aim INT, rating INT, name TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE monster_flags_list(idx INT PRIMARY KEY, name TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE monster_spell_flags_list(idx INT PRIMARY KEY, cap INT, div INT, name TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE object_flags_list(idx INT PRIMARY KEY, name TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE object_mods_list(idx INT PRIMARY KEY, name TEXT);");
	if (err) return false;

	err = stats_db_exec("CREATE TABLE origin_flags_list(idx INT PRIMARY KEY, name TEXT);");
	if (err) return false;

	for (i = 0; i < (int)N_ELEMENTS(level_introspection); ++i) {
		err = stats_db_exec(level_introspection[i].tbl_cmd);
		if (err) return false;
	}

	err = stats_db_exec("CREATE TABLE wearables_count(level INT, count INT, k_idx INT, origin INT, UNIQUE (level, k_idx, origin) ON CONFLICT REPLACE);");
	if (err) return false;

	for (i = 0; i < (int)N_ELEMENTS(wearables_introspection); ++i) {
		err = stats_db_exec(wearables_introspection[i].tbl_cmd);
		if (err) return false;
	}

	err = stats_dump_info();
	if (err) return false;

	return true;
}

/**
 * Given a pointer to a dynamically allocated array and a value, look up
 * the index with that value; e.g. given wearables_index[k_idx], return k_idx.
 * If not found, return -1 * value.
 */
static int stats_lookup_index(const int *index, int max_idx, int value)
{
	int idx;

	for (idx = 0; idx < max_idx; idx ++) {
		if (index[idx] == value) return idx;
	}

	return -1 * value;
}

static int get_artifact_count(void)
{
	return z_info->a_max;
}

static int get_consumables_count(void)
{
	return consumable_count + 1;
}

static int get_ego_count(void)
{
	return z_info->e_max;
}

static int get_monster_race_count(void)
{
	return z_info->r_max;
}

/**
 * Intended for use as struct structure_introspection's value_extractor
 * member when the structure member it is describing is declared as
 * "long long member_name[n0]".  p is the address of the start of the array
 * (i.e. address of the start of the structure + offsetof(structure type,
 * member_name)).
 */
static uint32_t extract_arr_long_long(const uint8_t *p, int i0, int n0, int i1)
{
	return *((long long*)p + i0);
}

/**
 * Intended for use as struct structure_introspection's value_extractor
 * member when the structure member it is describing is declared as
 * "uint32_t member_name[n0]".  p is the address of the start of the array
 * (i.e. address of the start of the structure + offsetof(structure type,
 * member_name)).
 */
static uint32_t extract_arr_uint32(const uint8_t *p, int i0, int n0, int i1)
{
	return *((uint32_t*)p + i0);
}

/**
 * Intended for use as struct structure_introspection's value_extractor
 * member when the structure member it is describing is declared as
 * "uint32_t member_name[n1][n0]".  p is the address of the start of the array
 * (i.e. address of the start of the structure + offsetof(structure type,
 * member_name)).
 */
static uint32_t extract_arr_arr_uint32(const uint8_t *p, int i0, int n0, int i1)
{
	return *((uint32_t*)p + i1 * (size_t)n0 + i0);
}

/**
 * Intended for use as struct structure_introspection's value_extractor
 * member when the structure member it is describing is declared as
 * "uint32_t *member_name[n1]".  p is the address of the start of the array
 * (i.e. address of the start of the structure + offsetof(structure type,
 * member_name)).
 */
static uint32_t extract_arr_ptr_uint32(const uint8_t *p, int i0, int n0, int i1)
{
	return *(*((uint32_t**)p + i1) + i0);
}

/**
 * Intended for use as struct structure_introspection's value_extractor
 * member when the structure member it is describing is declared as
 * "uint32_t *member_name".  p is the address of the pointer (i.e. address of
 * the start of the structure + offsetof(structure type, member_name)).
 */
static uint32_t extract_ptr_uint32(const uint8_t *p, int i0, int n0, int i1)
{
	return *(*((uint32_t**)p) + i0);
}

static int convert_to_consumables_index(int i0)
{
	return stats_lookup_index(consumables_index, z_info->k_max, i0);
}

static int stats_write_db_level_data(
		const struct structure_introspection* member_desc)
{
	char sql_buf[256];
	sqlite3_stmt *sql_stmt;
	int n = (member_desc->n0_extractor)
		? (*member_desc->n0_extractor)() : member_desc->n0;
	int err, level, i;

	strnfmt(sql_buf, 256, "INSERT INTO %s VALUES(?,?,?);",
		member_desc->name);
	err = stats_db_stmt_prep(&sql_stmt, sql_buf);
	if (err) return err;

	for (level = 1; level < LEVEL_MAX; level++)
		for (i = 0; i < n; i++) {
			uint32_t count = (*member_desc->value_extractor)(
				(const uint8_t*)&level_data[level]
				+ member_desc->offset, i, n, 0);

			if (!count) continue;

			err = stats_db_bind_ints(sql_stmt, 3, 0,
				level, count, i);
			if (err) return err;

			STATS_DB_STEP_RESET(sql_stmt)
		}

	return sqlite3_finalize(sql_stmt);
}

static int stats_write_db_level_data_items(
		const struct structure_introspection* member_desc)
{
	char sql_buf[256];
	sqlite3_stmt *sql_stmt;
	int nj = (member_desc->n1_extractor) ?
		(*member_desc->n1_extractor)() : member_desc->n1;
	int ni = (member_desc->n0_extractor) ?
		(*member_desc->n0_extractor)() : member_desc->n0;
	int err, level, j, i;

	strnfmt(sql_buf, 256, "INSERT INTO %s VALUES(?,?,?,?);",
		member_desc->name);
	err = stats_db_stmt_prep(&sql_stmt, sql_buf);
	if (err) return err;

	for (level = 1; level < LEVEL_MAX; level++)
		for (j = 0; j < nj; j++)
			for (i = 0; i < ni; i++) {
				uint32_t count =
					(*member_desc->value_extractor)(
					(const uint8_t*)&level_data[level]
					+ member_desc->offset, i, ni, j);

				if (!count) continue;

				err = stats_db_bind_ints(sql_stmt, 4, 0, level,
					count, (member_desc->index_converter)
					? (*member_desc->index_converter)(i) : i,
					j);
				if (err) return err;

				STATS_DB_STEP_RESET(sql_stmt)
			}

	return sqlite3_finalize(sql_stmt);
}

static int stats_write_db_wearables_count(void)
{
	sqlite3_stmt *sql_stmt;
	int err, level, origin, k_idx, idx;

	err = stats_db_stmt_prep(&sql_stmt, 
		"INSERT INTO wearables_count VALUES(?,?,?,?);");
	if (err) return err;

	for (level = 1; level < LEVEL_MAX; level++)
		for (origin = 0; origin < ORIGIN_STATS; origin++)
			for (idx = 0; idx < wearable_count + 1; idx++) {
				uint32_t count = level_data[level].wearables[origin][idx].count;
				/* Skip if object did not appear */
				if (!count) continue;

				k_idx = stats_lookup_index(wearables_index, 
					z_info->k_max, idx);

				/* Skip if pile */
				if (! k_idx) continue;

				err = stats_db_bind_ints(sql_stmt, 4, 0,
					level, count, k_idx, origin);
				if (err) return err;

				STATS_DB_STEP_RESET(sql_stmt)
			}

	return sqlite3_finalize(sql_stmt);
}

static int stats_write_db_wearables_array(
		const struct structure_introspection* member_desc)
{
	char sql_buf[256];
	sqlite3_stmt *sql_stmt;
	int n = (member_desc->n0_extractor)
		? (*member_desc->n0_extractor)() : member_desc->n0;
	int err, level, origin, idx, k_idx, i;

	strnfmt(sql_buf, 256, "INSERT INTO wearables_%s VALUES(?,?,?,?,?);",
		member_desc->name);
	err = stats_db_stmt_prep(&sql_stmt, sql_buf);
	if (err) return err;

	for (level = 1; level < LEVEL_MAX; level++)
		for (origin = 0; origin < ORIGIN_STATS; origin++)
			for (idx = 0; idx < wearable_count + 1; idx++) {
				k_idx = stats_lookup_index(wearables_index, 
					z_info->k_max, idx);

				/* Skip if pile */
				if (! k_idx) continue;

				for (i = 0; i < n; i++) {
					uint32_t count =
						(*member_desc->value_extractor)(
						(const uint8_t*)&level_data[level].wearables[origin][idx]
						+ member_desc->offset, i, n, 0);

					if (!count) continue;

					err = stats_db_bind_ints(sql_stmt, 5, 0,
						level, count, k_idx, origin, i);
					if (err) return err;

					STATS_DB_STEP_RESET(sql_stmt)
				}
			}

	return sqlite3_finalize(sql_stmt);
}

static int stats_write_db_wearables_2d_array(
		const struct structure_introspection* member_desc)
{
	char sql_buf[256];
	sqlite3_stmt *sql_stmt;
	int nj = (member_desc->n1_extractor) ?
		(*member_desc->n1_extractor)() : member_desc->n1;
	int ni = (member_desc->n0_extractor) ?
		(*member_desc->n0_extractor)() : member_desc->n0;
	int err, level, origin, idx, k_idx, i, j;

	strnfmt(sql_buf, 256, "INSERT INTO wearables_%s VALUES(?,?,?,?,?,?);",
		member_desc->name);
	err = stats_db_stmt_prep(&sql_stmt, sql_buf);
	if (err) return err;

	for (level = 1; level < LEVEL_MAX; level++)
		for (origin = 0; origin < ORIGIN_STATS; origin++)
			for (idx = 0; idx < wearable_count + 1; idx++) {
				k_idx = stats_lookup_index(wearables_index, 
					z_info->k_max, idx);

				/* Skip if pile */
				if (! k_idx) continue;

				for (j = 0; j < nj; j++)
					for (i = 0; i < ni; i++) {
						uint32_t count;

						if (i == 0 && j == 0) continue;

						count = (*member_desc->value_extractor)(
							(const uint8_t*)&level_data[level].wearables[origin][idx]
							+ member_desc->offset,
							i, ni, j);

						if (!count) continue;

						err = stats_db_bind_ints(sql_stmt, 6, 0,
							level, count, k_idx, origin, 
							j, i);
						if (err) return err;

						STATS_DB_STEP_RESET(sql_stmt)
					}
			}

	return sqlite3_finalize(sql_stmt);
}

static int stats_write_db(uint32_t run)
{
	char sql_buf[256];
	int err, i;

	/* Wrap entire write into a transaction */
	err = stats_db_exec("BEGIN TRANSACTION;");
	if (err) return err;

	strnfmt(sql_buf, 256, 
		"INSERT OR REPLACE INTO metadata VALUES('runs', %d);", run);
	err = stats_db_exec(sql_buf);
	if (err) return err;

	for (i = 0; i < (int)N_ELEMENTS(level_introspection); ++i) {
		err = (*level_introspection[i].inserter)(
			&level_introspection[i]);
		if (err) return err;
	}

	err = stats_write_db_wearables_count();
	if (err) return err;

	for (i = 0; i < (int)N_ELEMENTS(wearables_introspection); ++i) {
		err = (*wearables_introspection[i].inserter)(
			&wearables_introspection[i]);
		if (err) return err;
	}

	/* Commit transaction */
	err = stats_db_exec("COMMIT;");
	if (err) return err;

	return SQLITE_OK;
}

/**
 * Call with the number of runs that have been completed.
 */

#define STATS_PROGRESS_BAR_LEN 30

static void progress_bar(uint32_t run, time_t start) {
	uint32_t i;
	uint32_t n = (run * STATS_PROGRESS_BAR_LEN) / num_runs;
	uint32_t p10 = ((long long)run * 1000) / num_runs;

	time_t delta = time(NULL) - start;
	uint32_t togo = num_runs - run;
	uint32_t expect = delta ?
		((long long)delta * (long long)togo) / run : 0;

	int h = expect / 3600;
	int m = (expect % 3600) / 60;
	int s = expect % 60;

	printf("\r|");
	for (i = 0; i < n; i++) printf("*");
	for (i = 0; i < STATS_PROGRESS_BAR_LEN - n; i++) printf(" ");
	printf("| %d/%d (%5.1f%%) %3d:%02d:%02d ", run, num_runs, p10/10.0, h, m,
		   s);
	fflush(stdout);
}

/**
 * Clean up memory after each run. Should only affect character and
 * dungeon structs allocated during normal initialization, not persistent 
 * data like *_info.
 */

static void stats_cleanup_angband_run(void)
{
	if (character_dungeon) {
		wipe_mon_list(cave, player);
		if (player->cave) {
			cave_free(player->cave);
			player->cave = NULL;
		}
		if (cave) {
			cave_free(cave);
			cave = NULL;
		}
		character_dungeon = false;
	}
	mem_free(player->history);
	player->history = NULL;
}

static errr run_stats(void)
{
	uint32_t run;
	struct artifact *a_info_save = NULL;
	struct artifact_upkeep *aup_info_save = NULL;
	unsigned int i;
	int err;
	bool status; 

	time_t start;

	prep_output_dir();
	create_indices();
	alloc_memory();
	if (randarts) {
		a_info_save = mem_zalloc(z_info->a_max * sizeof(struct artifact));
		aup_info_save = mem_zalloc(z_info->a_max
			* sizeof(*aup_info_save));
		for (i = 0; i < z_info->a_max; i++) {
			if (!a_info[i].name) continue;

			memcpy(&a_info_save[i], &a_info[i],
				sizeof(struct artifact));
			memcpy(&aup_info_save[i], &aup_info[i],
				sizeof(struct artifact_upkeep));
		}
	}

	if (!quiet) printf("Creating the database and dumping info...\n");
	status = stats_prep_db();
	if (!status) quit("Couldn't prepare database!");

	if (!quiet) {
		printf("Beginning %d runs...\n", num_runs);
		fflush(stdout);
	}

	start = time(NULL);
	for (run = 1; run <= num_runs; run++) {
		if (!quiet) progress_bar(run - 1, start);

		if (randarts) {
			for (i = 0; i < z_info->a_max; i++) {
				memcpy(&a_info[i], &a_info_save[i],
					sizeof(struct artifact));
				memcpy(&aup_info[i], &aup_info_save[i],
					sizeof(struct artifact_upkeep));
			}
		}

		initialize_character();
		unkill_uniques();
		reset_artifacts();
		descend_dungeon();
		stats_cleanup_angband_run();

		/* Checkpoint every so many runs */
		if (run % RUNS_PER_CHECKPOINT == 0) {
			err = stats_write_db(run);
			if (err) {
				stats_db_close();
				quit_fmt("Problems writing to database!  sqlite3 errno %d.",
						 err);
			}
		}

		if (quiet && run % 1000 == 0) {
			printf("Finished %d runs.\n", run);
			fflush(stdout);
		}
	}

	if (!quiet) {
		progress_bar(num_runs, start);
		printf("\nSaving the data...\n");
		fflush(stdout);
	}

	err = stats_write_db(run);
	stats_db_close();
	if (err) quit_fmt("Problems writing to database!  sqlite3 errno %d.", err);

	if (randarts) {
		mem_free(aup_info_save);
		mem_free(a_info_save);
	}
	free_stats_memory();
	cleanup_angband();
	if (!quiet) printf("Done!\n");
	quit(NULL);
	exit(0);
}

typedef struct term_data term_data;
struct term_data {
	term t;
};

static term_data td;
typedef struct {
	int key;
	errr (*func)(int v);
} term_xtra_func;

static void term_init_stats(term *t) {
	return;
}

static void term_nuke_stats(term *t) {
	return;
}

static errr term_xtra_clear(int v) {
	return 0;
}

static errr term_xtra_noise(int v) {
	return 0;
}

static errr term_xtra_fresh(int v) {
	return 0;
}

static errr term_xtra_shape(int v) {
	return 0;
}

static errr term_xtra_alive(int v) {
	return 0;
}

static errr term_xtra_event(int v) {
	if (nextkey) {
		Term_keypress(nextkey, 0);
		nextkey = 0;
	}
	if (running_stats) {
		return 0;
	}
	running_stats = 1;
	return run_stats();
}

static errr term_xtra_flush(int v) {
	return 0;
}

static errr term_xtra_delay(int v) {
	return 0;
}

static errr term_xtra_react(int v) {
	return 0;
}

static term_xtra_func xtras[] = {
	{ TERM_XTRA_CLEAR, term_xtra_clear },
	{ TERM_XTRA_NOISE, term_xtra_noise },
	{ TERM_XTRA_FRESH, term_xtra_fresh },
	{ TERM_XTRA_SHAPE, term_xtra_shape },
	{ TERM_XTRA_ALIVE, term_xtra_alive },
	{ TERM_XTRA_EVENT, term_xtra_event },
	{ TERM_XTRA_FLUSH, term_xtra_flush },
	{ TERM_XTRA_DELAY, term_xtra_delay },
	{ TERM_XTRA_REACT, term_xtra_react },
	{ 0, NULL },
};

static errr term_xtra_stats(int n, int v) {
	int i;
	for (i = 0; xtras[i].func; i++) {
		if (xtras[i].key == n) {
			return xtras[i].func(v);
		}
	}
	return 0;
}

static errr term_curs_stats(int x, int y) {
	return 0;
}

static errr term_wipe_stats(int x, int y, int n) {
	return 0;
}

static errr term_text_stats(int x, int y, int n, int a, const wchar_t *s) {
	return 0;
}

static void term_data_link(int i) {
	term *t = &td.t;

	term_init(t, 80, 24, 256);

	/* Ignore some actions for efficiency and safety */
	t->never_bored = true;
	t->never_frosh = true;

	t->init_hook = term_init_stats;
	t->nuke_hook = term_nuke_stats;

	t->xtra_hook = term_xtra_stats;
	t->curs_hook = term_curs_stats;
	t->wipe_hook = term_wipe_stats;
	t->text_hook = term_text_stats;

	t->data = &td;

	Term_activate(t);

	angband_term[i] = t;
}

const char help_stats[] = "Stats mode, subopts -q(uiet) -r(andarts) -n(# of runs) -s(no selling)";

/**
 * Usage:
 *
 * angband -mstats -- [-q] [-r] [-nNNNN] [-s]
 *
 *   -q      Quiet mode (turn off progress messages)
 *   -r      Turn on randarts
 *   -nNNNN  Make NNNN runs through the dungeon (default: 1)
 *   -s      Turn on no-selling
 */

errr init_stats(int argc, char *argv[]) {
	int i;

	/* Skip over argv[0] */
	for (i = 1; i < argc; i++) {
		if (streq(argv[i], "-r")) {
			randarts = 1;
			continue;
		}
		if (streq(argv[i], "-q")) {
			quiet = true;
			continue;
		}
		if (prefix(argv[i], "-n")) {
			num_runs = atoi(&argv[i][2]);
			continue;
		}
		if (prefix(argv[i], "-s")) {
			no_selling = 1;
			continue;
		}
		printf("init-stats: bad argument '%s'\n", argv[i]);
	}

	term_data_link(0);
	return 0;
}

#endif /* USE_STATS */
