#ifndef FILE_H
#define FILE_H

#include <stddef.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,
};

uint8_t *file_read_all(const char *filepath, size_t *size, enum file_err *err);
void file_save(const char *filepath, const char *str, size_t str_size,
	       enum file_err *err);

void file_err_set(enum file_err *err, enum file_err err_);

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

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

uint8_t *
file_read_all(const char *filepath, size_t *size, enum file_err *err)
{
	int32_t fd = open(filepath, O_RDONLY);
	if ( fd < 0 ) {
		file_err_set(err, FILE_ERR_FAIL_OPEN);
		return NULL;
	}

	off_t file_size = lseek(fd, 0, SEEK_END);
	if ( file_size < 0 ) {
		file_err_set(err, FILE_ERR_FAIL_SEEK);
		close(fd);
		return NULL;
	}
	lseek(fd, 0, SEEK_SET);

	if ( file_size == 0 ) {
		file_err_set(err, FILE_ERR_EMPTY);
		close(fd);
		return NULL;
	}

	const size_t buf_size = ((size_t)file_size) + 1;
	uint8_t *buf = calloc(sizeof(uint8_t), buf_size);
	if ( buf == NULL ) {
		file_err_set(err, FILE_ERR_FAIL_CALLOC);
		close(fd);
		return NULL;
	}

	{
		ssize_t rd = read(fd, buf, (size_t)file_size);
		if ( rd < 0 ) {
			file_err_set(err, FILE_ERR_FAIL_READ);
			close(fd);
			free(buf);
			return NULL;
		}
		if ( rd == 0 ) {
			file_err_set(err, FILE_ERR_FILE_EMPTY);
			close(fd);
			free(buf);
			return NULL;
		}
	}

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

	if ( size != NULL ) {
		*size = buf_size;
	}
	file_err_set(err, FILE_ERR_OK);
	return buf;
}

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

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

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

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

	file_err_set(err, FILE_ERR_OK);
}


void
file_err_set(enum file_err *err, enum file_err err_)
{
	if ( err != NULL ) {
		*err = err_;
	}
}


#endif
#endif
