/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "strescape.h"
#include "unichar.h"
#include "mail-types.h"
#include "imap-parser.h"
#include "imap-util.h"

void imap_write_flags(string_t *dest, enum mail_flags flags,
		      const char *const *keywords)
{
	size_t size;

	size = str_len(dest);
	if ((flags & MAIL_ANSWERED) != 0)
		str_append(dest, "\\Answered ");
	if ((flags & MAIL_FLAGGED) != 0)
		str_append(dest, "\\Flagged ");
	if ((flags & MAIL_DELETED) != 0)
		str_append(dest, "\\Deleted ");
	if ((flags & MAIL_SEEN) != 0)
		str_append(dest, "\\Seen ");
	if ((flags & MAIL_DRAFT) != 0)
		str_append(dest, "\\Draft ");
	if ((flags & MAIL_RECENT) != 0)
		str_append(dest, "\\Recent ");

	if (keywords != NULL) {
		/* we have keywords too */
		while (*keywords != NULL) {
			str_append(dest, *keywords);
			str_append_c(dest, ' ');
			keywords++;
		}
	}

	if (str_len(dest) != size)
		str_truncate(dest, str_len(dest)-1);
}

enum mail_flags imap_parse_system_flag(const char *str)
{
	if (strcasecmp(str, "\\Answered") == 0)
		return MAIL_ANSWERED;
	else if (strcasecmp(str, "\\Flagged") == 0)
		return MAIL_FLAGGED;
	else if (strcasecmp(str, "\\Deleted") == 0)
		return MAIL_DELETED;
	else if (strcasecmp(str, "\\Seen") == 0)
		return MAIL_SEEN;
	else if (strcasecmp(str, "\\Draft") == 0)
		return MAIL_DRAFT;
	else if (strcasecmp(str, "\\Recent") == 0)
		return MAIL_RECENT;
	else
		return 0;
}

void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array)
{
	const struct seq_range *range;
	unsigned int i, count;

	range = array_get(array, &count);
	for (i = 0; i < count; i++) {
		if (i > 0)
			str_append_c(dest, ',');
		str_printfa(dest, "%u", range[i].seq1);
		if (range[i].seq1 != range[i].seq2)
			str_printfa(dest, ":%u", range[i].seq2);
	}
}

void imap_write_arg(string_t *dest, const struct imap_arg *arg)
{
	switch (arg->type) {
	case IMAP_ARG_NIL:
		str_append(dest, "NIL");
		break;
	case IMAP_ARG_ATOM:
		str_append(dest, imap_arg_as_astring(arg));
		break;
	case IMAP_ARG_STRING: {
		const char *strarg = imap_arg_as_astring(arg);
		str_append_c(dest, '"');
		str_append_escaped(dest, strarg, strlen(strarg));
		str_append_c(dest, '"');
		break;
	}
	case IMAP_ARG_LITERAL: {
		const char *strarg = imap_arg_as_astring(arg);
		str_printfa(dest, "{%zu}\r\n",
			    strlen(strarg));
		str_append(dest, strarg);
		break;
	}
	case IMAP_ARG_LIST:
		str_append_c(dest, '(');
		imap_write_args(dest, imap_arg_as_list(arg));
		str_append_c(dest, ')');
		break;
	case IMAP_ARG_LITERAL_SIZE:
	case IMAP_ARG_LITERAL_SIZE_NONSYNC:
		str_printfa(dest, "<%"PRIuUOFF_T" byte literal>",
			    imap_arg_as_literal_size(arg));
		break;
	case IMAP_ARG_EOL:
		i_unreached();
	}
}

void imap_write_args(string_t *dest, const struct imap_arg *args)
{
	bool first = TRUE;

	for (; !IMAP_ARG_IS_EOL(args); args++) {
		if (first)
			first = FALSE;
		else
			str_append_c(dest, ' ');
		imap_write_arg(dest, args);
	}
}

static void imap_human_args_fix_control_chars(char *str)
{
	size_t i;

	for (i = 0; str[i] != '\0'; i++) {
		if (str[i] < 0x20 || str[i] == 0x7f)
			str[i] = '?';
	}
}

void imap_write_args_for_human(string_t *dest, const struct imap_arg *args)
{
	bool first = TRUE;

	for (; !IMAP_ARG_IS_EOL(args); args++) {
		if (first)
			first = FALSE;
		else
			str_append_c(dest, ' ');

		switch (args->type) {
		case IMAP_ARG_NIL:
			str_append(dest, "NIL");
			break;
		case IMAP_ARG_ATOM:
			/* atom has only printable us-ascii chars */
			str_append(dest, imap_arg_as_astring(args));
			break;
		case IMAP_ARG_STRING:
		case IMAP_ARG_LITERAL: {
			const char *strarg = imap_arg_as_astring(args);

			if (strpbrk(strarg, "\r\n") != NULL) {
				str_printfa(dest, "<%zu byte multi-line literal>",
					    strlen(strarg));
				break;
			}
			strarg = str_escape(strarg);

			str_append_c(dest, '"');
			size_t start_pos = str_len(dest);
			/* append only valid UTF-8 chars */
			if (uni_utf8_get_valid_data((const unsigned char *)strarg,
						    strlen(strarg), dest))
				str_append(dest, strarg);
			/* replace all control chars */
			imap_human_args_fix_control_chars(
				str_c_modifiable(dest) + start_pos);
			str_append_c(dest, '"');
			break;
		}
		case IMAP_ARG_LIST:
			str_append_c(dest, '(');
			imap_write_args_for_human(dest, imap_arg_as_list(args));
			str_append_c(dest, ')');
			break;
		case IMAP_ARG_LITERAL_SIZE:
		case IMAP_ARG_LITERAL_SIZE_NONSYNC:
			str_printfa(dest, "<%"PRIuUOFF_T" byte literal>",
				    imap_arg_as_literal_size(args));
			break;
		case IMAP_ARG_EOL:
			i_unreached();
		}
	}
}

const char *imap_args_to_str(const struct imap_arg *args)
{
	string_t *str;

	str = t_str_new(128);
	imap_write_args(str, args);
	return str_c(str);
}

void imap_write_capability(string_t *dest, const ARRAY_TYPE(const_string) *capabilities)
{
	string_t *cap_str;
	string_t *cur_str;
	const char *const *tmp;
	if (array_is_empty(capabilities))
		return;
	cap_str = t_str_new(256);
	for (tmp = array_front(capabilities); *tmp != NULL; tmp++) {
		if (str_begins_with(*tmp, "IMAP4"))
			cur_str = dest;
		else
			cur_str = cap_str;
		if (str_len(cur_str) > 0)
			str_append_c(cur_str, ' ');
		str_append(cur_str, *tmp);
	}
	if (str_len(cap_str) > 0) {
		if (str_len(dest) > 0)
			str_append_c(dest, ' ');
		str_append(dest, str_c(cap_str));
	}
}

bool imap_reply_begins(const char *line, const char *prefix,
		       const char **suffix_r)
{
	const char *suffix;

	if (!str_begins_icase(line, prefix, &suffix))
		return FALSE;
	if (suffix[0] == '\0') {
		if (suffix_r != NULL)
			*suffix_r = suffix;
		return TRUE;
	}
	if (suffix[0] == ' ') {
		if (suffix_r != NULL)
			*suffix_r = suffix + 1;
		return TRUE;
	}
	return FALSE;
}
