/* SPDX-License-Identifier: GPL-3.0-or-later */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "internal.h"
#include "util.h"

typedef compressor_t *(*compressor_fun_t)(bool compress, size_t block_size,
					  char *options);

typedef void (*compressor_help_fun_t)(void);

static compressor_fun_t compressors[SQFS_COMP_MAX + 1] = {
#ifdef WITH_GZIP
	[SQFS_COMP_GZIP] = create_gzip_compressor,
#endif
#ifdef WITH_XZ
	[SQFS_COMP_XZ] = create_xz_compressor,
#endif
#ifdef WITH_LZO
	[SQFS_COMP_LZO] = create_lzo_compressor,
#endif
#ifdef WITH_LZ4
	[SQFS_COMP_LZ4] = create_lz4_compressor,
#endif
#ifdef WITH_ZSTD
	[SQFS_COMP_ZSTD] = create_zstd_compressor,
#endif
};

static const compressor_help_fun_t helpfuns[SQFS_COMP_MAX + 1] = {
#ifdef WITH_GZIP
	[SQFS_COMP_GZIP] = compressor_gzip_print_help,
#endif
#ifdef WITH_XZ
	[SQFS_COMP_XZ] = compressor_xz_print_help,
#endif
#ifdef WITH_LZO
	[SQFS_COMP_LZO] = compressor_lzo_print_help,
#endif
#ifdef WITH_LZ4
	[SQFS_COMP_LZ4] = compressor_lz4_print_help,
#endif
#ifdef WITH_ZSTD
	[SQFS_COMP_ZSTD] = compressor_zstd_print_help,
#endif
};

static const char *names[] = {
	[SQFS_COMP_GZIP] = "gzip",
	[SQFS_COMP_LZMA] = "lzma",
	[SQFS_COMP_LZO] = "lzo",
	[SQFS_COMP_XZ] = "xz",
	[SQFS_COMP_LZ4] = "lz4",
	[SQFS_COMP_ZSTD] = "zstd",
};

int generic_write_options(int fd, const void *data, size_t size)
{
	uint8_t buffer[size + 2];

	*((uint16_t *)buffer) = htole16(0x8000 | size);
	memcpy(buffer + 2, data, size);

	if (write_data("writing compressor options",
		       fd, buffer, sizeof(buffer))) {
		return -1;
	}

	return sizeof(buffer);
}

int generic_read_options(int fd, void *data, size_t size)
{
	uint8_t buffer[size + 2];

	if (read_data("reading compressor options",
		      fd, buffer, sizeof(buffer))) {
		return -1;
	}

	if (le16toh(*((uint16_t *)buffer)) != (0x8000 | size)) {
		fputs("reading compressor options: invalid meta data header\n",
		      stderr);
		return -1;
	}

	memcpy(data, buffer + 2, size);
	return 0;
}

bool compressor_exists(E_SQFS_COMPRESSOR id)
{
	if (id < SQFS_COMP_MIN || id > SQFS_COMP_MAX)
		return false;

	return (compressors[id] != NULL);
}

compressor_t *compressor_create(E_SQFS_COMPRESSOR id, bool compress,
				size_t block_size, char *options)
{
	if (id < SQFS_COMP_MIN || id > SQFS_COMP_MAX)
		return NULL;

	if (compressors[id] == NULL)
		return NULL;

	return compressors[id](compress, block_size, options);
}

void compressor_print_help(E_SQFS_COMPRESSOR id)
{
	if (id < SQFS_COMP_MIN || id > SQFS_COMP_MAX)
		return;

	if (compressors[id] == NULL)
		return;

	helpfuns[id]();
}

void compressor_print_available(void)
{
	size_t i;

	fputs("Available compressors:\n", stdout);

	for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
		if (compressor_exists(i))
			printf("\t%s\n", names[i]);
	}

	printf("\nDefault compressor: %s\n", names[compressor_get_default()]);
}

const char *compressor_name_from_id(E_SQFS_COMPRESSOR id)
{
	if (id < 0 || (size_t)id >= sizeof(names) / sizeof(names[0]))
		return NULL;

	return names[id];
}

int compressor_id_from_name(const char *name, E_SQFS_COMPRESSOR *out)
{
	size_t i;

	for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
		if (names[i] != NULL && strcmp(names[i], name) == 0) {
			*out = i;
			return 0;
		}
	}

	return -1;
}

E_SQFS_COMPRESSOR compressor_get_default(void)
{
#if defined(WITH_XZ)
	return SQFS_COMP_XZ;
#elif defined(WITH_ZSTD)
	return SQFS_COMP_ZSTD;
#elif defined(WITH_GZIP)
	return SQFS_COMP_GZIP;
#elif defined(WITH_LZO)
	return SQFS_COMP_LZO;
#elif defined(WITH_LZ4)
	return SQFS_COMP_LZ4;
#else
	fputs("No compressor implementation available!\n", stderr);
	exit(EXIT_FAILURE);
#endif
}