/***************************************************************************

    corefile.c

    File access functions.

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

***************************************************************************/

#include "corefile.h"
#include "unicode.h"

#include <stdarg.h>
#include <ctype.h>



/***************************************************************************
    VALIDATION
***************************************************************************/

#if !defined(CRLF) || (CRLF < 1) || (CRLF > 3)
#error CRLF undefined: must be 1 (CR), 2 (LF) or 3 (CR/LF)
#endif



/***************************************************************************
    CONSTANTS
***************************************************************************/

#define FILE_BUFFER_SIZE		512

#define OPEN_FLAG_HAS_CRC		0x10000



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

enum _text_file_type
{
	TFT_OSD = 0,	/* OSD depdendent encoding format used when BOMs missing */
	TFT_UTF8,		/* UTF-8 */
	TFT_UTF16BE,	/* UTF-16 (big endian) */
	TFT_UTF16LE,	/* UTF-16 (little endian) */
	TFT_UTF32BE,	/* UTF-32 (UCS-4) (big endian) */
	TFT_UTF32LE		/* UTF-32 (UCS-4) (little endian) */
};
typedef enum _text_file_type text_file_type;


/* typedef struct _core_file core_file -- declared in corefile.h */
struct _core_file
{
	osd_file *		file;						/* OSD file handle */
	UINT32			openflags;					/* flags we were opened with */
	UINT8			data_allocated;				/* was the data allocated by us? */
	UINT8 *			data;						/* file data, if RAM-based */
	UINT64			offset;						/* current file offset */
	UINT64			length;						/* total file length */
	text_file_type	text_type;					/* text output format */
	char			back_chars[UTF8_CHAR_MAX];	/* buffer to hold characters for ungetc */
	int				back_char_head;				/* head of ungetc buffer */
	int				back_char_tail;				/* tail of ungetc buffer */
	UINT64			bufferbase;					/* base offset of internal buffer */
	UINT32			bufferbytes;				/* bytes currently loaded into buffer */
	UINT8			buffer[FILE_BUFFER_SIZE];	/* buffer data */
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

/* misc helpers */
static UINT32 safe_buffer_copy(const void *source, UINT32 sourceoffs, UINT32 sourcelen, void *dest, UINT32 destoffs, UINT32 destlen);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    is_directory_separator - is a given character
    a directory separator? The following logic
    works for most platforms
-------------------------------------------------*/

INLINE int is_directory_separator(char c)
{
	return (c == '\\' || c == '/' || c == ':');
}



/***************************************************************************
    FILE OPEN/CLOSE
***************************************************************************/

/*-------------------------------------------------
    core_fopen - open a file for access and
    return an error code
-------------------------------------------------*/

file_error core_fopen(const char *filename, UINT32 openflags, core_file **file)
{
	file_error filerr = FILERR_NOT_FOUND;

	/* allocate the file itself */
	*file = malloc(sizeof(**file));
	if (*file == NULL)
		return FILERR_OUT_OF_MEMORY;
	memset(*file, 0, sizeof(**file));

	/* attempt to open the file */
	filerr = osd_open(filename, openflags, &(*file)->file, &(*file)->length);
	(*file)->openflags = openflags;

	/* handle errors and return */
	if (filerr != FILERR_NONE)
	{
		core_fclose(*file);
		*file = NULL;
	}
	return filerr;
}


/*-------------------------------------------------
    core_fopen_ram - open a RAM-based buffer for
    file-like access and return an error code
-------------------------------------------------*/

file_error core_fopen_ram(const void *data, size_t length, UINT32 openflags, core_file **file)
{
	/* can only do this for read access */
	if ((openflags & OPEN_FLAG_WRITE) != 0)
		return FILERR_INVALID_ACCESS;
	if ((openflags & OPEN_FLAG_CREATE) != 0)
		return FILERR_INVALID_ACCESS;

	/* allocate the file itself */
	*file = malloc(sizeof(**file));
	if (*file == NULL)
		return FILERR_OUT_OF_MEMORY;
	memset(*file, 0, sizeof(**file));

	/* claim the buffer */
	(*file)->data = (UINT8 *)data;
	(*file)->length = length;
	(*file)->openflags = openflags;

	return FILERR_NONE;
}


/*-------------------------------------------------
    core_fclose - closes a file
-------------------------------------------------*/

void core_fclose(core_file *file)
{
	/* close files and free memory */
	if (file->file != NULL)
		osd_close(file->file);
	if (file->data != NULL && file->data_allocated)
		free(file->data);
	free(file);
}



/***************************************************************************
    FILE POSITIONING
***************************************************************************/

/*-------------------------------------------------
    core_fseek - seek within a file
-------------------------------------------------*/

int core_fseek(core_file *file, INT64 offset, int whence)
{
	int err = 0;

	/* flush any buffered char */
	file->back_char_head = 0;
	file->back_char_tail = 0;

	/* switch off the relative location */
	switch (whence)
	{
		case SEEK_SET:
			file->offset = offset;
			break;

		case SEEK_CUR:
			file->offset += offset;
			break;

		case SEEK_END:
			file->offset = file->length + offset;
			break;
	}
	return err;
}


/*-------------------------------------------------
    core_ftell - return the current file position
-------------------------------------------------*/

UINT64 core_ftell(core_file *file)
{
	/* return the current offset */
	return file->offset;
}


/*-------------------------------------------------
    core_feof - return 1 if we're at the end
    of file
-------------------------------------------------*/

int core_feof(core_file *file)
{
	/* check for buffered chars */
	if (file->back_char_head != file->back_char_tail)
		return 0;

	/* if the offset == length, we're at EOF */
	return (file->offset >= file->length);
}


/*-------------------------------------------------
    core_fsize - returns the size of a file
-------------------------------------------------*/

UINT64 core_fsize(core_file *file)
{
	return file->length;
}



/***************************************************************************
    FILE READ
***************************************************************************/

/*-------------------------------------------------
    core_fread - read from a file
-------------------------------------------------*/

UINT32 core_fread(core_file *file, void *buffer, UINT32 length)
{
	file_error filerr;
	UINT32 bytes_read = 0;

	/* flush any buffered char */
	file->back_char_head = 0;
	file->back_char_tail = 0;

	/* handle real files */
	if (file->data == NULL)
	{
		/* if we're within the buffer, consume that first */
		if (file->offset >= file->bufferbase && file->offset < file->bufferbase + file->bufferbytes)
			bytes_read += safe_buffer_copy(file->buffer, file->offset - file->bufferbase, file->bufferbytes, buffer, bytes_read, length);

		/* if we've got a small amount left, read it into the buffer first */
		if (bytes_read < length)
		{
			if (length - bytes_read < sizeof(file->buffer) / 2)
			{
				/* read as much as makes sense into the buffer */
				file->bufferbase = file->offset + bytes_read;
				file->bufferbytes = 0;
				filerr = osd_read(file->file, file->buffer, file->bufferbase, sizeof(file->buffer), &file->bufferbytes);

				/* do a bounded copy from the buffer to the destination */
				bytes_read += safe_buffer_copy(file->buffer, 0, file->bufferbytes, buffer, bytes_read, length);
			}

			/* read the remainder directly from the file */
			else
			{
				UINT32 new_bytes_read = 0;
				filerr = osd_read(file->file, (UINT8 *)buffer + bytes_read, file->offset + bytes_read, length - bytes_read, &new_bytes_read);
				bytes_read += new_bytes_read;
			}
		}
	}

	/* handle RAM-based files */
	else
		bytes_read += safe_buffer_copy(file->data, (UINT32)file->offset, file->length, buffer, bytes_read, length);

	/* return the number of bytes read */
	file->offset += bytes_read;
	return bytes_read;
}


/*-------------------------------------------------
    core_fgetc - read a character from a file
-------------------------------------------------*/

int core_fgetc(core_file *file)
{
	int result;

	/* refresh buffer, if necessary */
	if (file->back_char_head == file->back_char_tail)
	{
		utf16_char utf16_buffer[UTF16_CHAR_MAX];
		char utf8_buffer[UTF8_CHAR_MAX];
		char default_buffer[16];
		unicode_char uchar = (unicode_char) ~0;
		int readlen, charlen;

		/* do we need to check the byte order marks? */
		if (file->offset == 0)
		{
			UINT8 bom[4];
			int pos = 0;

			if (core_fread(file, bom, 4) == 4)
			{
				if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf)
				{
					file->text_type = TFT_UTF8;
					pos = 3;
				}
				else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == 0xfe && bom[3] == 0xff)
				{
					file->text_type = TFT_UTF32BE;
					pos = 4;
				}
				else if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0x00 && bom[3] == 0x00)
				{
					file->text_type = TFT_UTF32LE;
					pos = 4;
				}
				else if (bom[0] == 0xfe && bom[1] == 0xff)
				{
					file->text_type = TFT_UTF16BE;
					pos = 2;
				}
				else if (bom[0] == 0xff && bom[1] == 0xfe)
				{
					file->text_type = TFT_UTF16LE;
					pos = 2;
				}
				else
				{
					file->text_type = TFT_OSD;
					pos = 0;
				}
			}
			core_fseek(file, pos, SEEK_SET);
		}

		/* fetch the next character */
		switch (file->text_type)
		{
			default:
			case TFT_OSD:
				readlen = core_fread(file, default_buffer, sizeof(default_buffer));
				if (readlen > 0)
				{
					charlen = osd_uchar_from_osdchar(&uchar, default_buffer, readlen / sizeof(default_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(default_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF8:
				readlen = core_fread(file, utf8_buffer, sizeof(utf8_buffer));
				if (readlen > 0)
				{
					charlen = uchar_from_utf8(&uchar, utf8_buffer, readlen / sizeof(utf8_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(utf8_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF16BE:
				readlen = core_fread(file, utf16_buffer, sizeof(utf16_buffer));
				if (readlen > 0)
				{
					charlen = uchar_from_utf16be(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF16LE:
				readlen = core_fread(file, utf16_buffer, sizeof(utf16_buffer));
				if (readlen > 0)
				{
					charlen = uchar_from_utf16le(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0]));
					core_fseek(file, (INT64) (charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR);
				}
				break;

			case TFT_UTF32BE:
				if (core_fread(file, &uchar, sizeof(uchar)) == sizeof(uchar))
					uchar = BIG_ENDIANIZE_INT32(uchar);
				break;

			case TFT_UTF32LE:
				if (core_fread(file, &uchar, sizeof(uchar)) == sizeof(uchar))
					uchar = LITTLE_ENDIANIZE_INT32(uchar);
				break;
		}

		if (uchar != ~0)
		{
			/* place the new character in the ring buffer */
			file->back_char_head = 0;
			file->back_char_tail = utf8_from_uchar(file->back_chars, ARRAY_LENGTH(file->back_chars), uchar);
/*          assert(file->back_char_tail != -1);*/
		}
	}

	/* now read from the ring buffer */
	if (file->back_char_head != file->back_char_tail)
	{
		result = file->back_chars[file->back_char_head++];
		file->back_char_head %= ARRAY_LENGTH(file->back_chars);
	}
	else
		result = EOF;

	return result;
}


/*-------------------------------------------------
    core_ungetc - put back a character read from
    a file
-------------------------------------------------*/

int core_ungetc(int c, core_file *file)
{
	file->back_chars[file->back_char_tail++] = (char) c;
	file->back_char_tail %= ARRAY_LENGTH(file->back_chars);
	return c;
}


/*-------------------------------------------------
    core_fgets - read a line from a text file
-------------------------------------------------*/

char *core_fgets(char *s, int n, core_file *file)
{
	char *cur = s;

	/* loop while we have characters */
	while (n > 0)
	{
		int c = core_fgetc(file);
		if (c == EOF)
			break;

		/* if there's a CR, look for an LF afterwards */
		if (c == 0x0d)
		{
			int c2 = core_fgetc(file);
			if (c2 != 0x0a)
				core_ungetc(c2, file);
			*cur++ = 0x0d;
			n--;
			break;
		}

		/* if there's an LF, reinterp as a CR for consistency */
		else if (c == 0x0a)
		{
			*cur++ = 0x0d;
			n--;
			break;
		}

		/* otherwise, pop the character in and continue */
		*cur++ = c;
		n--;
	}

	/* if we put nothing in, return NULL */
	if (cur == s)
		return NULL;

	/* otherwise, terminate */
	if (n > 0)
		*cur++ = 0;
	return s;
}


/*-------------------------------------------------
    core_fbuffer - return a pointer to the file
    buffer; if it doesn't yet exist, load the
    file into RAM first
-------------------------------------------------*/

const void *core_fbuffer(core_file *file)
{
	file_error filerr;
	UINT32 read_length;

	/* if we already have data, just return it */
	if (file->data != NULL)
		return file->data;

	/* allocate some memory */
	file->data = malloc(file->length);
	if (file->data == NULL)
		return NULL;
	file->data_allocated = TRUE;

	/* read the file */
	filerr = osd_read(file->file, file->data, 0, file->length, &read_length);
	if (filerr != FILERR_NONE || read_length != file->length)
	{
		free(file->data);
		file->data = NULL;
		return NULL;
	}

	/* close the file because we don't need it anymore */
	osd_close(file->file);
	file->file = NULL;
	return file->data;
}



/***************************************************************************
    FILE WRITE
***************************************************************************/

/*-------------------------------------------------
    core_fwrite - write to a file
-------------------------------------------------*/

UINT32 core_fwrite(core_file *file, const void *buffer, UINT32 length)
{
	UINT32 bytes_written = 0;
	file_error filerr;

	/* can't write to RAM-based stuff */
	if (file->data != NULL)
		return 0;

	/* flush any buffered char */
	file->back_char_head = 0;
	file->back_char_tail = 0;

	/* invalidate any buffered data */
	file->bufferbytes = 0;

	/* do the write */
	filerr = osd_write(file->file, buffer, file->offset, length, &bytes_written);

	/* return the number of bytes read */
	file->offset += bytes_written;
	file->length = MAX(file->length, file->offset);
	return bytes_written;
}


/*-------------------------------------------------
    core_fputs - write a line to a text file
-------------------------------------------------*/

int core_fputs(core_file *f, const char *s)
{
	char convbuf[1024];
	char *pconvbuf = convbuf;
	int count = 0;

	/* is this the beginning of the file?  if so, write a byte order mark */
	if (f->offset == 0 && !(f->openflags & OPEN_FLAG_NO_BOM))
	{
		*pconvbuf++ = 0xef;
		*pconvbuf++ = 0xbb;
		*pconvbuf++ = 0xbf;
	}

	/* convert '\n' to platform dependant line endings */
	while (*s != 0)
	{
		if (*s == '\n')
		{
			if (CRLF == 1)		/* CR only */
				*pconvbuf++ = 13;
			else if (CRLF == 2)	/* LF only */
				*pconvbuf++ = 10;
			else if (CRLF == 3)	/* CR+LF */
			{
				*pconvbuf++ = 13;
				*pconvbuf++ = 10;
			}
		}
		else
			*pconvbuf++ = *s;
		s++;

		/* if we overflow, break into chunks */
		if (pconvbuf >= convbuf + ARRAY_LENGTH(convbuf) - 10)
		{
			count += core_fwrite(f, convbuf, pconvbuf - convbuf);
			pconvbuf = convbuf;
		}
	}

	/* final flush */
	if (pconvbuf != convbuf)
		count += core_fwrite(f, convbuf, pconvbuf - convbuf);

	return count;
}


/*-------------------------------------------------
    core_vfprintf - vfprintf to a text file
-------------------------------------------------*/

int core_vfprintf(core_file *f, const char *fmt, va_list va)
{
	char buf[1024];
	vsnprintf(buf, sizeof(buf), fmt, va);
	return core_fputs(f, buf);
}


/*-------------------------------------------------
    core_fprintf - vfprintf to a text file
-------------------------------------------------*/

int CLIB_DECL core_fprintf(core_file *f, const char *fmt, ...)
{
	int rc;
	va_list va;
	va_start(va, fmt);
	rc = core_vfprintf(f, fmt, va);
	va_end(va);
	return rc;
}



/***************************************************************************
    FILENAME UTILITIES
***************************************************************************/

/*-------------------------------------------------
    core_filename_extract_base - extract the base
    name from a filename; note that this makes
    assumptions about path separators
-------------------------------------------------*/

astring *core_filename_extract_base(astring *result, const char *name, int strip_extension)
{
	/* find the start of the name */
	const char *start = name + strlen(name);
	while (start > name && !is_directory_separator(start[-1]))
		start--;

	/* copy the rest into an astring */
	astring_cpyc(result, start);

	/* chop the extension if present */
	if (strip_extension)
		astring_substr(result, 0, astring_rchr(result, 0, '.'));
	return result;
}


/*-------------------------------------------------
    core_filename_ends_with - does the given
    filename end with the specified extension?
-------------------------------------------------*/

int core_filename_ends_with(const char *filename, const char *extension)
{
	int namelen = strlen(filename);
	int extlen = strlen(extension);
	int matches = TRUE;

	/* work backwards checking for a match */
	while (extlen > 0)
		if (tolower(filename[--namelen]) != tolower(extension[--extlen]))
		{
			matches = FALSE;
			break;
		}

	return matches;
}



/***************************************************************************
    MISC HELPERS
***************************************************************************/

/*-------------------------------------------------
    safe_buffer_copy - copy safely from one
    bounded buffer to another
-------------------------------------------------*/

static UINT32 safe_buffer_copy(const void *source, UINT32 sourceoffs, UINT32 sourcelen, void *dest, UINT32 destoffs, UINT32 destlen)
{
	UINT32 sourceavail = sourcelen - sourceoffs;
	UINT32 destavail = destlen - destoffs;
	UINT32 bytes_to_copy = MIN(sourceavail, destavail);
	if (bytes_to_copy > 0)
		memcpy((UINT8 *)dest + destoffs, (const UINT8 *)source + sourceoffs, bytes_to_copy);
	return bytes_to_copy;
}
