/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "macros.h"

#include <assert.h> /* assert() */
#include <ctype.h> /* isdigit() tolower() */
#include <stddef.h> /* NULL size_t */
#include <stdio.h> /* snprintf() */
#include <stdlib.h> /* free() */
#include <string.h> /* memset() strncat() strcpy() strlen() strdup() */

#include "cfg/config.h"
#include "compat/fs_limits.h"
#include "modes/dialogs/msg_dialog.h"
#include "ui/colored_line.h"
#include "ui/quickview.h"
#include "ui/ui.h"
#include "utils/macros.h"
#include "utils/path.h"
#include "utils/str.h"
#include "utils/test_helpers.h"
#include "utils/utf8.h"
#include "utils/utils.h"
#include "filelist.h"
#include "filename_modifiers.h"
#include "registers.h"
#include "status.h"

/* Type of path to use during entry path expansion. */
typedef enum
{
	PT_NAME, /* Last path component, i.e. file name. */
	PT_REL,  /* Relative path. */
	PT_FULL, /* Full path. */
}
PathType;

/* Details about a macro found by find_next_macro(). */
typedef struct
{
	MacroKind kind;     /* Type of the macro or invalid/none mark. */
	const char *begin;  /* Points at the start of the macro (percent sign). */
	const char *end;    /* Points past the end of the macro. */

	const char *mod;  /* Location of filename modifiers. */
	const reg_t *reg; /* Register to be used by MK_r. */
	int quoted;       /* Whether escaping via quotes should be used during macro
	                     expansion. */
}
macro_info_t;

/* File iteration function. */
typedef int (*iter_func)(view_t *view, dir_entry_t **entry);

static char * expand_macros(const char command[], const char args[],
		MacroFlags *flags, int for_shell, int for_op, int single_only);
static macro_info_t find_next_macro(const char str[]);
static void limit_to_single_only(macro_info_t *info, int ncurr, int nother);
TSTATIC void append_selected_files(str_buf_t *expanded, view_t *view,
		int under_cursor, int quotes, const char mod[], iter_func iter,
		int for_shell);
static void append_entry(str_buf_t *expanded, view_t *view, PathType type,
		dir_entry_t *entry, int quotes, const char mod[], int for_shell);
static void expand_directory_path(str_buf_t *expanded, view_t *view, int quotes,
		const char mod[], int for_shell);
static void expand_register(str_buf_t *expanded, const reg_t *reg, const char
		curr_dir[], int quotes, const char mod[], int for_shell);
static void expand_preview(str_buf_t *expanded, MacroKind kind);
static preview_area_t get_preview_area(view_t *view);
static void append_path_to_expanded(str_buf_t *expanded, int quotes,
		const char path[]);
static cline_t expand_custom(const char **pattern, size_t nmacros,
		custom_macro_t macros[], int with_opt, int in_opt);
static char * add_missing_macros(char expanded[], size_t len, size_t nmacros,
		custom_macro_t macros[]);

char *
ma_expand(const char command[], const char args[], MacroFlags *flags,
		MacroExpandReason reason)
{
	int for_shell = (reason == MER_SHELL || reason == MER_SHELL_OP);
	int for_op = (reason == MER_OP || reason == MER_SHELL_OP);

	int lpending_marking = lwin.pending_marking;
	int rpending_marking = rwin.pending_marking;

	char *res =
		expand_macros(command, args, flags, for_shell, for_op, /*single_only=*/0);

	lwin.pending_marking = lpending_marking;
	rwin.pending_marking = rpending_marking;

	return res;
}

char *
ma_expand_single(const char command[])
{
	int lpending_marking = lwin.pending_marking;
	int rpending_marking = rwin.pending_marking;

	char *const res = expand_macros(command, NULL, NULL, /*for_shell=*/0,
			/*for_op=*/0, /*single_only=*/1);

	lwin.pending_marking = lpending_marking;
	rwin.pending_marking = rpending_marking;

	unescape(res, 0);
	return res;
}

/* Performs substitution of macros with their values.  args and flags
 * parameters can be NULL.  The string returned needs to be freed by the
 * caller.  After executing flags is one of MF_* values.  On error NULL is
 * returned. */
static char *
expand_macros(const char command[], const char args[], MacroFlags *flags,
		int for_shell, int for_op, int single_only)
{
	ma_flags_set(flags, MF_NONE);

	iter_func iter;
	int ncurr, nother;
	if(for_op)
	{
		iter = &iter_marked_entries;

		/* Turn selection into marking. */
		flist_set_marking(&lwin, 0);
		flist_set_marking(&rwin, 0);

		ncurr = flist_count_marked(curr_view);
		nother = flist_count_marked(other_view);
	}
	else
	{
		iter = &iter_selection_or_current;
		ncurr = curr_view->selected_files;
		nother = other_view->selected_files;
	}

	if(ma_contains1(command, MK_r))
	{
		regs_sync_from_shared_memory();
	}

	str_buf_t expanded = { .data = strdup("") };
	if(expanded.data == NULL)
	{
		goto exit;
	}

	const char *tail;
	macro_info_t info;
	for(tail = command; ; tail = info.end)
	{
		info = find_next_macro(tail);

		/* Append prefix until the current macro to result. */
		str_buf_append_n(&expanded, tail, info.begin - tail);

		if(info.kind == MK_NONE)
		{
			break;
		}

		if(single_only)
		{
			limit_to_single_only(&info, ncurr, nother);
		}

		switch(info.kind)
		{
			case MK_NONE:           /* Handled above.  Pass-through. */
			case MK_INVALID: break; /* Just skip over it. */
			case MK_PERCENT:
				str_buf_append(&expanded, "%");
				break;

			case MK_a:
				if(args != NULL)
				{
					str_buf_append(&expanded, args);
				}
				break;

			case MK_b:
				append_selected_files(&expanded, curr_view, 0, info.quoted, info.mod,
						iter, for_shell);
				str_buf_append(&expanded, " ");
				append_selected_files(&expanded, other_view, 0, info.quoted, info.mod,
						iter, for_shell);
				break;

			case MK_c:
				append_selected_files(&expanded, curr_view, 1, info.quoted, info.mod,
						iter, for_shell);
				break;
			case MK_C:
				append_selected_files(&expanded, other_view, 1, info.quoted, info.mod,
						iter, for_shell);
				break;

			case MK_f:
				append_selected_files(&expanded, curr_view, 0, info.quoted, info.mod,
						iter, for_shell);
				break;
			case MK_F:
				append_selected_files(&expanded, other_view, 0, info.quoted, info.mod,
						iter, for_shell);
				break;

			case MK_l:
				append_selected_files(&expanded, curr_view, 0, info.quoted, info.mod,
						&iter_selected_entries, for_shell);
				break;
			case MK_L:
				append_selected_files(&expanded, other_view, 0, info.quoted, info.mod,
						&iter_selected_entries, for_shell);
				break;

			case MK_d:
				expand_directory_path(&expanded, curr_view, info.quoted, info.mod,
						for_shell);
				break;
			case MK_D:
				expand_directory_path(&expanded, other_view, info.quoted, info.mod,
						for_shell);
				break;

			case MK_n: ma_flags_set(flags, MF_NO_TERM_MUX); break;
			case MK_N: ma_flags_set(flags, MF_KEEP_IN_FG); break;
			case MK_m: ma_flags_set(flags, MF_MENU_OUTPUT); break;
			case MK_M: ma_flags_set(flags, MF_MENU_NAV_OUTPUT); break;
			case MK_S: ma_flags_set(flags, MF_STATUSBAR_OUTPUT); break;
			case MK_q: ma_flags_set(flags, MF_PREVIEW_OUTPUT); break;
			case MK_s: ma_flags_set(flags, MF_SPLIT); break;
			case MK_v: ma_flags_set(flags, MF_SPLIT_VERT); break;
			case MK_u: ma_flags_set(flags, MF_CUSTOMVIEW_OUTPUT); break;
			case MK_U: ma_flags_set(flags, MF_VERYCUSTOMVIEW_OUTPUT); break;
			case MK_i: ma_flags_set(flags, MF_IGNORE); break;

			case MK_r:
				expand_register(&expanded, info.reg, flist_get_dir(curr_view),
						info.quoted, info.mod, for_shell);
				break;

			case MK_Iu: ma_flags_set(flags, MF_CUSTOMVIEW_IOUTPUT); break;
			case MK_IU: ma_flags_set(flags, MF_VERYCUSTOMVIEW_IOUTPUT); break;

			case MK_Pl: ma_flags_set(flags, MF_PIPE_FILE_LIST); break;
			case MK_Pz: ma_flags_set(flags, MF_PIPE_FILE_LIST_Z); break;

			case MK_pc:
				goto exit;
			case MK_pd:
				/* Just skip %pd. */
				break;
			case MK_pu:
				ma_flags_set(flags, MF_NO_CACHE);
				break;
			case MK_ph:
			case MK_pw:
			case MK_px:
			case MK_py:
				expand_preview(&expanded, info.kind);
				break;
		}
	}

exit:
	if(expanded.data == NULL)
	{
		show_error_msg("Memory Error",
				"Failed to allocate enough memory for macro expansion");
	}

	return expanded.data;
}

/* Finds first macro in the string.  Returns information about the macro from
 * which absence of a macro or details about it can be derived. */
static macro_info_t
find_next_macro(const char str[])
{
	static const char MACROS_WITH_QUOTING[] = "cCfFlLbdDr";

	macro_info_t info = {};

	const char *p = until_first(str, '%');
	if(*p == '\0')
	{
		info.begin = p;
		info.end = p;
		info.kind = MK_NONE;
		return info;
	}

	info.begin = p++;
	if(*p == '"' && char_is_one_of(MACROS_WITH_QUOTING, *(p + 1)))
	{
		info.quoted = 1;
		++p;
	}

	info.kind = MK_INVALID;
	switch(*p)
	{
		case '%': info.kind = MK_PERCENT; break;

		case 'a': info.kind = MK_a; break;
		case 'b': info.kind = MK_b; break;

		case 'c': info.kind = MK_c; break;
		case 'C': info.kind = MK_C; break;
		case 'f': info.kind = MK_f; break;
		case 'F': info.kind = MK_F; break;
		case 'l': info.kind = MK_l; break;
		case 'L': info.kind = MK_L; break;
		case 'd': info.kind = MK_d; break;
		case 'D': info.kind = MK_D; break;

		case 'n': info.kind = MK_n; break;
		case 'N': info.kind = MK_N; break;
		case 'm': info.kind = MK_m; break;
		case 'M': info.kind = MK_M; break;
		case 'S': info.kind = MK_S; break;
		case 'q': info.kind = MK_q; break;
		case 's': info.kind = MK_s; break;
		case 'v': info.kind = MK_v; break;
		case 'u': info.kind = MK_u; break;
		case 'U': info.kind = MK_U; break;
		case 'i': info.kind = MK_i; break;

		case 'r':
			info.kind = MK_r;
			info.reg = regs_find(tolower(*++p));
			if(info.reg == NULL)
			{
				--p;
				info.reg = regs_find(DEFAULT_REG_NAME);
				assert(info.reg != NULL);
			}
			break;

		case 'I':
			switch(*++p)
			{
				case 'u': info.kind = MK_Iu; break;
				case 'U': info.kind = MK_IU; break;

				default: --p; break;
			}
			break;

		case 'P':
			switch(*++p)
			{
				case 'l': info.kind = MK_Pl; break;
				case 'z': info.kind = MK_Pz; break;

				default: --p; break;
			}
			break;

		case 'p':
			switch(*++p)
			{
				case 'c': info.kind = MK_pc; break;
				case 'd': info.kind = MK_pd; break;
				case 'u': info.kind = MK_pu; break;
				case 'h': info.kind = MK_ph; break;
				case 'w': info.kind = MK_pw; break;
				case 'x': info.kind = MK_px; break;
				case 'y': info.kind = MK_py; break;

				default: --p; break;
			}
			break;
	}

	/* In combination with pointer updates in the switch above, this ensures
	 * skipping at least a single character after percent sign. */
	info.end = (*p != '\0' ? p + 1 : p);

	if(info.kind != MK_INVALID)
	{
		/* XXX: it's weird that filename modifiers are processed for all macros. */
		info.mod = info.end;
		info.end += mods_length(info.end);
	}

	return info;
}

/* Updates information about a macro in place to filter out non-single element
 * macros. */
static void
limit_to_single_only(macro_info_t *info, int ncurr, int nother)
{
	if(ONE_OF(info->kind, MK_c, MK_C, MK_d, MK_D))
	{
		/* Single-entry macro expansion is used only for internal purposes. */
		info->quoted = 0;
		return;
	}

	if(info->kind == MK_f && ncurr <= 1)
	{
		return;
	}
	if(info->kind == MK_F && nother <= 1)
	{
		return;
	}

	if(info->kind == MK_r && info->reg->nfiles <= 1)
	{
		return;
	}

	info->kind = MK_INVALID;
}

void
ma_flags_set(MacroFlags *flags, MacroFlags flag)
{
	if(flags == NULL)
	{
		return;
	}

	if(flag == MF_NONE)
	{
		*flags = MF_NONE;
	}
	else if(flag < MF_SECOND_SET_)
	{
		*flags = (*flags & ~0x000f) | flag;
	}
	else if(flag < MF_THIRD_SET_)
	{
		*flags = (*flags & ~0x00f0) | flag;
	}
	else if(flag < MF_FOURTH_SET_)
	{
		*flags = (*flags & ~0x0f00) | flag;
	}
	else
	{
		*flags = (*flags & ~0xf000) | flag;
	}
}

TSTATIC void
append_selected_files(str_buf_t *expanded, view_t *view, int under_cursor,
		int quotes, const char mod[], iter_func iter, int for_shell)
{
	const PathType type = (view == other_view)
	                    ? PT_FULL
	                    : (flist_custom_active(view) ? PT_REL : PT_NAME);
#ifdef _WIN32
	size_t old_len = expanded->len;
#endif

	if(!under_cursor)
	{
		int first = 1;
		dir_entry_t *entry = NULL;
		while(iter(view, &entry))
		{
			if(!first)
			{
				str_buf_append(expanded, " ");
			}

			append_entry(expanded, view, type, entry, quotes, mod, for_shell);
			first = 0;
		}
	}
	else
	{
		dir_entry_t *const curr = get_current_entry(view);
		if(!fentry_is_fake(curr))
		{
			append_entry(expanded, view, type, curr, quotes, mod, for_shell);
		}
	}

	if(expanded->data != NULL && for_shell && curr_stats.shell_type == ST_CMD)
	{
		internal_to_system_slashes(expanded->data + old_len);
	}
}

/* Appends path to the entry to the expanded string. */
static void
append_entry(str_buf_t *expanded, view_t *view, PathType type,
		dir_entry_t *entry, int quotes, const char mod[], int for_shell)
{
	char path[PATH_MAX + 1];
	const char *modified;

	switch(type)
	{
		case PT_NAME:
			copy_str(path, sizeof(path), entry->name);
			break;
		case PT_REL:
			get_short_path_of(view, entry, NF_NONE, 0, sizeof(path), path);
			break;
		case PT_FULL:
			get_full_path_of(entry, sizeof(path), path);
			break;

		default:
			assert(0 && "Unexpected path type");
			path[0] = '\0';
			break;
	}

	modified = mods_apply(path, flist_get_dir(view), mod, for_shell);
	append_path_to_expanded(expanded, quotes, modified);
}

static void
expand_directory_path(str_buf_t *expanded, view_t *view, int quotes,
		const char mod[], int for_shell)
{
	const char *modified = mods_apply(flist_get_dir(view), "/", mod, for_shell);
	append_path_to_expanded(expanded, quotes, modified);

	if(expanded->data != NULL && for_shell && curr_stats.shell_type == ST_CMD)
	{
		/* XXX: why update slashes in the whole result instead of only in the just
		 *      added path? */
		internal_to_system_slashes(expanded->data);
	}
}

/* Expands content of a register considering filename-modifiers. */
static void
expand_register(str_buf_t *expanded, const reg_t *reg, const char curr_dir[],
		int quotes, const char mod[], int for_shell)
{
	int i;
	for(i = 0; i < reg->nfiles; ++i)
	{
		const char *const modified = mods_apply(reg->files[i], curr_dir, mod,
				for_shell);
		append_path_to_expanded(expanded, quotes, modified);

		if(i != reg->nfiles - 1)
		{
			str_buf_append(expanded, " ");
		}
	}

	if(expanded->data != NULL && for_shell && curr_stats.shell_type == ST_CMD)
	{
		internal_to_system_slashes(expanded->data);
	}
}

/* Expands preview parameter macro specified by the key argument. */
static void
expand_preview(str_buf_t *expanded, MacroKind kind)
{
	const preview_area_t parea = get_preview_area(curr_view);

	int param;
	switch(kind)
	{
		case MK_ph: param = parea.h; break;
		case MK_pw: param = parea.w; break;
		case MK_px: param = getbegx(parea.view->win) + parea.x; break;
		case MK_py: param = getbegy(parea.view->win) + parea.y; break;

		default:
			assert(0 && "Unhandled preview property type");
			param = 0;
			break;
	}

	char num_str[32];
	snprintf(num_str, sizeof(num_str), "%d", param);

	str_buf_append(expanded, num_str);
}

/* Applies heuristics to determine area that is going to be used for preview.
 * Returns the area. */
static preview_area_t
get_preview_area(view_t *view)
{
	view_t *const other = (view == curr_view) ? other_view : curr_view;

	if(curr_stats.preview_hint != NULL)
	{
		return *(const preview_area_t *)curr_stats.preview_hint;
	}

	view_t *source = view;
	if(curr_stats.preview.on || (!view->explore_mode && other->explore_mode))
	{
		view = other;
	}

	const preview_area_t parea = {
		.source = source,
		.view = view,
		.x = ui_qv_left(view),
		.y = ui_qv_top(view),
		.w = ui_qv_width(view),
		.h = ui_qv_height(view),
	};
	return parea;
}

/* Appends the path to the expanded string with either proper escaping or
 * quoting. */
static void
append_path_to_expanded(str_buf_t *expanded, int quotes, const char path[])
{
	if(quotes)
	{
		const char *const dquoted = enclose_in_dquotes(path, curr_stats.shell_type);
		str_buf_append(expanded, dquoted);
	}
	else
	{
		char *const escaped = posix_like_escape(path, /*type=*/0);
		if(escaped == NULL)
		{
			str_buf_fail(expanded);
			return;
		}

		str_buf_append(expanded, escaped);
		free(escaped);
	}
}

const char *
ma_get_clear_cmd(const char cmd[])
{
	macro_info_t info = find_next_macro(cmd);
	for( ; info.kind != MK_NONE; info = find_next_macro(info.end))
	{
		if(info.kind == MK_pc)
		{
			const char *clear_cmd = skip_whitespace(info.end);
			return is_null_or_empty(clear_cmd) ? NULL : clear_cmd;
		}
	}

	return NULL;
}

int
ma_contains1(const char cmd[], MacroKind kind)
{
	macro_info_t info = find_next_macro(cmd);
	for( ; info.kind != MK_NONE; info = find_next_macro(info.end))
	{
		if(info.kind == kind)
		{
			return 1;
		}
	}
	return 0;
}

int
ma_contains2(const char cmd[], MacroKind kind1, MacroKind kind2)
{
	macro_info_t info = find_next_macro(cmd);
	for( ; info.kind != MK_NONE; info = find_next_macro(info.end))
	{
		if(ONE_OF(info.kind, kind1, kind2))
		{
			return 1;
		}
	}
	return 0;
}

char *
ma_expand_custom(const char pattern[], size_t nmacros, custom_macro_t macros[],
		int with_opt)
{
	cline_t result = ma_expand_colored_custom(pattern, nmacros, macros, with_opt);
	free(result.attrs);
	return result.line;
}

cline_t
ma_expand_colored_custom(const char pattern[], size_t nmacros,
		custom_macro_t macros[], int with_opt)
{
	cline_t result = expand_custom(&pattern, nmacros, macros, with_opt, 0);
	assert(strlen(result.attrs) == utf8_strsw(result.line) && "Broken attrs!");
	return result;
}

/* Expands macros of form %x in the pattern (%% is expanded to %) according to
 * macros specification and accounting for nesting inside %[ and %].  Updates
 * *pattern pointer.  Returns colored line. */
static cline_t
expand_custom(const char **pattern, size_t nmacros, custom_macro_t macros[],
		int with_opt, int in_opt)
{
	cline_t result = cline_make();

	int nexpansions = 0;
	while(**pattern != '\0')
	{
		const char *pat = (*pattern)++;
		if(pat[0] != '%')
		{
			const char single_char[] = { *pat, '\0' };
			result.line = extend_string(result.line, single_char, &result.line_len);
		}
		else if(pat[1] == '%' || pat[1] == '\0')
		{
			result.line = extend_string(result.line, "%", &result.line_len);
			*pattern += (pat[1] == '%');
		}
		else if(pat[1] == '*')
		{
			cline_set_attr(&result, /*user_color=*/0);
			++*pattern;
		}
		else if(isdigit(pat[1]) && pat[2] == '*')
		{
			int user_color = pat[1] - '0';
			cline_set_attr(&result, user_color);
			*pattern += 2;
		}
		else if(isdigit(pat[1]) && isdigit(pat[2]) && pat[3] == '*')
		{
			int user_color = (pat[1] - '0')*10 + (pat[2] - '0');
			cline_set_attr(&result, user_color);
			*pattern += 3;
		}
		else if(with_opt && pat[1] == '[')
		{
			++*pattern;
			cline_t opt = expand_custom(pattern, nmacros, macros, with_opt, 1);
			cline_splice_attrs(&result, &opt);
			result.line = extend_string(result.line, opt.line, &result.line_len);
			nexpansions += (opt.line[0] != '\0');
			free(opt.line);
			continue;
		}
		else if(in_opt && pat[1] == ']')
		{
			++*pattern;
			if(nexpansions == 0 && result.line != NULL)
			{
				cline_clear(&result);
			}
			return result;
		}
		else
		{
			size_t i = 0U;
			while(i < nmacros && macros[i].letter != **pattern)
			{
				++i;
			}
			++*pattern;
			if(i < nmacros)
			{
				const char *value = macros[i].value;
				if(macros[i].expand_mods)
				{
					assert(is_path_absolute(macros[i].parent) &&
							"Invalid parent for mods.");
					value = mods_apply(value, macros[i].parent, *pattern, 0);

					*pattern += mods_length(*pattern);
				}

				if(!macros[i].flag)
				{
					result.line = extend_string(result.line, value, &result.line_len);
				}
				--macros[i].uses_left;
				macros[i].explicit_use = 1;
				nexpansions += (value[0] != '\0');
			}
		}
	}

	if(in_opt)
	{
		/* Unmatched %[. */
		(void)strprepend(&result.line, &result.line_len, "%[");
		(void)strprepend(&result.attrs, &result.attrs_len, "  ");
	}
	else
	{
		result.line = add_missing_macros(result.line, result.line_len, nmacros,
				macros);
		cline_finish(&result);
	}

	return result;
}

/* Ensures that the expanded string contains required number of mandatory
 * macros.  Returns reallocated expanded. */
static char *
add_missing_macros(char expanded[], size_t len, size_t nmacros,
		custom_macro_t macros[])
{
	int groups[nmacros == 0 ? 1 : nmacros];
	size_t i;

	memset(&groups, 0, sizeof(groups));
	for(i = 0U; i < nmacros; ++i)
	{
		custom_macro_t *const macro = &macros[i];
		const int group = macro->group;
		if(group >= 0)
		{
			groups[group] += macro->uses_left;
		}
	}

	for(i = 0U; i < nmacros; ++i)
	{
		custom_macro_t *const macro = &macros[i];
		const int group = macro->group;
		int *const uses_left = (group >= 0) ? &groups[group] : &macro->uses_left;
		while(*uses_left > 0)
		{
			/* Make sure we don't add spaces for nothing. */
			if(macro->value[0] != '\0')
			{
				expanded = extend_string(expanded, " ", &len);
				expanded = extend_string(expanded, macro->value, &len);
			}
			--*uses_left;
		}
	}
	return expanded;
}

int
ma_flags_present(MacroFlags flags, MacroFlags flag)
{
	if(flag == MF_NONE)
	{
		return (flags == MF_NONE);
	}
	else if(flag < MF_SECOND_SET_)
	{
		return ((flags & 0x000f) == flag);
	}
	else if(flag < MF_THIRD_SET_)
	{
		return ((flags & 0x00f0) == flag);
	}
	else if(flag < MF_FOURTH_SET_)
	{
		return ((flags & 0x0f00) == flag);
	}
	else
	{
		return ((flags & 0xf000) == flag);
	}
}

int
ma_flags_missing(MacroFlags flags, MacroFlags flag)
{
	return !ma_flags_present(flags, flag);
}

const char *
ma_flags_to_str(MacroFlags flags)
{
	switch(flags)
	{
		case MF_FIRST_SET_:
		case MF_SECOND_SET_:
		case MF_THIRD_SET_:
		case MF_FOURTH_SET_:
		case MF_NONE: return "";

		case MF_MENU_OUTPUT: return "%m";
		case MF_MENU_NAV_OUTPUT: return "%M";
		case MF_STATUSBAR_OUTPUT: return "%S";
		case MF_PREVIEW_OUTPUT: return "%q";
		case MF_CUSTOMVIEW_OUTPUT: return "%u";
		case MF_VERYCUSTOMVIEW_OUTPUT: return "%U";
		case MF_CUSTOMVIEW_IOUTPUT: return "%Iu";
		case MF_VERYCUSTOMVIEW_IOUTPUT: return "%IU";

		case MF_SPLIT: return "%s";
		case MF_SPLIT_VERT: return "%v";
		case MF_IGNORE: return "%i";
		case MF_NO_TERM_MUX: return "%n";

		case MF_KEEP_IN_FG: return "%N";

		case MF_PIPE_FILE_LIST: return "%Pl";
		case MF_PIPE_FILE_LIST_Z: return "%Pz";

		case MF_NO_CACHE: return "%pu";
	}

	assert(0 && "Unhandled MacroFlags item.");
	return "";
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
