/************************************************************************
* file.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "file.h"
#include "path.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>

#define FILE_BUFF_STEP 1024

static int file_extend(file_t *file, int min)
{
	unsigned char* b;
	int inc = min;
	int ttl;
	if (min < FILE_BUFF_STEP)
		inc = FILE_BUFF_STEP;
	ttl = file->size+inc;

	b = realloc(file->data,ttl);
	if (!b)
		return 0;

	file->data = b;
	file->size = ttl;

	return inc;
}

/* load a file into memory */
file_t *file_load(char* type, char* name)
{
	file_t *ft;
	FILE *f;
	char* fn;
	char* path;

	if (type) {
		fn = name;
		path = path_get(type,name,1,NULL,0);
	}else{
		path = name;
		fn = strrchr(name,'/');
		if (!fn)
			return NULL;
		fn++;
	}

	if (!path)
		return NULL;

	f = fopen(path, "rb");
	if (!f) {
		free(path);
		return NULL;
	}

	ft = malloc(sizeof(file_t));
	if (!ft) {
		free(path);
		fclose(f);
		return NULL;
	}

	ft->pos = 0;
	ft->modified = 0;

	fseek(f, 0, SEEK_END);
	ft->len = ftell(f);
	fseek(f, 0, SEEK_SET);

	ft->size = ft->len+1;

	ft->data = malloc(ft->size);
	if (!ft->data) {
		free(path);
		fclose(f);
		free(ft);
		return NULL;
	}

	if (ft->len != fread(ft->data, 1, ft->len, f)) {
		free(path);
		fclose(f);
		free(ft->data);
		free(ft);
		return NULL;
	}

	/* this lets us do string operations on text files */
	ft->data[ft->len] = 0;

	fclose(f);

	ft->name = strdup(fn);
	ft->path = path;

	return ft;
}

/* load a file into memory */
file_t *file_create(char* type, char *name)
{
	file_t *ft;
	char* fn;
	char* path;

	if (name) {
		if (type) {
			fn = name;
			path = path_get(type,name,0,NULL,0);
		}else{
			path = strdup(name);
			fn = strrchr(name,'/');
			if (!fn)
				return NULL;
			fn++;
		}
	}else{
		fn = NULL;
		path = NULL;
	}

	ft = malloc(sizeof(file_t));
	if (!ft) {
		free(path);
		return NULL;
	}

	ft->pos = 0;
	ft->len = 0;
	ft->size = 0;
	ft->modified = 0;

	ft->data = NULL;

	if (fn)
		ft->name = strdup(fn);
	ft->path = path;

	return ft;
}

/* free a loaded file */
void file_free(file_t *file)
{
	if (!file)
		return;

	if (file->data)
		free(file->data);

	if (file->name)
		free(file->name);

	free(file);
}

/* flush file data to disk */
void file_flush(file_t *file)
{
	FILE *f;
	if (!file || !file->modified || !file->path)
		return;

	if (!path_exists(file->path)) {
		if (!path_create(NULL,file->path))
			return;
	}

	f = fopen(file->path, "wb");
	if (!f)
		return;

	if (file->len) {
		if (fwrite(file->data,1,file->len,f) != file->len) {
			fclose(f);
			return;
		}
	}

	fclose(f);
	file->modified = 0;
}

/* find the next occurance of value from offset in file, return it's position
 * relative to offset */
int file_find(file_t *file, int offset, unsigned char value)
{
	int r;
	if (!file)
		return -1;

	for (r=offset; r<file->len; r++) {
		if (file->data[r] == value)
			return (r-offset);
	}
	return -1;
}

/* find the next occurance of value from offset in file, return it's position
 * relative to offset */
int file_strfind(file_t *file, int offset, char* value)
{
	int r;
	char* v;
	if (!file || file->len <= offset)
		return -1;

	v = strstr((char*)(file->data+offset),value);
	if (!v)
		return -1;

	r = (v-(char*)file->data);
	if (r < 0)
		return -1;

	return (r-offset);
}

/* read from a file buffer to dst */
int file_read(file_t *file, void* dst, int size)
{
	int len;
	if (!file || !file->len)
		return -1;

	len = file->len - file->pos;
	if (size < len) {
		len = size;
	}

	if (len > 0) {
		memcpy(dst, file->data+file->pos, len);
		file->pos += len;
	}

	return len;
}

/* read a line from a file buffer to dst */
int file_readline(file_t *file, char* dst, int size)
{
	int len;
	char* b;
	char* e;
	if (!file || !file->len)
		return -1;
	if (file->pos >= file->len)
		return -1;

	b = (char*)(file->data+file->pos);

	e = strchr(b,'\n');
	if (e) {
		len = (e-b);
		file->pos += len+1;
	}else{
		len = strlen(b);
		file->pos += len;
	}

	if (len >= size)
		len = size-1;

	memcpy(dst, b, len);
	dst[len] = 0;

	if (file->pos > file->len)
		file->pos = file->len;

	return len;
}

/* read integer from a file buffer */
int file_read_int(file_t *file)
{
	int32_t r;
	file_read(file,&r,4);
	return r;
}

/* read short integer from a file buffer */
int16_t file_read_short(file_t *file)
{
	int16_t r;
	file_read(file,&r,2);
	return r;
}

/* read char from a file buffer */
char file_read_char(file_t *file)
{
	char r;
	file_read(file,&r,1);
	return r;
}

/* read unsigned integer from a file buffer */
uint32_t file_read_uint(file_t *file)
{
	uint32_t r;
	file_read(file,&r,4);
	return r;
}

/* read float from a file buffer */
float file_read_float(file_t *file)
{
	float r;
	file_read(file,&r,4);
	return r;
}

/* seek to a position in a file buffer */
int file_seek(file_t *file, int offset, int origin)
{
	if (!file)
		return -1;

	switch (origin) {
	case SEEK_SET:
		if (file->len < offset)
			offset = file->len;

		file->pos = offset;
		break;
	case SEEK_CUR:
		offset += file->pos;
		if (file->len < offset)
			offset = file->len;

		file->pos = offset;
		break;
	case SEEK_END:
		file->pos = file->len;
		break;
	default:;
	};

	if (file->pos < 0)
		file->pos = 0;

	return 0;
}

/* get the position in a file buffer */
int file_tell(file_t *file)
{
	if (!file)
		return -1;

	return file->pos;
}

/* get the pointer to the current file position */
void *file_get(file_t *file)
{
	return file->data+file->pos;
}

/* write data to a file buffer */
int file_write(file_t *file, void *buff, int size)
{
	if (!file || !buff || size)
		return -1;

	if (file->size <= (file->pos+size+1)) {
		int inc = file->pos+size+2;
		if (file_extend(file,inc) < inc)
			return 0;
	}

	if (!memcpy(file->data+file->pos,buff,size)) {
		file->data[file->pos] = 0;
		return 0;
	}

	file->pos += size;
	if (file->pos >= file->len) {
		file->len = file->pos;
		file->data[file->pos] = 0;
	}
	file->modified = 1;

	return size;
}

/* write a formatted string to a file buffer (printf style) */
int file_writef(file_t *file, char* fmt, ...)
{
	va_list ap;
	int l;
	int s;
	if (!file)
		return -1;

	if (!file->size || !file->data) {
		if (file_extend(file,FILE_BUFF_STEP) <= 0)
			return 0;
	}

	/* basically "keep trying till it all fits" */
	while (1) {
		va_start(ap, fmt);
		s = (file->size-1)-file->pos;
		l = vsnprintf((char*)(file->data+file->pos), s, fmt, ap);
		va_end(ap);
		if (l < s)
			break;
		if (file_extend(file,s+FILE_BUFF_STEP) <= 0) {
			if (file->data)
				file->data[file->len] = 0;
			return 0;
		}
	}

	file->pos += l;
	if (file->pos >= file->len) {
		file->len = file->pos;
		file->data[file->pos] = 0;
	}
	file->modified = 1;

	return l;
}
