#ifndef FILE_H
#define FILE_H

#include <stdint.h>

#define CONCAT(a, b) CONCAT_INNER(a, b)
#define CONCAT_INNER(a, b) a ## b

#define UNIQUE_NAME(base) CONCAT(base, __COUNTER__)

enum file_err {
	FILE_ERR_OK,
	FILE_ERR_FAIL_OPEN,
	FILE_ERR_FAIL_SEEK,
	FILE_ERR_FAIL_READ,
	FILE_ERR_FAIL_WRITE,
	FILE_ERR_FAIL_CALLOC,
	FILE_ERR_FAIL_CLOSE,
	FILE_ERR_FILE_EMPTY,
	FILE_ERR_FAIL_WROTE_MORE,
	FILE_ERR_EMPTY,
};

struct ret_void_p_err {
	void *f1;		/* ptr */
	size_t f2;		/* size */
	enum file_err f3;	/* err */
};

struct ret_void_p_err file_read_all(const char *filepath);
enum file_err file_save(const char *filepath, const char *str, size_t str_size);

#if defined(BMP_IMP) || defined(IMP)

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

struct ret_void_p_err
file_read_all(const char *filepath)
{
	enum file_err err = FILE_ERR_OK;

	int32_t fd = open(filepath, O_RDONLY);
	if ( fd < 0 ) {
		err = FILE_ERR_FAIL_OPEN;
		goto err;
	}

	off_t file_size = lseek(fd, 0, SEEK_END);
	if ( file_size < 0 ) {
		err = FILE_ERR_FAIL_SEEK;
		goto err_close;
	}
	lseek(fd, 0, SEEK_SET);

	if ( file_size == 0 ) {
		err = FILE_ERR_EMPTY;
		goto err_close;
	}

	const size_t buf_size = ((size_t)file_size) + 1;
	uint8_t *buf = calloc(sizeof(uint8_t), buf_size);
	if ( buf == NULL ) {
		err = FILE_ERR_FAIL_CALLOC;
		goto err_close;
	}

	{
		ssize_t rd = read(fd, buf, (size_t)file_size);
		if ( rd < 0 ) {
			err = FILE_ERR_FAIL_READ;
			free(buf);
			goto err_close;
		}
		if ( rd == 0 ) {
			err = FILE_ERR_FILE_EMPTY;
			free(buf);
			goto err_close;
		}
	}

	if ( close(fd) != 0 ) {
		/* It should be possible to handle EIO */
		err = FILE_ERR_FAIL_CLOSE;
		goto err;
	}

	return (struct ret_void_p_err) {
		.f1 = buf,
		.f2 = buf_size,
		.f3 = err
	};

err_close: ;
	if ( close(fd) != 0 ) {
		/* It should be possible to handle EIO */
		err = FILE_ERR_FAIL_CLOSE;
		goto err;
	}

err: ;
	return (struct ret_void_p_err) {
		.f1 = NULL,
		.f2 = 0,
		.f3 = err
	};
}

enum file_err
file_save(const char *filepath, const char *str, size_t str_size)
{
	int32_t fd = open(filepath,
			  O_WRONLY | O_CREAT | O_TRUNC,
			  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if ( fd < 0 ) {
		return FILE_ERR_FAIL_OPEN;
	}

	ssize_t wrote = write(fd, str, str_size);
	if ( wrote == -1 ) {
		return FILE_ERR_FAIL_WRITE;
	}

	if ( ((size_t) wrote) != str_size ) {
		return FILE_ERR_FAIL_WROTE_MORE;
	}

	if ( close(fd) != 0 ) {
		/* It should be possible to handle EIO */
		return FILE_ERR_FAIL_CLOSE;
	}

	return FILE_ERR_OK;
}


#endif
#endif
