/**
 * @file   cmp_tool.c
 * @author Dominik Loidolt (dominik.loidolt@univie.ac.at)
 * @author Johannes Seelig (johannes.seelig@univie.ac.at)
 * @date   2020
 *
 * @copyright GPLv2
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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.
 *
 * @brief command line tool for PLATO ICU/RDCU compression/decompression
 * @see README.md
 * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001
 */

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>

#include <cmp_tool-config.h>

#include "cmp_support.h"
#include "cmp_io.h"
#include "cmp_icu.h"
#include "cmp_chunk.h"
#include "cmp_rdcu_cfg.h"
#include "decmp.h"
#include "cmp_guess.h"
#include "cmp_entity.h"
#include "rdcu_pkt_to_file.h"


#define BUFFER_LENGTH_DEF_FAKTOR 2

#define DEFAULT_MODEL_ID 53264  /* random default id */


/**
 * @brief checks if an optional argument is present
 *
 * this macro evaluates whether the current argument pointer optarg is null and
 * if there is a valid argument present at the current index of argv it updates
 * optarg and increments the index optind if an argument is found it also
 * ensures that the argument is not null empty or another option
 *
 * @return true if an optional argument is present and updates optarg
 * @see https://stackoverflow.com/a/69177115
 */

#define OPTIONAL_ARGUMENT_IS_PRESENT \
	((optarg == NULL \
	&& optind < argc /* make sure optind is valid */ \
	&& NULL != argv[optind] /* make sure it's not a null string */ \
	&& '\0' != argv[optind][0] /* ... or an empty string */ \
	&& '-' != argv[optind][0]) /* ... or another option */ \
	? ((optarg = argv[optind++]) != NULL) /* update optind so the next getopt_long invocation skips argv[optind] */ \
	: (optarg != NULL))


/* find a good set of compression parameters for a given dataset */
static int guess_cmp_pars(struct rdcu_cfg *rcfg, struct cmp_par *chunk_par,
			  uint32_t input_size, const char *guess_option, const
			  char *guess_level_str);

/* compress chunk data and write the results to files */
static int compression_of_chunk(const void *chunk, uint32_t size, void *model,
				const struct cmp_par *chunk_par);

/* compress the data and write the results to files */
static int compression_for_rdcu(struct rdcu_cfg *rcfg);

/* decompress the data and write the results in file(s)*/
static int decompression(const struct cmp_entity *ent, uint16_t *input_model_buf);

/* create a default configuration for a compression data type */
enum cfg_default_opt {DIFF_CFG, MODEL_CFG};
static void cmp_cfg_create_default(struct rdcu_cfg *rcfg, enum cfg_default_opt mode);


/*
 * For long options that have no equivalent short option, use a
 * non-character as a pseudo short option, starting with CHAR_MAX + 1.
 */
enum {
	DIFF_CFG_OPTION = CHAR_MAX + 1,
	GUESS_OPTION,
	GUESS_LEVEL,
	RDCU_PKT_OPTION,
	LAST_INFO,
	NO_HEADER,
	MODEL_ID,
	MODEL_COUTER
};

static const struct option long_options[] = {
	{"rdcu_par", no_argument, NULL, 'a'},
	{"model_cfg", no_argument, NULL, 'n'},
	{"help", no_argument, NULL, 'h'},
	{"verbose", no_argument, NULL, 'v'},
	{"version", no_argument, NULL, 'V'},
	{"rdcu_pkt", no_argument, NULL, RDCU_PKT_OPTION},
	{"diff_cfg", no_argument, NULL, DIFF_CFG_OPTION},
	{"guess", optional_argument, NULL, GUESS_OPTION},
	{"guess_level", required_argument, NULL, GUESS_LEVEL},
	{"last_info", required_argument, NULL, LAST_INFO},
	{"no_header", no_argument, NULL, NO_HEADER},
	{"model_id", required_argument, NULL, MODEL_ID},
	{"model_counter", required_argument, NULL, MODEL_COUTER},
	{"binary", no_argument, NULL, 'b'},
	{NULL, 0, NULL, 0}
};

/* prefix of the generated output file names */
static const char *output_prefix = DEFAULT_OUTPUT_PREFIX;

/* if non zero additional RDCU parameters are included in the compression
 * configuration and decompression information files
 */
static int add_rdcu_pars;

/* if non zero generate RDCU setup packets */
static int rdcu_pkt_mode;

/* file name of the last compression information file to generate parallel RDCU setup packets */
static const char *last_info_file_name;

/* option flags for file IO */
static int io_flags;

/* if non zero add a compression entity header in front of the compressed data */
static int include_cmp_header = 1;

/* model ID set by the --model_id option */
static uint32_t model_id = DEFAULT_MODEL_ID;

/* model counter set by the --model_counter option */
static uint32_t model_counter;


#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#define CMP_MAIN cmp_tool_main
/* Redefine (f)printf to do nothing */
__extension__
#define printf(...) do {} while (0)
#define fprintf(...) do {} while (0)
#define fflush(...) do {} while (0)

int CMP_MAIN(int argc, char **argv);

/**
 * @brief callable main function
 * Resets the global variables and calls the cmp_tool main function.
 *
 * @param argc argument count
 * @param argv argument vector
 *
 * @returns EXIT_SUCCESS on success, EXIT_FAILURE on error.
 */

int testable_cmp_tool_main(int argc, char **argv)
{
	output_prefix = DEFAULT_OUTPUT_PREFIX;
	add_rdcu_pars = 0;
	rdcu_pkt_mode = 0;
	last_info_file_name = NULL;
	io_flags = 0;
	include_cmp_header = 1;
	model_id = DEFAULT_MODEL_ID;
	model_counter = 0;

	optind = 0;
	return CMP_MAIN(argc, argv);
}
#else
#define CMP_MAIN main
#endif


/**
 * @brief This is the main function of the compression / decompression tool
 *
 * @param argc argument count
 * @param argv argument vector
 * @see README.md
 *
 * @returns EXIT_SUCCESS on success, EXIT_FAILURE on error
 */

int CMP_MAIN(int argc, char **argv)
{
	int opt;
	int error;

	const char *cfg_file_name = NULL;
	const char *info_file_name = NULL;
	const char *data_file_name = NULL;
	const char *model_file_name = NULL;
	const char *guess_option = NULL;
	const char *guess_level_str = NULL;
	const char *program_name = argv[0];

	int cmp_operation = 0;
	int print_model_cfg = 0;
	int guess_operation = 0;
	int print_diff_cfg = 0;

	/* buffer containing all read in compressed data for decompression */
	struct cmp_entity *decomp_entity = NULL;
	/* buffer containing the read in model */
	uint16_t *input_model_buf = NULL;
	/* size of the data to be compressed and the model of it */
	uint32_t input_size = 0;

	struct cmp_info info = {0}; /* RDCU decompression information struct */
	struct rdcu_cfg rcfg = {0}; /* RDCU compressor configuration struct */
	struct cmp_par chunk_par = {0}; /* compressor parameters for chunk compression */
	enum cmp_type cmp_type;

	/* show help if no arguments are provided */
	if (argc < 2) {
		print_help(program_name);
		return EXIT_FAILURE;
	}

	while ((opt = getopt_long(argc, argv, "abc:d:hi:m:no:vV", long_options,
				  NULL)) != -1) {
		switch (opt) {
		case 'a': /* --rdcu_par */
			add_rdcu_pars = 1;
			break;
		case 'b':
			io_flags |= CMP_IO_BINARY;
			break;
		case 'c':
			cmp_operation = 1;
			cfg_file_name = optarg;
			break;
		case 'd':
			data_file_name = optarg;
			break;
		case 'h': /* --help */
			print_help(argv[0]);
			return EXIT_SUCCESS;
		case 'i':
			info_file_name = optarg;
			include_cmp_header = 0;
			break;
		case 'm': /* read model */
			model_file_name = optarg;
			break;
		case 'n': /* --model_cfg */
			print_model_cfg = 1;
			break;
		case 'o':
			output_prefix = optarg;
			break;
		case 'v': /* --verbose */
			if (io_flags & CMP_IO_VERBOSE)
				io_flags |= CMP_IO_VERBOSE_EXTRA;
			io_flags |= CMP_IO_VERBOSE;
			break;
		case 'V': /* --version */
			printf("%s version %s\n", PROGRAM_NAME, CMP_TOOL_VERSION);
			return EXIT_SUCCESS;
		case DIFF_CFG_OPTION:
			print_diff_cfg = 1;
			break;
		case GUESS_OPTION:
			guess_operation = 1;
			if (OPTIONAL_ARGUMENT_IS_PRESENT)
				guess_option = optarg;
			break;
		case GUESS_LEVEL:
			guess_level_str = optarg;
			break;
		case LAST_INFO:
			last_info_file_name = optarg;
			/* fall through */
		case RDCU_PKT_OPTION:
			rdcu_pkt_mode = 1;
			add_rdcu_pars = 1;
			/* fall through */
		case NO_HEADER:
			include_cmp_header = 0;
			break;
		case MODEL_ID:
			if (atoui32("model_id", optarg, &model_id))
				return EXIT_FAILURE;
			if (model_counter > UINT16_MAX) {
				fprintf(stderr, "%s: Error: model id value to large.\n", PROGRAM_NAME);
				return EXIT_FAILURE;
			}
			break;
		case MODEL_COUTER:
			if (atoui32("model_counter", optarg, &model_counter))
				return EXIT_FAILURE;
			if (model_counter > UINT8_MAX) {
				fprintf(stderr, "%s: Error: model counter value to large.\n", PROGRAM_NAME);
				return EXIT_FAILURE;
			}
			break;
		default:
			print_help(program_name);
			return EXIT_FAILURE;
		}
	}
	argc -= optind;

#ifdef ARGUMENT_INPUT_MODE

	argv += optind;
	if (argc > 2) {
		printf("%s: To many arguments.\n", PROGRAM_NAME);
		print_help(argv[0]);
		return EXIT_FAILURE;
	}

	if (argc > 0) {
		if (!data_file_name)
			data_file_name = argv[0];
		else {
			printf("You can define the data file using either the -d option or the first argument, but not both.\n");
			print_help(program_name);
			return EXIT_FAILURE;
		}
	}
	if (argc > 1) {
		if (!model_file_name)
			model_file_name = argv[1];
		else {
			printf("You can define the model file using either the -m option or the second argument, but not both.\n");
			print_help(program_name);
			return EXIT_FAILURE;
		}
	}
#else
	if (argc > 0) {
		printf("%s: To many arguments.\n", PROGRAM_NAME);
		print_help(argv[0]);
		return EXIT_FAILURE;
	}
#endif

	if (print_model_cfg || print_diff_cfg) {
		if (print_model_cfg && print_diff_cfg) {
			fprintf(stderr, "%s: Cannot use -n, --model_cfg and -diff_cfg together.\n",
				PROGRAM_NAME);
			return EXIT_FAILURE;
		}
		if (print_model_cfg)
			cmp_cfg_create_default(&rcfg, MODEL_CFG);
		else
			cmp_cfg_create_default(&rcfg, DIFF_CFG);
		cmp_cfg_print(&rcfg, add_rdcu_pars);
		return EXIT_SUCCESS;
	}

	{
		static const char str[] = "### PLATO Compression/Decompression Tool Version " CMP_TOOL_VERSION " ###\n";
		size_t str_len = strlen(str) - 1; /* -1 for \n */
		size_t i;

		for (i = 0; i < str_len; i++)
			printf("#");
		printf("\n");
		printf("%s", str);
		for (i = 0; i < str_len; i++)
			printf("#");
		printf("\n");
	}

	if (!data_file_name) {
		fprintf(stderr, "%s: No data file (-d option) specified.\n", PROGRAM_NAME);
		return EXIT_FAILURE;
	}

	if (!cfg_file_name && !info_file_name && !guess_operation && !include_cmp_header) {
		fprintf(stderr, "%s: No configuration file (-c option) or decompression information file (-i option) specified.\n",
			PROGRAM_NAME);
		return EXIT_FAILURE;
	}


	if (cmp_operation || guess_operation) {
		ssize_t size;

		if (cmp_operation) {
			printf("## Starting the compression ##\n");
			printf("Importing configuration file %s ... ", cfg_file_name);
			cmp_type = cmp_cfg_read(cfg_file_name, &rcfg, &chunk_par, io_flags & CMP_IO_VERBOSE);
			if (cmp_type == CMP_TYPE_ERROR)
				goto fail;
			printf("DONE\n");
		} else { /* guess_operation */
			printf("## Search for a good set of compression parameters ##\n");

			if (guess_option == NULL || !case_insensitive_compare(guess_option, "chunk"))
				cmp_type = CMP_TYPE_CHUNK;
			else
				cmp_type = CMP_TYPE_RDCU;
		}

		printf("Importing data file %s ... ", data_file_name);
		if (cmp_type == CMP_TYPE_RDCU) {
			if (rcfg.samples == 0) {
				/* count the samples in the data file when samples == 0 */
				size = read_file_data(data_file_name, cmp_type, NULL, 0, io_flags);
				if (size <= 0 || size > INT32_MAX || (size_t)size % sizeof(uint16_t)) /* empty file is treated as an error */
					goto fail;
				rcfg.samples = (uint32_t)((size_t)size/sizeof(uint16_t));
				printf("\nNo samples parameter set. Use samples = %u.\n... ", rcfg.samples);
			}

			input_size = rcfg.samples * sizeof(uint16_t);
		} else {
			size  = read_file_data(data_file_name, cmp_type, NULL, 0, io_flags);
			if (size <= 0 || size > INT32_MAX) /* empty file is treated as an error */
				goto fail;
			input_size = (uint32_t)size;
		}

		if (input_size > CMP_ENTITY_MAX_ORIGINAL_SIZE) {
			fprintf(stderr, "%s: Error input data size is to large; maximum original data size: %lu\n", PROGRAM_NAME, CMP_ENTITY_MAX_ORIGINAL_SIZE);
			goto fail;
		}

		rcfg.input_buf = malloc(input_size);
		if (!rcfg.input_buf) {
			fprintf(stderr, "%s: Error allocating memory for input data buffer.\n", PROGRAM_NAME);
			goto fail;
		}

		size = read_file_data(data_file_name, cmp_type, rcfg.input_buf,
				      input_size, io_flags);
		if (size < 0)
			goto fail;
		printf("DONE\n");

	} else { /* decompression mode*/
		printf("## Starting the decompression ##\n");
		if (info_file_name) {
			ssize_t f_size;
			size_t ent_size;

			printf("Importing decompression information file %s ... ", info_file_name);
			error  = cmp_info_read(info_file_name, &info, io_flags & CMP_IO_VERBOSE);
			if (error)
				goto fail;
			printf("DONE\n");

			printf("Importing compressed data file %s ... ", data_file_name);

			ent_size = cmp_ent_create(NULL, DATA_TYPE_IMAGETTE, info.cmp_mode_used == CMP_MODE_RAW,
						  cmp_bit_to_byte(info.cmp_size));
			if (!ent_size)
				goto fail;
			decomp_entity = calloc(1, ent_size);
			if (!decomp_entity) {
				fprintf(stderr, "%s: Error allocating memory for decompression input buffer.\n", PROGRAM_NAME);
				goto fail;
			}
			ent_size = cmp_ent_create(decomp_entity, DATA_TYPE_IMAGETTE, info.cmp_mode_used == CMP_MODE_RAW,
						  cmp_bit_to_byte(info.cmp_size));
			if (!ent_size)
				goto fail;

			f_size = read_file8(data_file_name, cmp_ent_get_data_buf(decomp_entity),
					    cmp_bit_to_byte(info.cmp_size), io_flags);
			if (f_size < 0)
				goto fail;

			error = cmp_ent_write_rdcu_cmp_pars(decomp_entity, &info, NULL);
			if (error)
				goto fail;
		} else { /* read in compressed data with header */
			ssize_t size;
			size_t buf_size;

			printf("Importing compressed data file %s ... ", data_file_name);
			size = read_file_cmp_entity(data_file_name, NULL, 0, io_flags);
			if (size < 0 || size > INT32_MAX)
				goto fail;
			/* to be save allocate at least the size of the cmp_entity struct */
			buf_size = (size_t)size;
			if (buf_size < sizeof(struct cmp_entity))
				buf_size = sizeof(struct cmp_entity);

			decomp_entity = calloc(1, buf_size);
			if (!decomp_entity) {
				fprintf(stderr, "%s: Error allocating memory for the compression entity buffer.\n", PROGRAM_NAME);
				goto fail;
			}
			size = read_file_cmp_entity(data_file_name, decomp_entity,
						    (uint32_t)size, io_flags);
			if (size < 0)
				goto fail;

			if (io_flags & CMP_IO_VERBOSE_EXTRA) {
				cmp_ent_print(decomp_entity);
				printf("\n");
			}

		}
		if (cmp_ent_get_data_type(decomp_entity) == DATA_TYPE_CHUNK)
			cmp_type = CMP_TYPE_CHUNK;
		else
			cmp_type = CMP_TYPE_RDCU;

		printf("DONE\n");
	}

	if (model_file_name && !guess_operation &&
	    ((cmp_operation && !model_mode_is_used(rcfg.cmp_mode)) ||
	     (!cmp_operation && !model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity)))))
		printf("Warring: Model file (-m option) specified but no model is used.\n");

	/* read in model */
	if ((cmp_operation && model_mode_is_used(rcfg.cmp_mode)) ||
	    (!cmp_operation && model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity))) ||
	    (guess_operation && model_file_name)) {
		ssize_t size;
		uint32_t model_size;

		printf("Importing model file %s ... ", model_file_name ? model_file_name : "");
		if (!model_file_name) {
			fprintf(stderr, "%s: No model file (-m option) specified.\n", PROGRAM_NAME);
			goto fail;
		}

		if (cmp_operation || guess_operation)
			model_size = input_size;
		else
			model_size = cmp_ent_get_original_size(decomp_entity);

		size = read_file_data(model_file_name, cmp_type, NULL,
				      model_size, io_flags);
		if (size < 0)
			goto fail;
		if (size < (ssize_t)model_size) {
			fprintf(stderr, "%s: %s: Error: The files do not contain enough data. Expected: 0x%x, has 0x%x.\n",
				PROGRAM_NAME, model_file_name, model_size, (uint32_t)size);
			goto fail;
		}
		if (size != (ssize_t)model_size) {
			fprintf(stderr, "%s: %s: Error: Model file size does not match original data size.\n", PROGRAM_NAME, model_file_name);
			goto fail;
		}

		input_model_buf = malloc(model_size);
		if (!input_model_buf) {
			fprintf(stderr, "%s: Error allocating memory for model buffer.\n", PROGRAM_NAME);
			goto fail;
		}

		size = read_file_data(model_file_name, cmp_type, input_model_buf,
				      model_size, io_flags);
		if (size < 0)
			goto fail;


		printf("DONE\n");

		rcfg.model_buf = input_model_buf;
		rcfg.icu_new_model_buf = input_model_buf; /* in-place model update */
	}

	if (guess_operation) {
		error = guess_cmp_pars(&rcfg, &chunk_par, input_size,
				       guess_option, guess_level_str);
	} else if (cmp_operation) {
		if (cmp_type == CMP_TYPE_CHUNK)
			error = compression_of_chunk(rcfg.input_buf, input_size,
						     input_model_buf, &chunk_par);
		else
			error = compression_for_rdcu(&rcfg);
	} else {
		error = decompression(decomp_entity, input_model_buf);
	}
	if (error)
		goto fail;

	/* write our the updated model for compressed or decompression */
	if (!guess_operation &&
	    ((cmp_operation && model_mode_is_used(rcfg.cmp_mode)) ||
	    (!cmp_operation && model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity))))) {
		uint32_t model_size;

		printf("Write updated model to file %s_upmodel.dat ... ", output_prefix);
		if (cmp_operation)
			model_size = input_size;
		else
			model_size = cmp_ent_get_original_size(decomp_entity);


		error = write_input_data_to_file(input_model_buf, model_size, cmp_type,
						 output_prefix, "_upmodel.dat", io_flags);
		if (error)
			goto fail;
		printf("DONE\n");
	}

	free(rcfg.input_buf);
	free(decomp_entity);
	free(input_model_buf);

	return EXIT_SUCCESS;

fail:
	printf("FAILED\n");

	free(rcfg.input_buf);
	free(decomp_entity);
	free(input_model_buf);

	return EXIT_FAILURE;
}


/**
 * @brief find a good set of compression parameters for a given dataset
 */

static int guess_cmp_pars(struct rdcu_cfg *rcfg, struct cmp_par *chunk_par,
			  uint32_t input_size, const char *guess_option,
			  const char *guess_level_str)
{
	int error;
	uint32_t cmp_size_bit;
	double cr MAYBE_UNUSED;
	enum cmp_data_type data_type;
	char *endptr;
	int guess_level;

	if (guess_level_str) {
		long number = strtol(guess_level_str, &endptr, 10);

		if (errno != 0 || *endptr != '\0' || number < INT_MIN || number > INT_MAX) {
			printf("Invalid guess level number: %s\n", guess_level_str);
			return -1;
		}
		guess_level = (int)number;
	} else {
		guess_level = DEFAULT_GUESS_LEVEL;
	}

	printf("Search for a good set of compression parameters (level: %d) ... ", guess_level);
	fflush(stdout);
	if (guess_option && !case_insensitive_compare(guess_option, "rdcu")) {
		if (add_rdcu_pars)
			data_type = DATA_TYPE_IMAGETTE_ADAPTIVE;
		else
			data_type = DATA_TYPE_IMAGETTE;
		if (rcfg->model_buf)
			rcfg->cmp_mode = CMP_GUESS_DEF_MODE_MODEL;
		else
			rcfg->cmp_mode = CMP_GUESS_DEF_MODE_DIFF;
	} else if (guess_option && !case_insensitive_compare(guess_option, "chunk")) {
		data_type = DATA_TYPE_CHUNK;
	} else {
		data_type = DATA_TYPE_IMAGETTE;
		error = cmp_mode_parse(guess_option, &rcfg->cmp_mode);
		if (error) {
			fprintf(stderr, "%s: Error: unknown guess option: %s\n", PROGRAM_NAME, guess_option);
			return -1;
		}
	}
	if (model_mode_is_used(rcfg->cmp_mode) && !rcfg->model_buf) {
		fprintf(stderr, "%s: Error: model mode needs model data (-m option)\n", PROGRAM_NAME);
		return -1;
	}

	if (data_type == DATA_TYPE_CHUNK) {
		uint32_t result = cmp_guess_chunk(rcfg->input_buf, input_size,
					rcfg->model_buf, chunk_par, guess_level);

		if (cmp_is_error(result))
			return -1;

		cmp_size_bit = 8 * result;
		printf("DONE\n");

		printf("Write the guessed compression chunk parameters to file %s.par ... ", output_prefix);
		error = cmp_par_fo_file(chunk_par, output_prefix, io_flags & CMP_IO_VERBOSE);
		if (error)
			return -1;
	} else {
		input_size = rcfg->samples * sizeof(uint16_t);
		cmp_size_bit = cmp_guess(rcfg, guess_level);
		if (!cmp_size_bit)
			return -1;
		if (include_cmp_header)
			cmp_size_bit = CHAR_BIT * (cmp_bit_to_byte(cmp_size_bit) +
				cmp_ent_cal_hdr_size(data_type, rcfg->cmp_mode == CMP_MODE_RAW));
		printf("DONE\n");

		printf("Write the guessed compression configuration to file %s.cfg ... ", output_prefix);
		error = cmp_cfg_fo_file(rcfg, output_prefix, io_flags & CMP_IO_VERBOSE, add_rdcu_pars);
		if (error)
			return -1;
	}

	printf("DONE\n");

	cr = (8.0 * input_size)/cmp_size_bit;
	printf("Guessed parameters can compress the data with a CR of %.2f.\n", cr);

	return 0;
}


/**
 * @brief generate packets to setup an RDCU compression
 */

static int gen_rdcu_write_pkts(const struct rdcu_cfg *rcfg)
{
	int error;

	error = init_rmap_pkt_to_file();
	if (error) {
		fprintf(stderr, "%s: Read RMAP packet config file .rdcu_pkt_mode_cfg failed.\n",
			PROGRAM_NAME);
		return -1;
	}

	if (last_info_file_name) {
		/* generation of packets for parallel read/write RDCU setup */
		struct cmp_info last_info = {0};

		error  = cmp_info_read(last_info_file_name, &last_info, io_flags & CMP_IO_VERBOSE);
		if (error) {
			fprintf(stderr, "%s: %s: Importing last decompression information file failed.\n",
				PROGRAM_NAME, last_info_file_name);
			return -1;
		}

		error = gen_rdcu_parallel_pkts(rcfg, &last_info);
		if (error)
			return -1;
	}

	/* generation of packets for non-parallel read/write RDCU setup */
	error = gen_write_rdcu_pkts(rcfg);
	if (error)
		return -1;

	return 0;
}


/**
 * return a current PLATO timestamp
 */

static uint64_t return_timestamp(void)
{
	return cmp_ent_create_timestamp(NULL);
}


/**
 * @brief compress chunk data and write the results to files
 */

static int compression_of_chunk(const void *chunk, uint32_t size, void *model,
				const struct cmp_par *chunk_par)
{
	uint32_t bound = compress_chunk_cmp_size_bound(chunk, size);
	uint32_t *cmp_data;
	uint32_t cmp_size;
	int error = 0;

	compress_chunk_init(&return_timestamp, cmp_tool_gen_version_id(CMP_TOOL_VERSION));

	if (!bound)
		return -1;
	cmp_data = calloc(1, bound);
	if (cmp_data == NULL) {
		fprintf(stderr, "%s: Error allocating memory for output buffer.\n", PROGRAM_NAME);
		return -1;
	}

	printf("Compress chunk data ... ");
	cmp_size = compress_chunk(chunk, size, model, model,
				  cmp_data, bound, chunk_par);
	if (cmp_is_error(cmp_size))
		goto cmp_chunk_fail;

	cmp_size = compress_chunk_set_model_id_and_counter(cmp_data, cmp_size,
			(uint16_t)model_id, (uint8_t)model_counter);
	if (cmp_is_error(cmp_size))
		goto cmp_chunk_fail;

	printf("DONE\nWrite compressed data to file %s.cmp ... ", output_prefix);
	error = write_data_to_file(cmp_data, cmp_size, output_prefix, ".cmp", io_flags);

cmp_chunk_fail:
	free(cmp_data);
	cmp_data = NULL;
	if (cmp_is_error(cmp_size)) {
		fprintf(stderr, "%s: %s.\n", PROGRAM_NAME, cmp_get_error_name(cmp_size));
		printf("FAILED\n");
		return (int)cmp_get_error_code(cmp_size);
	}
	if (error) {
		printf("FAILED\n");
		return -1;
	}
	printf("DONE\n");
	return 0;
}


/**
 * @brief compress the data and write the results to files
 */

static int compression_for_rdcu(struct rdcu_cfg *rcfg)
{
	uint64_t start_time = cmp_ent_create_timestamp(NULL);
	enum cmp_data_type data_type = add_rdcu_pars ?
		DATA_TYPE_IMAGETTE_ADAPTIVE : DATA_TYPE_IMAGETTE;
	uint32_t cmp_size;
	int error;
	uint32_t cmp_size_byte, out_buf_size;
	size_t s;
	struct cmp_entity *cmp_entity = NULL;
	void *data_to_write_to_file;
	struct cmp_info info;

	if (rcfg->buffer_length == 0) {
		rcfg->buffer_length = (rcfg->samples+1) * BUFFER_LENGTH_DEF_FAKTOR; /* +1 to prevent malloc(0)*/
		printf("No buffer_length parameter set. Use buffer_length = %u as compression buffer size.\n",
		       rcfg->buffer_length);
	}

	if (rdcu_pkt_mode) {
		void *tmp = rcfg->icu_new_model_buf;

		rcfg->icu_new_model_buf = NULL;
		printf("Generate compression setup packets ...\n");
		error = gen_rdcu_write_pkts(rcfg);
		if (error)
			goto error_cleanup;
		printf("... DONE\n");
		rcfg->icu_new_model_buf = tmp;
	}

	printf("Compress data ... ");
	out_buf_size = rcfg->buffer_length * sizeof(uint16_t);
	if (out_buf_size > CMP_ENTITY_MAX_SIZE * BUFFER_LENGTH_DEF_FAKTOR) {
		fprintf(stderr, "%s: Error buffer_length parameter to large.\n", PROGRAM_NAME);
		goto error_cleanup;
	}
	cmp_entity = calloc(1, out_buf_size + sizeof(struct cmp_entity));
	if (cmp_entity == NULL) {
		fprintf(stderr, "%s: Error allocating memory for output buffer.\n", PROGRAM_NAME);
		goto error_cleanup;
	}
	s = cmp_ent_create(cmp_entity, data_type, rcfg->cmp_mode == CMP_MODE_RAW, out_buf_size);
	if (!s) {
		fprintf(stderr, "%s: error occurred while creating the compression entity header.\n", PROGRAM_NAME);
		goto error_cleanup;
	}
	rcfg->icu_output_buf = cmp_ent_get_data_buf(cmp_entity);

	cmp_size = compress_like_rdcu(rcfg, &info);
	if (cmp_is_error(cmp_size)) {
		if (cmp_get_error_code(cmp_size) == CMP_ERROR_SMALL_BUFFER)
			fprintf(stderr, "Error: The buffer for the compressed data is too small to hold the compressed data. Try a larger buffer_length parameter.\n");
		goto error_cleanup;
	}

	if (!model_counter && model_mode_is_used(rcfg->cmp_mode))
		model_counter++;

	s = cmp_ent_create(cmp_entity, data_type, rcfg->cmp_mode == CMP_MODE_RAW, cmp_bit_to_byte(cmp_size));
	if (!s) {
		fprintf(stderr, "%s: error occurred while creating the compression entity header.\n", PROGRAM_NAME);
		goto error_cleanup;
	}
	error = cmp_ent_set_version_id(cmp_entity, cmp_tool_gen_version_id(CMP_TOOL_VERSION));
	error |= cmp_ent_set_start_timestamp(cmp_entity, start_time);
	error |= cmp_ent_set_end_timestamp(cmp_entity, cmp_ent_create_timestamp(NULL));
	error |= cmp_ent_set_model_id(cmp_entity, model_id);
	error |= cmp_ent_set_model_counter(cmp_entity, model_counter);
	error |= cmp_ent_write_rdcu_cmp_pars(cmp_entity, &info, rcfg);
	if (error) {
		fprintf(stderr, "%s: error occurred while creating the compression entity header.\n", PROGRAM_NAME);
		goto error_cleanup;
	}

	if (include_cmp_header) {
		data_to_write_to_file = cmp_entity;
		cmp_size_byte = cmp_ent_get_size(cmp_entity);
	} else {
		data_to_write_to_file = cmp_ent_get_data_buf(cmp_entity);
		cmp_size_byte = cmp_ent_get_cmp_data_size(cmp_entity);
	}

	printf("DONE\n");

	if (rdcu_pkt_mode) {
		printf("Generate the read results packets ... ");
		error = gen_read_rdcu_pkts(&info);
		if (error)
			goto error_cleanup;
		printf("DONE\n");
	}

	printf("Write compressed data to file %s.cmp ... ", output_prefix);
	error = write_data_to_file(data_to_write_to_file, cmp_size_byte,
				   output_prefix, ".cmp", io_flags);
	if (error)
		goto error_cleanup;
	printf("DONE\n");

	if (!include_cmp_header) {
		printf("Write decompression information to file %s.info ... ",
		       output_prefix);
		error = cmp_info_to_file(&info, output_prefix, add_rdcu_pars);
		if (error)
			goto error_cleanup;
		printf("DONE\n");

		if (io_flags & CMP_IO_VERBOSE) {
			printf("\n");
			print_cmp_info(&info);
			printf("\n");
		}
	}

	free(cmp_entity);
	rcfg->icu_output_buf = NULL;

	return 0;

error_cleanup:
	free(cmp_entity);
	rcfg->icu_output_buf = NULL;

	return -1;
}


/**
 * @brief decompress the data and write the results in file(s)
 */

static int decompression(const struct cmp_entity *ent, uint16_t *input_model_buf)
{
	int error;
	int decomp_size;
	uint16_t *decomp_output;
	enum cmp_type cmp_type;

	printf("Decompress data ... ");

	decomp_size = decompress_cmp_entiy(ent, input_model_buf, input_model_buf, NULL);
	if (decomp_size < 0)
		return -1;
	if (decomp_size == 0) {
		printf("\nWarring: No data are decompressed.\n... ");
		printf("DONE\n");
		return 0;
	}

	decomp_output = malloc((size_t)decomp_size);
	if (decomp_output == NULL) {
		fprintf(stderr, "%s: Error allocating memory for decompressed data.\n", PROGRAM_NAME);
		return -1;
	}

	decomp_size = decompress_cmp_entiy(ent, input_model_buf, input_model_buf, decomp_output);
	if (decomp_size <= 0) {
		free(decomp_output);
		return -1;
	}

	printf("DONE\n");

	printf("Write decompressed data to file %s.dat ... ", output_prefix);

	if (cmp_ent_get_data_type(ent) == DATA_TYPE_CHUNK)
		cmp_type = CMP_TYPE_CHUNK;
	else
		cmp_type = CMP_TYPE_RDCU;
	error = write_input_data_to_file(decomp_output, (uint32_t)decomp_size, cmp_type,
					 output_prefix, ".dat", io_flags);

	free(decomp_output);
	if (error)
		return -1;

	printf("DONE\n");

	return 0;
}


/**
 * @brief create a default configuration for a compression data type
 */

static void cmp_cfg_create_default(struct rdcu_cfg *rcfg, enum cfg_default_opt mode)
{
	if (!rcfg) /* nothing to do */
		return;

	switch (mode) {
	case MODEL_CFG:
		rdcu_cfg_create(rcfg, CMP_DEF_IMA_MODEL_CMP_MODE, CMP_DEF_IMA_MODEL_MODEL_VALUE,
				CMP_DEF_IMA_MODEL_LOSSY_PAR);
		rdcu_cfg_buffers(rcfg, NULL, 0, NULL, CMP_DEF_IMA_MODEL_RDCU_DATA_ADR,
				CMP_DEF_IMA_MODEL_RDCU_MODEL_ADR, CMP_DEF_IMA_MODEL_RDCU_UP_MODEL_ADR,
				CMP_DEF_IMA_MODEL_RDCU_BUFFER_ADR, 0);
		rdcu_cfg_imagette_default(rcfg);
		break;
	case DIFF_CFG:
		rdcu_cfg_create(rcfg, CMP_DEF_IMA_DIFF_CMP_MODE,
				CMP_DEF_IMA_DIFF_MODEL_VALUE,
				CMP_DEF_IMA_DIFF_LOSSY_PAR);
		rdcu_cfg_buffers(rcfg, NULL, 0, NULL, CMP_DEF_IMA_DIFF_RDCU_DATA_ADR,
				CMP_DEF_IMA_DIFF_RDCU_MODEL_ADR, CMP_DEF_IMA_DIFF_RDCU_UP_MODEL_ADR,
				CMP_DEF_IMA_DIFF_RDCU_BUFFER_ADR, 0);
		rdcu_cfg_imagette_default(rcfg);
		break;
	}
}