/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "util.h"

struct range {
	uint_least32_t lower;
	uint_least32_t upper;
};

struct properties_payload {
	struct properties *prop;
	const struct property_spec *spec;
	uint_least8_t speclen;
	int (*set_value)(struct properties_payload *, uint_least32_t,
	                 int_least64_t);
	uint_least8_t (*handle_conflict)(uint_least32_t, uint_least8_t,
	                                 uint_least8_t);
};

struct break_test_payload {
	struct break_test **test;
	size_t *testlen;
};

static void *
reallocate_array(void *p, size_t len, size_t size)
{
	if (len > 0 && size > SIZE_MAX / len) {
		errno = ENOMEM;
		return NULL;
	}

	return realloc(p, len * size);
}

int
hextocp(const char *str, size_t len, uint_least32_t *cp)
{
	size_t i;
	int off;
	char relative;

	/* the maximum valid codepoint is 0x10FFFF */
	if (len > 6) {
		fprintf(stderr, "hextocp: '%.*s' is too long.\n", (int)len,
		        str);
		return 1;
	}

	for (i = 0, *cp = 0; i < len; i++) {
		if (str[i] >= '0' && str[i] <= '9') {
			relative = '0';
			off = 0;
		} else if (str[i] >= 'a' && str[i] <= 'f') {
			relative = 'a';
			off = 10;
		} else if (str[i] >= 'A' && str[i] <= 'F') {
			relative = 'A';
			off = 10;
		} else {
			fprintf(stderr, "hextocp: '%.*s' is not hexadecimal.\n",
			        (int)len, str);
			return 1;
		}

		*cp += ((uint_least32_t)1 << (4 * (len - i - 1))) *
		       (uint_least32_t)(str[i] - relative + off);
	}

	if (*cp > UINT32_C(0x10FFFF)) {
		fprintf(stderr, "hextocp: '%.*s' is too large.\n", (int)len,
		        str);
		return 1;
	}

	return 0;
}

int
parse_cp_list(const char *str, uint_least32_t **cp, size_t *cplen)
{
	size_t count, i;
	const char *tmp1 = NULL, *tmp2 = NULL;

	if (strlen(str) == 0) {
		*cp = NULL;
		*cplen = 0;
		return 0;
	}

	/* count the number of spaces in the string and infer list length */
	for (count = 1, tmp1 = str; (tmp2 = strchr(tmp1, ' ')) != NULL;
	     count++, tmp1 = tmp2 + 1) {
		;
	}

	/* allocate resources */
	if (!(*cp = calloc((*cplen = count), sizeof(**cp)))) {
		fprintf(stderr, "calloc: %s\n", strerror(errno));
		exit(1);
	}

	/* go through the string again, parsing the numbers */
	for (i = 0, tmp1 = tmp2 = str; tmp2 != NULL; i++) {
		tmp2 = strchr(tmp1, ' ');
		if (hextocp(tmp1, tmp2 ? (size_t)(tmp2 - tmp1) : strlen(tmp1),
		            &((*cp)[i]))) {
			return 1;
		}
		if (tmp2 != NULL) {
			tmp1 = tmp2 + 1;
		}
	}

	return 0;
}

static int
range_parse(const char *str, struct range *range)
{
	char *p;

	if ((p = strstr(str, "..")) == NULL) {
		/* input has the form "XXXXXX" */
		if (hextocp(str, strlen(str), &range->lower)) {
			return 1;
		}
		range->upper = range->lower;
	} else {
		/* input has the form "XXXXXX..XXXXXX" */
		if (hextocp(str, (size_t)(p - str), &range->lower) ||
		    hextocp(p + 2, strlen(p + 2), &range->upper)) {
			return 1;
		}
	}

	return 0;
}

static bool
get_line(char **buf, size_t *bufsize, FILE *fp, size_t *len)
{
	int ret = EOF;

	for (*len = 0;; (*len)++) {
		if (*len > 0 && *buf != NULL && (*buf)[*len - 1] == '\n') {
			/*
			 * if the previously read character was a newline,
			 * we fake an end-of-file so we NUL-terminate and
			 * are done.
			 */
			ret = EOF;
		} else {
			ret = fgetc(fp);
		}

		if (*len >= *bufsize) {
			/* the buffer needs to be expanded */
			*bufsize += 512;
			if ((*buf = realloc(*buf, *bufsize)) == NULL) {
				fprintf(stderr, "get_line: Out of memory.\n");
				exit(1);
			}
		}

		if (ret != EOF) {
			(*buf)[*len] = (char)ret;
		} else {
			(*buf)[*len] = '\0';
			break;
		}
	}

	return *len == 0 && (feof(fp) || ferror(fp));
}

void
parse_file_with_callback(const char *fname,
                         int (*callback)(const char *, char **, size_t, char *,
                                         void *),
                         void *payload)
{
	FILE *fp;
	char *line = NULL, **field = NULL, *comment;
	size_t linebufsize = 0, i, fieldbufsize = 0, j, nfields, len;

	/* open file */
	if (!(fp = fopen(fname, "r"))) {
		fprintf(stderr, "parse_file_with_callback: fopen '%s': %s.\n",
		        fname, strerror(errno));
		exit(1);
	}

	while (!get_line(&line, &linebufsize, fp, &len)) {
		/* remove trailing newline */
		if (len > 0 && line[len - 1] == '\n') {
			line[len - 1] = '\0';
			len--;
		}

		/* skip empty lines and comment lines */
		if (len == 0 || line[0] == '#') {
			continue;
		}

		/* tokenize line into fields */
		for (i = 0, nfields = 0, comment = NULL; i < (size_t)len; i++) {
			/* skip leading whitespace */
			while (line[i] == ' ') {
				i++;
			}

			/* check if we crashed into the comment */
			if (line[i] != '#') {
				/* extend field buffer, if necessary */
				if (++nfields > fieldbufsize) {
					if ((field = realloc(
						     field,
						     nfields *
							     sizeof(*field))) ==
					    NULL) {
						fprintf(stderr,
						        "parse_file_with_"
						        "callback: realloc: "
						        "%s.\n",
						        strerror(errno));
						exit(1);
					}
					fieldbufsize = nfields;
				}

				/* set current position as field start */
				field[nfields - 1] = &line[i];

				/* continue until we reach ';' or '#' or end */
				while (line[i] != ';' && line[i] != '#' &&
				       line[i] != '\0') {
					i++;
				}
			}

			if (line[i] == '#') {
				/* set comment-variable for later */
				comment = &line[i + 1];
			}

			/* go back whitespace and terminate field there */
			if (i > 0) {
				for (j = i - 1; line[j] == ' '; j--) {
					;
				}
				line[j + 1] = '\0';
			} else {
				line[i] = '\0';
			}

			/* if comment is set, we are done */
			if (comment != NULL) {
				break;
			}
		}

		/* skip leading whitespace in comment */
		while (comment != NULL && comment[0] == ' ') {
			comment++;
		}

		/* call callback function */
		if (callback(fname, field, nfields, comment, payload)) {
			fprintf(stderr, "parse_file_with_callback: "
			                "Malformed input.\n");
			exit(1);
		}
	}

	/* close file */
	if (fclose(fp)) {
		fprintf(stderr, "parse_file_with_callback: fclose '%s': %s.\n",
		        fname, strerror(errno));
		exit(1);
	}

	/* cleanup */
	free(line);
	free(field);
}

static int
properties_callback(const char *file, char **field, size_t nfields,
                    char *comment, void *payload)
{
	/* prop always has the length 0x110000 */
	struct properties_payload *p = (struct properties_payload *)payload;
	struct range r;
	uint_least8_t i;
	uint_least32_t cp;

	(void)comment;

	if (nfields < 2) {
		return 1;
	}

	for (i = 0; i < p->speclen; i++) {
		/* identify fitting file and identifier */
		if (p->spec[i].file && !strcmp(p->spec[i].file, file) &&
		    (!strcmp(p->spec[i].ucdname, field[1]) ||
		     (comment != NULL &&
		      !strncmp(p->spec[i].ucdname, comment,
		               strlen(p->spec[i].ucdname)) &&
		      comment[strlen(p->spec[i].ucdname)] == ' ')) &&
		    (p->spec[i].ucdsubname == NULL ||
		     (nfields >= 3 &&
		      !strcmp(p->spec[i].ucdsubname, field[2])))) {
			/* parse range in first field */
			if (range_parse(field[0], &r)) {
				return 1;
			}

			/* apply to all codepoints in the range */
			for (cp = r.lower; cp <= r.upper; cp++) {
				if (p->set_value(payload, cp, i)) {
					exit(1);
				}
			}
			break;
		}
	}

	return 0;
}

void
properties_compress(const struct properties *prop,
                    struct properties_compressed *comp)
{
	uint_least32_t cp, i;

	/* initialization */
	if (!(comp->offset = malloc((size_t)UINT32_C(0x110000) *
	                            sizeof(*(comp->offset))))) {
		fprintf(stderr, "malloc: %s\n", strerror(errno));
		exit(1);
	}
	comp->data = NULL;
	comp->datalen = 0;

	for (cp = 0; cp < UINT32_C(0x110000); cp++) {
		for (i = 0; i < comp->datalen; i++) {
			if (!memcmp(&(prop[cp]), &(comp->data[i]),
			            sizeof(*prop))) {
				/* found a match! */
				comp->offset[cp] = i;
				break;
			}
		}
		if (i == comp->datalen) {
			/*
			 * found no matching properties-struct, so
			 * add current properties to data and add the
			 * offset in the offset-table
			 */
			if (!(comp->data = reallocate_array(
				      comp->data, ++(comp->datalen),
				      sizeof(*(comp->data))))) {
				fprintf(stderr, "reallocate_array: %s\n",
				        strerror(errno));
				exit(1);
			}
			memcpy(&(comp->data[comp->datalen - 1]), &(prop[cp]),
			       sizeof(*prop));
			comp->offset[cp] = comp->datalen - 1;
		}
	}
}

double
properties_get_major_minor(const struct properties_compressed *comp,
                           struct properties_major_minor *mm)
{
	size_t i, j, compression_count = 0;

	/*
	 * we currently have an array comp->offset which maps the
	 * codepoints 0..0x110000 to offsets into comp->data.
	 * To improve cache-locality instead and allow a bit of
	 * compressing, instead of directly mapping a codepoint
	 * 0xAAAABB with comp->offset, we generate two arrays major
	 * and minor such that
	 *    comp->offset(0xAAAABB) == minor[major[0xAAAA] + 0xBB]
	 * This yields a major-array of length 2^16 and a minor array
	 * of variable length depending on how many common subsequences
	 * can be filtered out.
	 */

	/* initialize */
	if (!(mm->major = malloc((size_t)0x1100 * sizeof(*(mm->major))))) {
		fprintf(stderr, "malloc: %s\n", strerror(errno));
		exit(1);
	}
	mm->minor = NULL;
	mm->minorlen = 0;

	for (i = 0; i < (size_t)0x1100; i++) {
		/*
		 * we now look at the cp-range (i << 8)..(i << 8 + 0xFF)
		 * and check if its corresponding offset-data already
		 * exists in minor (because then we just point there
		 * and need less storage)
		 */
		for (j = 0; j + 0xFF < mm->minorlen; j++) {
			if (!memcmp(&(comp->offset[i << 8]), &(mm->minor[j]),
			            sizeof(*(comp->offset)) * 0x100)) {
				break;
			}
		}
		if (j + 0xFF < mm->minorlen) {
			/* found an index */
			compression_count++;
			mm->major[i] = j;
		} else {
			/*
			 * add "new" sequence to minor and point to it
			 * in major
			 */
			mm->minorlen += 0x100;
			if (!(mm->minor =
			              reallocate_array(mm->minor, mm->minorlen,
			                               sizeof(*(mm->minor))))) {
				fprintf(stderr, "reallocate_array: %s\n",
				        strerror(errno));
				exit(1);
			}
			memcpy(&(mm->minor[mm->minorlen - 0x100]),
			       &(comp->offset[i << 8]),
			       sizeof(*(mm->minor)) * 0x100);
			mm->major[i] = mm->minorlen - 0x100;
		}
	}

	/* return compression ratio */
	return (double)compression_count / 0x1100 * 100;
}

void
properties_print_lookup_table(const char *name, const size_t *data,
                              size_t datalen)
{
	const char *type;
	size_t i, maxval;

	for (i = 0, maxval = 0; i < datalen; i++) {
		if (data[i] > maxval) {
			maxval = data[i];
		}
	}

	type = (maxval <= UINT_LEAST8_MAX)  ? "uint_least8_t" :
	       (maxval <= UINT_LEAST16_MAX) ? "uint_least16_t" :
	       (maxval <= UINT_LEAST32_MAX) ? "uint_least32_t" :
	                                      "uint_least64_t";

	printf("static const %s %s[] = {\n\t", type, name);
	for (i = 0; i < datalen; i++) {
		printf("%zu", data[i]);
		if (i + 1 == datalen) {
			printf("\n");
		} else if ((i + 1) % 8 != 0) {
			printf(", ");
		} else {
			printf(",\n\t");
		}
	}
	printf("};\n");
}

void
properties_print_derived_lookup_table(
	char *name, size_t *offset, size_t offsetlen,
	int_least64_t (*get_value)(const struct properties *, size_t),
	const void *payload)
{
	const char *type;
	size_t i;
	int_least64_t minval, maxval;

	for (i = 0, minval = INT_LEAST64_MAX, maxval = INT_LEAST64_MIN;
	     i < offsetlen; i++) {
		if (get_value(payload, offset[i]) > maxval) {
			maxval = get_value(payload, offset[i]);
		} else if (get_value(payload, offset[i]) < minval) {
			minval = get_value(payload, offset[i]);
		}
	}

	if (minval < 0) {
		/* we need a signed type */
		type = (minval >= INT_LEAST8_MIN && maxval <= INT_LEAST8_MAX) ?
		               "int_least8_t" :
		       (minval >= INT_LEAST16_MIN &&
		        maxval <= INT_LEAST16_MAX) ?
		               "int_least16_t" :
		       (minval >= INT_LEAST32_MIN &&
		        maxval <= INT_LEAST32_MAX) ?
		               "int_least32_t" :
		               "int_least64_t";
	} else {
		/* we are fine with an unsigned type */
		type = (maxval <= UINT_LEAST8_MAX)  ? "uint_least8_t" :
		       (maxval <= UINT_LEAST16_MAX) ? "uint_least16_t" :
		       (maxval <= UINT_LEAST32_MAX) ? "uint_least32_t" :
		                                      "uint_least64_t";
	}

	printf("static const %s %s[] = {\n\t", type, name);
	for (i = 0; i < offsetlen; i++) {
		printf("%" PRIiLEAST64, get_value(payload, offset[i]));
		if (i + 1 == offsetlen) {
			printf("\n");
		} else if ((i + 1) % 8 != 0) {
			printf(", ");
		} else {
			printf(",\n\t");
		}
	}
	printf("};\n");
}

static void
properties_print_enum(const struct property_spec *spec, size_t speclen,
                      const char *enumname, const char *enumprefix)
{
	size_t i;

	printf("enum %s {\n", enumname);
	for (i = 0; i < speclen; i++) {
		printf("\t%s_%s,\n", enumprefix, spec[i].enumname);
	}
	printf("\tNUM_%sS,\n};\n\n", enumprefix);
}

static int
set_value_bp(struct properties_payload *payload, uint_least32_t cp,
             int_least64_t value)
{
	if (payload->prop[cp].property != payload->speclen) {
		if (payload->handle_conflict == NULL) {
			fprintf(stderr,
			        "set_value_bp: "
			        "Unhandled character break property "
			        "overwrite for 0x%06X (%s <- %s).\n",
			        cp,
			        payload->spec[payload->prop[cp].property]
			                .enumname,
			        payload->spec[value].enumname);
			return 1;
		} else {
			value = payload->handle_conflict(
				cp, (uint_least8_t)payload->prop[cp].property,
				(uint_least8_t)value);
		}
	}
	payload->prop[cp].property = value;

	return 0;
}

static int_least64_t
get_value_bp(const struct properties *prop, size_t offset)
{
	return prop[offset].property;
}

void
properties_generate_break_property(
	const struct property_spec *spec, uint_least8_t speclen,
	uint_least8_t (*fill_missing)(uint_least32_t),
	uint_least8_t (*handle_conflict)(uint_least32_t, uint_least8_t,
                                         uint_least8_t),
	void (*post_process)(struct properties *), const char *prefix,
	const char *argv0)
{
	struct properties_compressed comp;
	struct properties_major_minor mm;
	struct properties_payload payload;
	struct properties *prop;
	size_t i, j, prefixlen = strlen(prefix);
	char buf1[64], prefix_uc[64], buf2[64], buf3[64], buf4[64];

	/*
	 * allocate property buffer for all 0x110000 codepoints and
	 * initialize its entries to the known invalid value "speclen"
	 */
	if (!(prop = calloc(UINT32_C(0x110000), sizeof(*prop)))) {
		fprintf(stderr, "calloc: %s\n", strerror(errno));
		exit(1);
	}
	for (i = 0; i < UINT32_C(0x110000); i++) {
		prop[i].property = speclen;
	}

	/* generate data */
	payload.prop = prop;
	payload.spec = spec;
	payload.speclen = speclen;
	payload.set_value = set_value_bp;
	payload.handle_conflict = handle_conflict;

	/* parse each file exactly once and ignore NULL-fields */
	for (i = 0; i < speclen; i++) {
		for (j = 0; j < i; j++) {
			if (spec[i].file && spec[j].file &&
			    !strcmp(spec[i].file, spec[j].file)) {
				/* file has already been parsed */
				break;
			}
		}
		if (i == j && spec[i].file) {
			/* file has not been processed yet */
			parse_file_with_callback(spec[i].file,
			                         properties_callback, &payload);
		}
	}

	/* fill in the missing properties that weren't explicitly given */
	for (i = 0; i < UINT32_C(0x110000); i++) {
		if (payload.prop[i].property == speclen) {
			if (fill_missing != NULL) {
				payload.prop[i].property =
					fill_missing((uint_least32_t)i);
			} else {
				payload.prop[i].property = 0;
			}
		}
	}

	/* post-processing */
	if (post_process != NULL) {
		post_process(payload.prop);
	}

	/* compress data */
	printf("/* Automatically generated by %s */\n#include <stdint.h>\n\n",
	       argv0);
	properties_compress(prop, &comp);

	fprintf(stderr, "%s: %s-LUT compression-ratio: %.2f%%\n", argv0, prefix,
	        properties_get_major_minor(&comp, &mm));

	/* prepare names */
	if ((size_t)snprintf(buf1, LEN(buf1), "%s_property", prefix) >=
	    LEN(buf1)) {
		fprintf(stderr, "snprintf: String truncated.\n");
		exit(1);
	}
	if (LEN(prefix_uc) + 1 < prefixlen) {
		fprintf(stderr, "snprintf: Buffer too small.\n");
		exit(1);
	}
	for (i = 0; i < prefixlen; i++) {
		prefix_uc[i] = (char)toupper(prefix[i]);
	}
	prefix_uc[prefixlen] = '\0';
	if ((size_t)snprintf(buf2, LEN(buf2), "%s_PROP", prefix_uc) >=
	            LEN(buf2) ||
	    (size_t)snprintf(buf3, LEN(buf3), "%s_major", prefix) >=
	            LEN(buf3) ||
	    (size_t)snprintf(buf4, LEN(buf4), "%s_minor", prefix) >=
	            LEN(buf4)) {
		fprintf(stderr, "snprintf: String truncated.\n");
		exit(1);
	}

	/* print data */
	properties_print_enum(spec, speclen, buf1, buf2);
	properties_print_lookup_table(buf3, mm.major, 0x1100);
	printf("\n");
	properties_print_derived_lookup_table(buf4, mm.minor, mm.minorlen,
	                                      get_value_bp, comp.data);

	/* free data */
	free(prop);
	free(comp.data);
	free(comp.offset);
	free(mm.major);
	free(mm.minor);
}

static int
break_test_callback(const char *fname, char **field, size_t nfields,
                    char *comment, void *payload)
{
	struct break_test *t,
		**test = ((struct break_test_payload *)payload)->test;
	size_t i, *testlen = ((struct break_test_payload *)payload)->testlen,
		  commentlen;
	char *token;

	(void)fname;

	if (nfields < 1) {
		return 1;
	}

	/* append new testcase and initialize with zeroes */
	if ((*test = realloc(*test, ++(*testlen) * sizeof(**test))) == NULL) {
		fprintf(stderr, "break_test_callback: realloc: %s.\n",
		        strerror(errno));
		return 1;
	}
	t = &(*test)[*testlen - 1];
	memset(t, 0, sizeof(*t));

	/* parse testcase "<÷|×> <cp> <÷|×> ... <cp> <÷|×>" */
	for (token = strtok(field[0], " "), i = 0; token != NULL;
	     i++, token = strtok(NULL, " ")) {
		if (i % 2 == 0) {
			/* delimiter or start of sequence */
			if (i == 0 ||
			    !strncmp(token, "\xC3\xB7", 2)) { /* UTF-8 */
				/*
				 * '÷' indicates a breakpoint,
				 * the current length is done; allocate
				 * a new length field and set it to 0
				 */
				if ((t->len = realloc(
					     t->len,
					     ++t->lenlen * sizeof(*t->len))) ==
				    NULL) {
					fprintf(stderr,
					        "break_test_"
					        "callback: realloc: %s.\n",
					        strerror(errno));
					return 1;
				}
				t->len[t->lenlen - 1] = 0;
			} else if (!strncmp(token, "\xC3\x97", 2)) { /* UTF-8 */
				/* '×' indicates a non-breakpoint, do nothing */
			} else {
				fprintf(stderr,
				        "break_test_callback: "
				        "Malformed delimiter '%s'.\n",
				        token);
				return 1;
			}
		} else {
			/* add codepoint to cp-array */
			if ((t->cp = realloc(t->cp,
			                     ++t->cplen * sizeof(*t->cp))) ==
			    NULL) {
				fprintf(stderr,
				        "break_test_callback: "
				        "realloc: %s.\n",
				        strerror(errno));
				return 1;
			}
			if (hextocp(token, strlen(token),
			            &t->cp[t->cplen - 1])) {
				return 1;
			}
			if (t->lenlen > 0) {
				t->len[t->lenlen - 1]++;
			}
		}
	}
	if (t->lenlen > 0 && t->len[t->lenlen - 1] == 0) {
		/*
		 * we allocated one more length than we needed because
		 * the breakpoint was at the end
		 */
		t->lenlen--;
	}

	/* store comment */
	if (comment != NULL) {
		commentlen = strlen(comment) + 1;
		if (((*test)[*testlen - 1].descr = malloc(commentlen)) ==
		    NULL) {
			fprintf(stderr, "break_test_callback: malloc: %s.\n",
			        strerror(errno));
			return 1;
		}
		memcpy((*test)[*testlen - 1].descr, comment, commentlen);
	}

	return 0;
}

void
break_test_list_parse(char *fname, struct break_test **test, size_t *testlen)
{
	struct break_test_payload pl = {
		.test = test,
		.testlen = testlen,
	};
	*test = NULL;
	*testlen = 0;

	parse_file_with_callback(fname, break_test_callback, &pl);
}

void
break_test_list_print(const struct break_test *test, size_t testlen,
                      const char *identifier, const char *progname)
{
	size_t i, j;

	printf("/* Automatically generated by %s */\n"
	       "#include <stdint.h>\n#include <stddef.h>\n\n"
	       "#include \"../gen/types.h\"\n\n",
	       progname);

	printf("static const struct break_test %s[] = {\n", identifier);
	for (i = 0; i < testlen; i++) {
		printf("\t{\n");

		printf("\t\t.cp     = (uint_least32_t[]){");
		for (j = 0; j < test[i].cplen; j++) {
			printf(" UINT32_C(0x%06X)", test[i].cp[j]);
			if (j + 1 < test[i].cplen) {
				putchar(',');
			}
		}
		printf(" },\n");
		printf("\t\t.cplen  = %zu,\n", test[i].cplen);

		printf("\t\t.len    = (size_t[]){");
		for (j = 0; j < test[i].lenlen; j++) {
			printf(" %zu", test[i].len[j]);
			if (j + 1 < test[i].lenlen) {
				putchar(',');
			}
		}
		printf(" },\n");
		printf("\t\t.lenlen = %zu,\n", test[i].lenlen);

		printf("\t\t.descr  = \"%s\",\n", test[i].descr);

		printf("\t},\n");
	}
	printf("};\n");
}

void
break_test_list_free(struct break_test *test, size_t testlen)
{
	size_t i;

	for (i = 0; i < testlen; i++) {
		free(test[i].cp);
		free(test[i].len);
		free(test[i].descr);
	}

	free(test);
}
