///////////////////////////////////////////////////////////////////////////////
//
/// \file       suffix.c
/// \brief      Checks filename suffix and creates the destination filename
//
//  Author:     Lasse Collin
//
//  This file has been put into the public domain.
//  You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////

#include "private.h"

// For case-insensitive filename suffix on case-insensitive systems
#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
#	define strcmp strcasecmp
#endif


static char *custom_suffix = NULL;


/// \brief      Test if the char is a directory separator
static bool
is_dir_sep(char c)
{
#ifdef TUKLIB_DOSLIKE
	return c == '/' || c == '\\' || c == ':';
#else
	return c == '/';
#endif
}


/// \brief      Test if the string contains a directory separator
static bool
has_dir_sep(const char *str)
{
#ifdef TUKLIB_DOSLIKE
	return strpbrk(str, "/\\:") != NULL;
#else
	return strchr(str, '/') != NULL;
#endif
}


/// \brief      Checks if src_name has given compressed_suffix
///
/// \param      suffix      Filename suffix to look for
/// \param      src_name    Input filename
/// \param      src_len     strlen(src_name)
///
/// \return     If src_name has the suffix, src_len - strlen(suffix) is
///             returned. It's always a positive integer. Otherwise zero
///             is returned.
static size_t
test_suffix(const char *suffix, const char *src_name, size_t src_len)
{
	const size_t suffix_len = strlen(suffix);

	// The filename must have at least one character in addition to
	// the suffix. src_name may contain path to the filename, so we
	// need to check for directory separator too.
	if (src_len <= suffix_len
			|| is_dir_sep(src_name[src_len - suffix_len - 1]))
		return 0;

	if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
		return src_len - suffix_len;

	return 0;
}


/// \brief      Removes the filename suffix of the compressed file
///
/// \return     Name of the uncompressed file, or NULL if file has unknown
///             suffix.
static char *
uncompressed_name(const char *src_name, const size_t src_len)
{
	static const struct {
		const char *compressed;
		const char *uncompressed;
	} suffixes[] = {
		{ ".xz",    "" },
		{ ".txz",   ".tar" }, // .txz abbreviation for .txt.gz is rare.
		{ ".lzma",  "" },
		{ ".tlz",   ".tar" },
		// { ".gz",    "" },
		// { ".tgz",   ".tar" },
	};

	const char *new_suffix = "";
	size_t new_len = 0;

	if (opt_format == FORMAT_RAW) {
		// Don't check for known suffixes when --format=raw was used.
		if (custom_suffix == NULL) {
			message_error(_("%s: With --format=raw, "
					"--suffix=.SUF is required unless "
					"writing to stdout"), src_name);
			return NULL;
		}
	} else {
		for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
			new_len = test_suffix(suffixes[i].compressed,
					src_name, src_len);
			if (new_len != 0) {
				new_suffix = suffixes[i].uncompressed;
				break;
			}
		}
	}

	if (new_len == 0 && custom_suffix != NULL)
		new_len = test_suffix(custom_suffix, src_name, src_len);

	if (new_len == 0 && HAVE_LFN_SUPPORT(src_name)) {
		message_warning(_("%s: Filename has an unknown suffix, "
				"skipping"), src_name);
		return NULL;
	}

	if (HAVE_LFN_SUPPORT(src_name)) {
		const size_t new_suffix_len = strlen(new_suffix);
		char *dest_name = xmalloc(new_len + new_suffix_len + 1);

		memcpy(dest_name, src_name, new_len);
		memcpy(dest_name + new_len, new_suffix, new_suffix_len);
		dest_name[new_len + new_suffix_len] = '\0';

		return dest_name;
	} else {
		// For plain DOS there are filename length limitations (8.3).
		//
		// compressed name       uncompressed name
		//   filename.exx    -->   filename.ex
		//   filename.exz    -->   filename.e
		//   filename.xz     -->   filename
		//
		// compressed name       uncompressed name
		//   filename.exl    -->   filename.ex
		//   filename.elz    -->   filename.e
		//   filename.lzm    -->   filename

		size_t new_len1, new_len2, suffix_len;
		for (new_len = src_len; new_len && src_name[new_len] != '.'; new_len--)
			;
		suffix_len = src_len - new_len;
		new_len1 = suffix_len > 3 ? new_len + 3 : suffix_len - 1;  // Last suffix char.
		new_len2 = new_len1 - 1;

		if (suffix_len > 3) {
			if (src_name[new_len1] == 'x' || src_name[new_len1] == 'X' || src_name[new_len1] == 'l' || src_name[new_len1] == 'L') {
				char *dest_name = xmalloc(new_len1);

				memcpy(dest_name, src_name, new_len1);
				dest_name[new_len1] = '\0';  // Discard last suffix char.

				return dest_name;
			} else
			if (((src_name[new_len2] == 'x' || src_name[new_len2] == 'l') && src_name[new_len1] == 'z') ||
			    ((src_name[new_len2] == 'X' || src_name[new_len2] == 'L') && src_name[new_len1] == 'Z')) {
				char *dest_name = xmalloc(new_len2);

				memcpy(dest_name, src_name, new_len2);
				dest_name[new_len2] = '\0';  // Discard last 2 suffix chars.

				return dest_name;
			} else 
			if ((src_name[new_len + 1] == 'l' && src_name[new_len2] == 'z' && src_name[new_len1] == 'm') ||
			    (src_name[new_len + 1] == 'L' && src_name[new_len2] == 'Z' && src_name[new_len1] == 'M')) {
				char *dest_name = xmalloc(new_len + 1);

				memcpy(dest_name, src_name, new_len);
				dest_name[new_len] = '\0';  // Discard all 3 suffix chars.

				return dest_name;
			} else {
				message_warning(_("%s: Filename has an unknown suffix, "
						"skipping"), src_name);
				return NULL;
			}
		} else {
			if ((src_name[new_len2] == 'x' && src_name[new_len1] == 'z') ||
			    (src_name[new_len2] == 'X' && src_name[new_len1] == 'Z')) {
				char *dest_name = xmalloc(new_len2);

				memcpy(dest_name, src_name, new_len);
				dest_name[new_len] = '\0';

				return dest_name;
			} else {
				message_warning(_("%s: Filename has an unknown suffix, "
						"skipping"), src_name);
				return NULL;
			}
		}
	}
}


/// \brief      Appends suffix to src_name
///
/// In contrast to uncompressed_name(), we check only suffixes that are valid
/// for the specified file format.
static char *
compressed_name(const char *src_name, const size_t src_len)
{
	// The order of these must match the order in args.h.
	static const char *const all_suffixes[][3] = {
		{
			".xz",
			".txz",
			NULL
		}, {
			".lzma",
			".tlz",
			NULL
/*
		}, {
			".gz",
			".tgz",
			NULL
*/
		}, {
			// --format=raw requires specifying the suffix
			// manually or using stdout.
			NULL
		}
	};

	// args.c ensures this.
	assert(opt_format != FORMAT_AUTO);

	const size_t format = opt_format - 1;
	const char *const *suffixes = all_suffixes[format];

	for (size_t i = 0; suffixes[i] != NULL; ++i) {
		if (test_suffix(suffixes[i], src_name, src_len) != 0) {
			message_warning(_("%s: File already has `%s' "
					"suffix, skipping"), src_name,
					suffixes[i]);
			return NULL;
		}
	}

	if (custom_suffix != NULL) {
		if (test_suffix(custom_suffix, src_name, src_len) != 0) {
			message_warning(_("%s: File already has `%s' "
					"suffix, skipping"), src_name,
					custom_suffix);
			return NULL;
		}
	}

	// TODO: Hmm, maybe it would be better to validate this in args.c,
	// since the suffix handling when decoding is weird now.
	if (opt_format == FORMAT_RAW && custom_suffix == NULL) {
		message_error(_("%s: With --format=raw, "
				"--suffix=.SUF is required unless "
				"writing to stdout"), src_name);
		return NULL;
	}

	const char *suffix = custom_suffix != NULL
			? custom_suffix : suffixes[0];
	if (HAVE_LFN_SUPPORT(src_name)) {
		const size_t suffix_len = strlen(suffix);

		char *dest_name = xmalloc(src_len + suffix_len + 1);

		memcpy(dest_name, src_name, src_len);
		memcpy(dest_name + src_len, suffix, suffix_len);
		dest_name[src_len + suffix_len] = '\0';

		return dest_name;
	} else {
		// For plain DOS there are filename length limitations (8.3).
		//
		// uncompressed name     compressed name
		//   filename.ext    -->   filename.exx
		//   filename.ex     -->   filename.exx
		//   filename.e      -->   filename.exz
		//
		// uncompressed name     compressed name
		//   filename.ext    -->   filename.exl
		//   filename.ex     -->   filename.exl
		//   filename.e      -->   filename.elz

		size_t len;
		for (len = src_len; len && src_name[len] != '.'; len--)
			;

		if (len) {
			// File name has an extension so only a part of the suffix can be appended.
			if (src_len - len > 2) {
				// Only one character left for suffix.
				const size_t name_len = len + 1 + 2;          // Dot plus 2 extension chars

				char *dest_name = xmalloc(name_len + 1 + 1);  // and 1 suffix char.

				memcpy(dest_name, src_name, name_len);
				if (suffix[0] == '.')
					memcpy(dest_name + name_len,  suffix + 1, 1);
				else
					memcpy(dest_name + name_len,  suffix, 1);
				dest_name[name_len + 1] = '\0';

				return dest_name;
			} else {
				// Two characters left for suffix.
				const size_t name_len = len + 1 + 1;          // Dot plus 1 extension char

				char *dest_name = xmalloc(name_len + 2 + 1);  // and 2 suffix chars.

				memcpy(dest_name, src_name, name_len);
				if (suffix[0] == '.')
					memcpy(dest_name + name_len,  suffix + 1, 2);
				else
					memcpy(dest_name + name_len,  suffix, 2);
				dest_name[name_len + 2] = '\0';

				return dest_name;
			}
		} else {
			// File name has no extension so simply append the whole suffix.
			const size_t name_len = suffix[0] == '.' ? src_len : src_len + 1;  // custom suffix may have no leading dot.
			const size_t suffix_len = strlen(suffix);

			char *dest_name = xmalloc(name_len + suffix_len + 1);

			memcpy(dest_name, src_name, src_len);
			if (suffix[0] != '.')
				memcpy(dest_name + src_len, ".", 1);
			memcpy(dest_name + name_len, suffix, suffix_len);
			if (suffix_len > 3)
				dest_name[name_len + ((name_len != src_len) ? 3 : 4)] = '\0';
			else
				dest_name[name_len + suffix_len] = '\0';

			return dest_name;
		}
	}
}


extern char *
suffix_get_dest_name(const char *src_name)
{
	assert(src_name != NULL);

	// Length of the name is needed in all cases to locate the end of
	// the string to compare the suffix, so calculate the length here.
	const size_t src_len = strlen(src_name);

	return opt_mode == MODE_COMPRESS
			? compressed_name(src_name, src_len)
			: uncompressed_name(src_name, src_len);
}


extern void
suffix_set(const char *suffix)
{
	// Empty suffix and suffixes having a directory separator are
	// rejected. Such suffixes would break things later.
	if (suffix[0] == '\0' || has_dir_sep(suffix))
		message_fatal(_("%s: Invalid filename suffix"), optarg);

	// Replace the old custom_suffix (if any) with the new suffix.
	free(custom_suffix);
	custom_suffix = xstrdup(suffix);
	return;
}
