/*
* UAE - The Un*x Amiga Emulator
*
* Library of functions to make emulated filesystem as independent as
* possible of the host filesystem's capabilities.
*
* Copyright 1999 Bernd Schmidt
*/

#include "sysconfig.h"
#include "sysdeps.h"

#include "options.h"
#include "uae.h"
#include "uae/memory.h"
#include "custom.h"
#include "newcpu.h"
#include "filesys.h"
#include "autoconf.h"
#include "fsusage.h"
#include "scsidev.h"
#include "fsdb.h"
#include "uae/io.h"

#ifdef FSUAE // NL
#include "uae/fs.h"
#undef _WIN32
#endif

/* The on-disk format is as follows:
* Offset 0, 1 byte, valid
* Offset 1, 4 bytes, mode
* Offset 5, 257 bytes, aname
* Offset 263, 257 bytes, nname
* Offset 519, 81 bytes, comment
*/

#define TRACING_ENABLED 0
#if TRACING_ENABLED
#define TRACE(x) do { write_log x; } while(0)
#else
#define TRACE(x)
#endif

TCHAR *nname_begin (TCHAR *nname)
{
	TCHAR *p = _tcsrchr (nname, FSDB_DIR_SEPARATOR);
	if (p)
		return p + 1;
	return nname;
}

#ifndef _WIN32
/* Find the name REL in directory DIRNAME.  If we find a file that
* has exactly the same name, return REL.  If we find a file that
* has the same name when compared case-insensitively, return a
* malloced string that contains the name we found.  If no file
* exists that compares equal to REL, return 0.  */
TCHAR *fsdb_search_dir (const TCHAR *dirname, TCHAR *rel)
{
	TCHAR *p = 0;
	int de;
	my_opendir_s *dir;
	TCHAR fn[MAX_DPATH];

	dir = my_opendir (dirname);
	/* This really shouldn't happen...  */
	if (! dir)
		return 0;

	while (p == 0 && (de = my_readdir (dir, fn)) != 0) {
		if (strcmp (fn, rel) == 0)
			p = rel;
		else if (strcasecmp (fn, rel) == 0)
			p = my_strdup (fn);
	}
	my_closedir (dir);
	return p;
}
#endif

static FILE *get_fsdb (a_inode *dir, const TCHAR *mode)
{
	TCHAR *n;
	FILE *f;

	if (!dir->nname)
		return NULL;
	n = build_nname (dir->nname, FSDB_FILE);
	f = uae_tfopen (n, mode);
	xfree (n);
	return f;
}

static void kill_fsdb (a_inode *dir)
{
	if (!dir->nname)
		return;
	TCHAR *n = build_nname (dir->nname, FSDB_FILE);
	_wunlink (n);
	xfree (n);
}

static void fsdb_fixup (FILE *f, uae_u8 *buf, int size, a_inode *base)
{
	TCHAR *nname;
	int ret;

	if (buf[0] == 0)
		return;
	TCHAR *fnname = au ((char*)buf + 5 + 257);
	nname = build_nname (base->nname, fnname);
	xfree (fnname);
	ret = fsdb_exists (nname);
	if (ret) {
		xfree (nname);
		return;
	}
	TRACE ((_T("uaefsdb '%s' deleted\n"), nname));
	/* someone deleted this file/dir outside of emulation.. */
	buf[0] = 0;
	xfree (nname);
}

/* Prune the db file the first time this directory is opened in a session.  */
void fsdb_clean_dir (a_inode *dir)
{
	uae_u8 buf[1 + 4 + 257 + 257 + 81];
	TCHAR *n;
	FILE *f;
	off_t pos1 = 0, pos2;

	if (!dir->nname)
		return;
	n = build_nname (dir->nname, FSDB_FILE);
	f = uae_tfopen (n, _T("r+b"));
	if (f == 0) {
		xfree (n);
		return;
	}
	for (;;) {
		pos2 = ftell (f);
		if (fread (buf, 1, sizeof buf, f) < sizeof buf)
			break;
		fsdb_fixup (f, buf, sizeof buf, dir);
		if (buf[0] == 0)
			continue;
		if (pos1 != pos2) {
			fseek (f, pos1, SEEK_SET);
			fwrite (buf, 1, sizeof buf, f);
			fseek (f, pos2 + sizeof buf, SEEK_SET);
		}
		pos1 += sizeof buf;
	}
	fclose (f);
	if (pos1 == 0) {
		kill_fsdb (dir);
	} else {
		my_truncate (n, pos1);
	}
	xfree (n);
}

static a_inode *aino_from_buf (a_inode *base, uae_u8 *buf, long off)
{
	uae_u32 mode;
	a_inode *aino = xcalloc (a_inode, 1);
	TCHAR *s;

	mode = do_get_mem_long ((uae_u32 *)(buf + 1));
	buf += 5;
	aino->aname = au ((char*)buf);
	buf += 257;
	s = au ((char*)buf);
	aino->nname = build_nname (base->nname, s);
	xfree (s);
	buf += 257;
	aino->comment = *buf != '\0' ? au ((char*)buf) : 0;
	fsdb_fill_file_attrs (base, aino);
	aino->amigaos_mode = mode;
	aino->has_dbentry = 1;
	aino->dirty = 0;
	aino->db_offset = off;
	return aino;
}

a_inode *fsdb_lookup_aino_aname (a_inode *base, const TCHAR *aname)
{
	FILE *f;

	f = get_fsdb (base, _T("r+b"));
	if (f == 0) {
		if (currprefs.filesys_custom_uaefsdb && (base->volflags & MYVOLUMEINFO_STREAMS))
			return custom_fsdb_lookup_aino_aname (base, aname);
		return 0;
	}
	for (;;) {
		uae_u8 buf[1 + 4 + 257 + 257 + 81];
		TCHAR *s;
		if (fread (buf, 1, sizeof buf, f) < sizeof buf)
			break;
		s = au ((char*)buf + 5);
		if (buf[0] != 0 && same_aname (s, aname)) {
			long pos = ftell (f) - sizeof buf;
			fclose (f);
			xfree (s);
			return aino_from_buf (base, buf, pos);
		}
		xfree (s);
	}
	fclose (f);
	return 0;
}

a_inode *fsdb_lookup_aino_nname (a_inode *base, const TCHAR *nname)
{
	FILE *f;
	char *s;

	f = get_fsdb (base, _T("r+b"));
	if (f == 0) {
		if (currprefs.filesys_custom_uaefsdb && (base->volflags & MYVOLUMEINFO_STREAMS))
			return custom_fsdb_lookup_aino_nname (base, nname);
		return 0;
	}
	s = ua (nname);
	for (;;) {
		uae_u8 buf[1 + 4 + 257 + 257 + 81];
		if (fread (buf, 1, sizeof buf, f) < sizeof buf)
			break;
		if (buf[0] != 0 && strcmp ((char*)buf + 5 + 257, s) == 0) {
			long pos = ftell (f) - sizeof buf;
			fclose (f);
			xfree (s);
			return aino_from_buf (base, buf, pos);
		}
	}
	xfree (s);
	fclose (f);
	return 0;
}

int fsdb_used_as_nname (a_inode *base, const TCHAR *nname)
{
	FILE *f;
	uae_u8 buf[1 + 4 + 257 + 257 + 81];

	f = get_fsdb (base, _T("r+b"));
	if (f == 0) {
		if (currprefs.filesys_custom_uaefsdb && (base->volflags & MYVOLUMEINFO_STREAMS))
			return custom_fsdb_used_as_nname (base, nname);
		return 0;
	}
	for (;;) {
		TCHAR *s;
		if (fread (buf, 1, sizeof buf, f) < sizeof buf)
			break;
		if (buf[0] == 0)
			continue;
		s = au ((char*)buf + 5 + 257);
		if (_tcscmp (s, nname) == 0) {
			xfree (s);
			fclose (f);
			return 1;
		}
		xfree (s);
	}
	fclose (f);
	return 0;
}

static int needs_dbentry (a_inode *aino)
{
	const TCHAR *nn_begin;

	if (aino->deleted)
		return 0;

	if (! fsdb_mode_representable_p (aino, aino->amigaos_mode) || aino->comment != 0)
		return 1;

	nn_begin = nname_begin (aino->nname);
	return _tcscmp (nn_begin, aino->aname) != 0;
}

static void write_aino (FILE *f, a_inode *aino)
{
	uae_u8 buf[1 + 4 + 257 + 257 + 81] = { 0 };

	buf[0] = aino->needs_dbentry ? 1 : 0;
	do_put_mem_long ((uae_u32 *)(buf + 1), aino->amigaos_mode);
	ua_copy ((char*)buf + 5, 256, aino->aname);
	buf[5 + 256] = '\0';
	ua_copy ((char*)buf + 5 + 257, 256, nname_begin (aino->nname));
	buf[5 + 257 + 256] = '\0';
	ua_copy ((char*)buf + 5 + 2 * 257, 80, aino->comment ? aino->comment : _T(""));
	buf[5 + 2 * 257 + 80] = '\0';
	aino->db_offset = ftell (f);
	fwrite (buf, 1, sizeof buf, f);
	aino->has_dbentry = aino->needs_dbentry;
	TRACE ((_T("%d '%s' '%s' written\n"), aino->db_offset, aino->aname, aino->nname));
}

/* Write back the db file for a directory.  */

void fsdb_dir_writeback (a_inode *dir)
{
#ifdef FSUAE
	// .uaem files are used instead of fsdb
#else
	FILE *f;
	int changes_needed = 0;
	int entries_needed = 0;
	a_inode *aino;
	uae_u8 *tmpbuf;
	int size, i;

	TRACE ((_T("fsdb writeback %s\n"), dir->aname));
	/* First pass: clear dirty bits where unnecessary, and see if any work
	* needs to be done.  */
	for (aino = dir->child; aino; aino = aino->sibling) {
		/*
		int old_needs_dbentry = aino->needs_dbentry || aino->has_dbentry;
		aino->needs_dbentry = needs_dbentry (aino);
		entries_needed |= aino->has_dbentry | aino->needs_dbentry;
		*/
		int old_needs_dbentry = aino->has_dbentry;
		int need = needs_dbentry (aino);
		aino->needs_dbentry = need;
		entries_needed |= need;
		if (! aino->dirty)
			continue;
		if (! aino->needs_dbentry && ! old_needs_dbentry)
			aino->dirty = 0;
		else
			changes_needed = 1;
	}
	if (! entries_needed) {
		kill_fsdb (dir);
		TRACE ((_T("fsdb removed\n")));
		return;
	}

	if (! changes_needed) {
		TRACE ((_T("not modified\n")));
		return;
	}

	f = get_fsdb (dir, _T("r+b"));
	if (f == 0) {
		if ((currprefs.filesys_custom_uaefsdb  && (dir->volflags & MYVOLUMEINFO_STREAMS)) || currprefs.filesys_no_uaefsdb) {
			for (aino = dir->child; aino; aino = aino->sibling) {
				aino->dirty = 0;
				aino->has_dbentry = 0;
				aino->needs_dbentry = 0;
			}
			return;
		}
		f = get_fsdb (dir, _T("w+b"));
		if (f == 0) {
			TRACE ((_T("failed\n")));
			/* This shouldn't happen... */
			return;
		}
	}
	fseek (f, 0, SEEK_END);
	size = ftell (f);
	fseek (f, 0, SEEK_SET);
	tmpbuf = 0;
	if (size > 0) {
		tmpbuf = (uae_u8*)malloc (size);
		fread (tmpbuf, 1, size, f);
	}
	TRACE ((_T("**** updating '%s' %d\n"), dir->aname, size));

	for (aino = dir->child; aino; aino = aino->sibling) {
		if (! aino->dirty)
			continue;
		aino->dirty = 0;

		i = 0;
		while (!aino->has_dbentry && i < size) {
			TCHAR *s = au ((char*)tmpbuf + i +  5);
			if (!_tcscmp (s, aino->aname)) {
				aino->has_dbentry = 1;
				aino->db_offset = i;
			}
			xfree (s);
			i += 1 + 4 + 257 + 257 + 81;
		}

		if (! aino->has_dbentry) {
			fseek (f, 0, SEEK_END);
			aino->has_dbentry = 1;
		} else {
			fseek (f, aino->db_offset, SEEK_SET);
		}
		write_aino (f, aino);
	}
	TRACE ((_T("end\n")));
	fclose (f);
	xfree (tmpbuf);
#endif
}
