/* テスト */
/* 
 * Debug Status: Nastily broken.
 * - Fixed __T on this page.
 */
/*
 * File: savefile.c
 * Purpose: Savefile loading and saving main routines
 *
 * Copyright (c) 2009 Andi Sidwell <andi@takkaria.org>
 *
 * 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 <errno.h>
extern "C"
{
#include "angband.h"
#include "savefile.h"
}
extern void prt(const _TCHAR *str, int row, int col);

/** Magic bits at beginning of savefile */
static const byte savefile_magic[4] = { 83, 97, 118, 101 };
static const _TCHAR savefile_name[5] = SAVEFILE_NAME;
extern void message_flush(void);
extern size_t strnfmt(_TCHAR *buf, size_t max, const _TCHAR *fmt, ...);

#define HEADSIZE (sizeof(savefile_magic) + sizeof(savefile_name))
#define HEADNAMESIZE (16 * sizeof(_TCHAR))

/**
 * Big list of all savefile block types.
 */
static const struct
{
	_TCHAR name[16];
	int (*loader)(u32b version);
	void (*saver)(void);
	u32b cur_ver;
	u32b oldest_ver;
} savefile_blocks[] =
{
	{ __T("rng"), rd_randomizer, wr_randomizer, 1, 1 },
	{ __T("options"), rd_options, wr_options, 1, 1 },
	{ __T("messages"), rd_messages, wr_messages, 1, 1 },
	{ __T("monster memory"), rd_monster_memory, wr_monster_memory, 1, 1 },
	{ __T("object memory"), rd_object_memory, wr_object_memory, 1, 1 },
	{ __T("quests"), rd_quests, wr_quests, 1, 1 },
	{ __T("artifacts"), rd_artifacts, wr_artifacts, 1, 1 },
	{ __T("player"), rd_player, wr_player, 2, 1 },
	{ __T("squelch"), rd_squelch, wr_squelch, 1, 1 },
	{ __T("misc"), rd_misc, wr_misc, 1, 1 },
	{ __T("player hp"), rd_player_hp, wr_player_hp, 1, 1 },
	{ __T("player spells"), rd_player_spells, wr_player_spells, 1, 1 },
	{ __T("randarts"), rd_randarts, wr_randarts, 1, 1 },
	{ __T("inventory"), rd_inventory, wr_inventory, 1, 1 },
	{ __T("stores"), rd_stores, wr_stores, 1, 1 },
	{ __T("dungeon"), rd_dungeon, wr_dungeon, 1, 1 },
	{ __T("objects"), rd_objects, wr_objects, 1, 1 },
	{ __T("monsters"), rd_monsters, wr_monsters, 1, 1 },
	{ __T("ghost"), rd_ghost, wr_ghost, 1, 1 },
	{ __T("history"), rd_history, wr_history, 1, 1 },
};

/* Buffer bits */
static byte *buffer;
static u32b buffer_size;
static u32b buffer_pos;
static u32b buffer_check;

#define BUFFER_INITIAL_SIZE		1024
#define BUFFER_BLOCK_INCREMENT	1024

#define SAVEFILE_HEAD_SIZE	(HEADNAMESIZE + 12) /* Was 28 */

/** Utility **/

/*
 * Hack -- Show information on the screen, one line at a time.
 *
 * Avoid the top two lines, to avoid interference with "msg_print()".
 */
void note(const _TCHAR *msg)
{
	static int y = 2;
	
	/* Draw the message */
	prt(msg, y, 0);
	
	/* Advance one line (wrap if needed) */
	if (++y >= 24) y = 2;
	
	/* Flush it */
	Term_fresh();
}

/** Base put/get **/

static void sf_put(byte v)
{
	assert(buffer != NULL);
	assert(buffer_size > 0);

	if (buffer_size == buffer_pos)
	{
		buffer_size += BUFFER_BLOCK_INCREMENT;
		buffer = (byte *)mem_realloc(buffer, buffer_size);
	}
	
	assert(buffer_pos < buffer_size);

	buffer[buffer_pos++] = v;
	buffer_check += v;
}

static byte sf_get(void)
{
	assert(buffer != NULL);
	assert(buffer_size > 0);
	assert(buffer_pos < buffer_size);

	buffer_check += buffer[buffer_pos];

	return buffer[buffer_pos++];
}

/* accessor */

void wr_byte(byte v)
{
	sf_put(v);
}

void wr_wchar(wchar_t v) // TODO Get this to work.
{
	byte lefthalf = (byte) v & 0xFF;
	byte righthalf = (byte) (v >> 8) & 0xFF;
	sf_put(lefthalf);
	sf_put(righthalf);
}

void wr_u16b(u16b v)
{
	sf_put((byte)(v & 0xFF));
	sf_put((byte)((v >> 8) & 0xFF));
}

void wr_s16b(s16b v)
{
	wr_u16b((u16b)v);
}

void wr_u32b(u32b v)
{
	sf_put((byte)(v & 0xFF));
	sf_put((byte)((v >> 8) & 0xFF));
	sf_put((byte)((v >> 16) & 0xFF));
	sf_put((byte)((v >> 24) & 0xFF));
}

void wr_s32b(s32b v)
{
	wr_u32b((u32b)v);
}

void wr_string(const _TCHAR *str)
{
	while (*str)
	{
		wr_wchar(*str);
		str++;
	}
	wr_wchar(*str);
}


void rd_byte(byte *ip)
{
	*ip = sf_get();
}

void rd_u16b(u16b *ip)
{
	(*ip) = sf_get();
	(*ip) |= ((u16b)(sf_get()) << 8);
}

void rd_s16b(s16b *ip)
{
	rd_u16b((u16b*)ip);
}

void rd_u32b(u32b *ip)
{
	(*ip) = sf_get();
	(*ip) |= ((u32b)(sf_get()) << 8);
	(*ip) |= ((u32b)(sf_get()) << 16);
	(*ip) |= ((u32b)(sf_get()) << 24);
}

void rd_s32b(s32b *ip)
{
	rd_u32b((u32b*)ip);
}

void rd_wchar(_TCHAR *ip)
{
	(*ip) = sf_get();
	(*ip) |= ((wchar_t)(sf_get()) << 8);
}

void rd_string(_TCHAR *str, int max)
{
	wchar_t tmpchar;
	int i = 0;

	do
	{
		rd_wchar(&tmpchar);

		if (i < max) str[i] = tmpchar;
		if (!tmpchar) break;
	} while (++i);

	str[max - 1] = 0;
}

void strip_bytes(int n)
{
	byte tmp8u;
	while (n--) rd_byte(&tmp8u);
}

/*** ****/

static bool try_save(ang_file *file)
{
	byte savefile_head[SAVEFILE_HEAD_SIZE];
	size_t i, pos;
	errno_t ret;

	/* Start off the buffer */
	buffer = (byte *)mem_alloc(BUFFER_INITIAL_SIZE);
	buffer_size = BUFFER_INITIAL_SIZE;

	for (i = 0; i < N_ELEMENTS(savefile_blocks); i++)
	{
		buffer_pos = 0;
		buffer_check = 0;

		savefile_blocks[i].saver();

		/* 16 character block name */
		ret = _tcscpy_s((_TCHAR *)savefile_head, sizeof(savefile_head) / sizeof(_TCHAR), 
				savefile_blocks[i].name); /* TODO I don't like the way this mixes byte and _TCHAR but it seems to work */
		assert(ret == 0);
		pos = _tcsclen(savefile_blocks[i].name) * sizeof(_TCHAR);
		while (pos < 16 * sizeof(_TCHAR))
			savefile_head[pos++] = 0; /* 32 */
		
		/* 4-byte block version */
		savefile_head[pos++] = (savefile_blocks[i].cur_ver & 0xFF); 
		savefile_head[pos++] = ((savefile_blocks[i].cur_ver >> 8) & 0xFF);
		savefile_head[pos++] = ((savefile_blocks[i].cur_ver >> 16) & 0xFF);
		savefile_head[pos++] = ((savefile_blocks[i].cur_ver >> 24) & 0xFF); /* 36 */
		
		/* 4-byte block size */
		savefile_head[pos++] = (buffer_pos & 0xFF);
		savefile_head[pos++] = ((buffer_pos >> 8) & 0xFF);
		savefile_head[pos++] = ((buffer_pos >> 16) & 0xFF);
		savefile_head[pos++] = ((buffer_pos >> 24) & 0xFF); /* 40 */
		
		/* 4-byte block checksum */
		savefile_head[pos++] = (buffer_check & 0xFF);
		savefile_head[pos++] = ((buffer_check >> 8) & 0xFF);
		savefile_head[pos++] = ((buffer_check >> 16) & 0xFF);
		savefile_head[pos++] = ((buffer_check >> 24) & 0xFF); /* 44 */

		assert(pos == SAVEFILE_HEAD_SIZE);
		
		file_write(file, (const char*)savefile_head, SAVEFILE_HEAD_SIZE);
		file_write(file, (const char*)buffer, buffer_pos); /* TODO Check this works */

		/* pad to 4 byte multiples */
		if (buffer_pos % 4)
			file_write(file, "xxx", 4 - (buffer_pos % 4)); /* TODO Check this works */
	}
	mem_free(buffer);
	
	return TRUE;
}

static bool try_load(ang_file *file)
{
	byte savefile_head[SAVEFILE_HEAD_SIZE];
	u32b block_version, block_size = 0, block_checksum;
	
	while (TRUE)
	{
		size_t i;
		size_t n_read = 0;
		int ret;

		/* Load in the next header */
		ret = file_read(file, (_TCHAR *)savefile_head, SAVEFILE_HEAD_SIZE, &n_read);
		assert(ret==0);

		/* If nothing was read, that's the end of the file */
		if (n_read == 0) 
			break;

		assert(n_read == SAVEFILE_HEAD_SIZE);
		
		/* 16-character (not byte) block name, null-terminated */
		assert(savefile_head[HEADNAMESIZE - 1] == 0);

		/* Determine the block ID */
		for (i = 0; i < N_ELEMENTS(savefile_blocks); i++)
		{
			if (_tcsnccmp((_TCHAR *) savefile_head, savefile_blocks[i].name,
					sizeof(savefile_blocks[i].name)) == 0)
				break;
		}
		assert(i < N_ELEMENTS(savefile_blocks));
		
		/* 4-byte block version */
		block_version = ((u32b) savefile_head[HEADNAMESIZE+ 0]) |
				((u32b) savefile_head[HEADNAMESIZE + 1] << 8) |
				((u32b) savefile_head[HEADNAMESIZE + 2] << 16) |
				((u32b) savefile_head[HEADNAMESIZE + 3] << 24);
		
		/* 4-byte block size */
		block_size = ((u32b) savefile_head[HEADNAMESIZE + 4]) |
				((u32b) savefile_head[HEADNAMESIZE + 5] << 8) |
				((u32b) savefile_head[HEADNAMESIZE + 6] << 16) |
				((u32b) savefile_head[HEADNAMESIZE + 7] << 24);
		
		/* 4-byte block checksum */
		block_checksum = ((u32b) savefile_head[HEADNAMESIZE + 8]) |
				((u32b) savefile_head[HEADNAMESIZE + 9] << 8) |
				((u32b) savefile_head[HEADNAMESIZE + 10] << 16) |
				((u32b) savefile_head[HEADNAMESIZE + 11] << 24);

		/* pad to 4 bytes */
		if (block_size % 4)
			block_size += 4 - (block_size % 4);

		/* Read stuff in */
		buffer = (byte *)mem_alloc(block_size);
		buffer_size = block_size;
		buffer_pos = 0;
		buffer_check = 0;

		ret = file_read(file, (_TCHAR *) buffer, block_size, &n_read);
		assert(ret == 0);
		assert(n_read == block_size);

		/* Try loading */
		if (savefile_blocks[i].loader(block_version))
			return -1;

/*		assert(buffer_check == block_checksum); */
		mem_free(buffer);
	}
	return 0;
}

/*
 * Attempt to save the player in a savefile
 */
bool old_save(void)
{
	ang_file *file;

	_TCHAR new_savefile[1024];
	_TCHAR old_savefile[1024];
	
	/* New savefile */
	strnfmt(new_savefile, _countof(new_savefile), __T("%s.new"), savefile);
	strnfmt(old_savefile, _countof(old_savefile), __T("%s.old"), savefile);
	
	/* Make sure that the savefile doesn't already exist */
	safe_setuid_grab();
	file_delete(new_savefile);
	file_delete(old_savefile);
	safe_setuid_drop();
	
	/* Open the savefile */
	safe_setuid_grab();
	file = file_open(new_savefile, MODE_WRITE_BIN, FTYPE_SAVE);
	safe_setuid_drop();

	if (file)
	{
		file_write(file, (const char*)&savefile_magic, sizeof(savefile_magic));
		file_writew(file, (const _TCHAR*)&savefile_name, sizeof(savefile_name));

		character_saved = try_save(file);
		file_close(file);
	}
	if (character_saved)
	{
		bool err = FALSE;
		
		safe_setuid_grab();
		
		if (_file_exists(savefile) && !file_move(savefile, old_savefile))
			err = TRUE;
		
		if (!err)
		{
			if (!file_move(new_savefile, savefile))
				err = TRUE;
			
			if (err)
				file_move(old_savefile, savefile);
			else
				file_delete(old_savefile);
		}
		safe_setuid_drop();
		
		return err ? FALSE : TRUE;
	}
	/* Delete temp file */
	safe_setuid_grab();
	file_delete(new_savefile);
	safe_setuid_drop();
	
	return FALSE;
}

/*
 * Attempt to Load a "savefile"
 *
 * On multi-user systems, you may only "read" a savefile if you will be
 * allowed to "write" it later, this prevents painful situations in which
 * the player loads a savefile belonging to someone else, and then is not
 * allowed to save his game when he quits.
 *
 * We return "TRUE" if the savefile was usable, and we set the
 * flag "character_loaded" if a real, living, character was loaded.
 *
 * Note that we always try to load the "current" savefile, even if
 * there is no such file, so we must check for "empty" savefile names.
 */
bool old_load(void)
{
	ang_file *fh;
	byte head[HEADSIZE];
	
	const _TCHAR *what = __T("generic");
	errr err = 0;

	/* Clear screen */
	Term_clear();
	
	fh = file_open(savefile, MODE_READ_BIN, -1);
	if (!fh)
	{
		err = -1;
		what = __T("Cannot open savefile");
	}
	/* Process file */
	if (!err)
	{
		size_t n_read = 0;
		int ret;

		/* Read version / ID marker */
		ret = file_read(fh, (_TCHAR *) &head, HEADSIZE, &n_read);

		assert(ret == 0);

		if (n_read != HEADSIZE)
		{
			file_close(fh);

			what = __T("Cannot read savefile");
			err = -1;
		}
		sf_major = head[0];
		sf_minor = head[1];
		sf_patch = head[2];
		sf_extra = head[3];
	}
	/* Process file */
	if (!err)
	{
		if (sf_major == savefile_magic[0] && sf_minor == savefile_magic[1] &&
				sf_patch == savefile_magic[2] && sf_extra == savefile_magic[3])
		{
			if (_tcsnccmp((_TCHAR *) &head[4], SAVEFILE_NAME, 4) != 0)
			{
				err = -1;
				what = __T("Savefile from different variant");
			}
			else
			{
				err = try_load(fh);
				file_close(fh);
				if (!err) 
					what = __T("cannot read savefile");
			}
		}
#if 0
		else if (sf_major == 3 && sf_minor == 0 &&
				sf_patch == 14 && sf_extra == 0)
		{
			file_close(fh);
			err = rd_savefile_old();
			if (err) what = __T("Cannot parse savefile");
		}
#endif
		else
		{
			what = __T("Unreadable savefile (too old?)");
			err = -1;
		}
	}
	
	/* Paranoia */
	if (!err)
	{
		/* Invalid turn */
		if (!turn) err = -1;
		
		/* Message (below) */
		if (err) 
			what = __T("Broken savefile");
	}
	
	
	/* Okay */
	if (!err)
	{
		/* Still alive */
		if (p_ptr->chp >= 0)
		{
			/* Reset cause of death */
			_tcscpy_s(p_ptr->died_from, _countof(p_ptr->died_from), __T("(alive and well)"));
		}
		
		/* Success */
		return (TRUE);
	}

	/* Message */
	msg_format(__T("Error (%s) reading %d.%d.%d savefile."),
	           what, sf_major, sf_minor, sf_patch);
	message_flush();
	
	/* Oops */
	return (FALSE);
}

