// SPDX-License-Identifier: MIT

/*
 * Compton - a compositor for X11
 *
 * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
 *
 * Copyright (c) 2011-2013, Christopher Jeffrey
 * See LICENSE-mit for more information.
 *
 */

#include <ctype.h>
#include <fnmatch.h>
#include <stdio.h>
#include <string.h>

// libpcre
#ifdef CONFIG_REGEX_PCRE
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>

#endif

#include <X11/Xlib.h>
#include <xcb/xcb.h>

#include "atom.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "log.h"
#include "string_utils.h"
#include "utils.h"
#include "win.h"
#include "x.h"

#include "c2.h"

#pragma GCC diagnostic error "-Wunused-parameter"

#define C2_MAX_LEVELS 10

typedef struct _c2_b c2_b_t;
typedef struct _c2_l c2_l_t;

/// Pointer to a condition tree.
typedef struct {
	bool isbranch : 1;
	union {
		c2_b_t *b;
		c2_l_t *l;
	};
} c2_ptr_t;

/// Initializer for c2_ptr_t.
#define C2_PTR_INIT                                                                      \
	{ .isbranch = false, .l = NULL, }

static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;

/// Operator of a branch element.
typedef enum {
	C2_B_OUNDEFINED,
	C2_B_OAND,
	C2_B_OOR,
	C2_B_OXOR,
} c2_b_op_t;

/// Structure for branch element in a window condition
struct _c2_b {
	bool neg : 1;
	c2_b_op_t op;
	c2_ptr_t opr1;
	c2_ptr_t opr2;
};

/// Initializer for c2_b_t.
#define C2_B_INIT                                                                        \
	{ .neg = false, .op = C2_B_OUNDEFINED, .opr1 = C2_PTR_INIT, .opr2 = C2_PTR_INIT, }

/// Structure for leaf element in a window condition
struct _c2_l {
	bool neg : 1;
	enum {
		C2_L_OEXISTS,
		C2_L_OEQ,
		C2_L_OGT,
		C2_L_OGTEQ,
		C2_L_OLT,
		C2_L_OLTEQ,
	} op : 3;
	enum {
		C2_L_MEXACT,
		C2_L_MSTART,
		C2_L_MCONTAINS,
		C2_L_MWILDCARD,
		C2_L_MPCRE,
	} match : 3;
	bool match_ignorecase : 1;
	char *tgt;
	xcb_atom_t tgtatom;
	bool tgt_onframe;
	int index;
	enum {
		C2_L_PUNDEFINED = -1,
		C2_L_PID = 0,
		C2_L_PX,
		C2_L_PY,
		C2_L_PX2,
		C2_L_PY2,
		C2_L_PWIDTH,
		C2_L_PHEIGHT,
		C2_L_PWIDTHB,
		C2_L_PHEIGHTB,
		C2_L_PBDW,
		C2_L_PFULLSCREEN,
		C2_L_POVREDIR,
		C2_L_PARGB,
		C2_L_PFOCUSED,
		C2_L_PWMWIN,
		C2_L_PBSHAPED,
		C2_L_PROUNDED,
		C2_L_PCLIENT,
		C2_L_PWINDOWTYPE,
		C2_L_PLEADER,
		C2_L_PNAME,
		C2_L_PCLASSG,
		C2_L_PCLASSI,
		C2_L_PROLE,
	} predef;
	enum c2_l_type {
		C2_L_TUNDEFINED,
		C2_L_TSTRING,
		C2_L_TCARDINAL,
		C2_L_TWINDOW,
		C2_L_TATOM,
		C2_L_TDRAWABLE,
	} type;
	int format;
	enum {
		C2_L_PTUNDEFINED,
		C2_L_PTSTRING,
		C2_L_PTINT,
	} ptntype;
	char *ptnstr;
	long ptnint;
#ifdef CONFIG_REGEX_PCRE
	pcre2_code *regex_pcre;
	pcre2_match_data *regex_pcre_match;
#endif
};

/// Initializer for c2_l_t.
#define C2_L_INIT                                                                           \
	{                                                                                   \
		.neg = false, .op = C2_L_OEXISTS, .match = C2_L_MEXACT,                     \
		.match_ignorecase = false, .tgt = NULL, .tgtatom = 0, .tgt_onframe = false, \
		.predef = C2_L_PUNDEFINED, .index = 0, .type = C2_L_TUNDEFINED,             \
		.format = 0, .ptntype = C2_L_PTUNDEFINED, .ptnstr = NULL, .ptnint = 0,      \
	}

static const c2_l_t leaf_def = C2_L_INIT;

/// Linked list type of conditions.
struct _c2_lptr {
	c2_ptr_t ptr;
	void *data;
	struct _c2_lptr *next;
};

/// Initializer for c2_lptr_t.
#define C2_LPTR_INIT                                                                     \
	{ .ptr = C2_PTR_INIT, .data = NULL, .next = NULL, }

/// Structure representing a predefined target.
typedef struct {
	const char *name;
	enum c2_l_type type;
	int format;
} c2_predef_t;

// Predefined targets.
static const c2_predef_t C2_PREDEFS[] = {
    [C2_L_PID] = {"id", C2_L_TCARDINAL, 0},
    [C2_L_PX] = {"x", C2_L_TCARDINAL, 0},
    [C2_L_PY] = {"y", C2_L_TCARDINAL, 0},
    [C2_L_PX2] = {"x2", C2_L_TCARDINAL, 0},
    [C2_L_PY2] = {"y2", C2_L_TCARDINAL, 0},
    [C2_L_PWIDTH] = {"width", C2_L_TCARDINAL, 0},
    [C2_L_PHEIGHT] = {"height", C2_L_TCARDINAL, 0},
    [C2_L_PWIDTHB] = {"widthb", C2_L_TCARDINAL, 0},
    [C2_L_PHEIGHTB] = {"heightb", C2_L_TCARDINAL, 0},
    [C2_L_PBDW] = {"border_width", C2_L_TCARDINAL, 0},
    [C2_L_PFULLSCREEN] = {"fullscreen", C2_L_TCARDINAL, 0},
    [C2_L_POVREDIR] = {"override_redirect", C2_L_TCARDINAL, 0},
    [C2_L_PARGB] = {"argb", C2_L_TCARDINAL, 0},
    [C2_L_PFOCUSED] = {"focused", C2_L_TCARDINAL, 0},
    [C2_L_PWMWIN] = {"wmwin", C2_L_TCARDINAL, 0},
    [C2_L_PBSHAPED] = {"bounding_shaped", C2_L_TCARDINAL, 0},
    [C2_L_PROUNDED] = {"rounded_corners", C2_L_TCARDINAL, 0},
    [C2_L_PCLIENT] = {"client", C2_L_TWINDOW, 0},
    [C2_L_PWINDOWTYPE] = {"window_type", C2_L_TSTRING, 0},
    [C2_L_PLEADER] = {"leader", C2_L_TWINDOW, 0},
    [C2_L_PNAME] = {"name", C2_L_TSTRING, 0},
    [C2_L_PCLASSG] = {"class_g", C2_L_TSTRING, 0},
    [C2_L_PCLASSI] = {"class_i", C2_L_TSTRING, 0},
    [C2_L_PROLE] = {"role", C2_L_TSTRING, 0},
};

/**
 * Get the numeric property value from a win_prop_t.
 */
static inline long winprop_get_int(winprop_t prop, size_t index) {
	long tgt = 0;

	if (!prop.nitems || index >= prop.nitems) {
		return 0;
	}

	switch (prop.format) {
	case 8: tgt = *(prop.p8 + index); break;
	case 16: tgt = *(prop.p16 + index); break;
	case 32: tgt = *(prop.p32 + index); break;
	default: assert(0); break;
	}

	return tgt;
}

/**
 * Compare next word in a string with another string.
 */
static inline int strcmp_wd(const char *needle, const char *src) {
	int ret = mstrncmp(needle, src);
	if (ret) {
		return ret;
	}

	char c = src[strlen(needle)];
	if (isalnum((unsigned char)c) || '_' == c) {
		return 1;
	}

	return 0;
}

/**
 * Return whether a c2_ptr_t is empty.
 */
static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) {
	return !(p.isbranch ? (bool)p.b : (bool)p.l);
}

/**
 * Reset a c2_ptr_t.
 */
static inline void c2_ptr_reset(c2_ptr_t *pp) {
	if (pp) {
		memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
	}
}

/**
 * Combine two condition trees.
 */
static inline c2_ptr_t c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) {
	c2_ptr_t p = {.isbranch = true, .b = NULL};
	p.b = cmalloc(c2_b_t);

	p.b->neg = false;
	p.b->op = op;
	p.b->opr1 = p1;
	p.b->opr2 = p2;

	return p;
}

/**
 * Get the precedence value of a condition branch operator.
 */
static inline int c2h_b_opp(c2_b_op_t op) {
	switch (op) {
	case C2_B_OAND: return 2;
	case C2_B_OOR: return 1;
	case C2_B_OXOR: return 1;
	default: break;
	}

	assert(0);
	return 0;
}

/**
 * Compare precedence of two condition branch operators.
 *
 * Associativity is left-to-right, forever.
 *
 * @return positive number if op1 > op2, 0 if op1 == op2 in precedence,
 *         negative number otherwise
 */
static inline int c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) {
	return c2h_b_opp(op1) - c2h_b_opp(op2);
}

static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level);

static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult);

static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult);

static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult);

static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult);

static void c2_free(c2_ptr_t p);

/**
 * Wrapper of c2_free().
 */
static inline void c2_freep(c2_ptr_t *pp) {
	if (pp) {
		c2_free(*pp);
		c2_ptr_reset(pp);
	}
}

static const char *c2h_dump_str_tgt(const c2_l_t *pleaf);

static const char *c2h_dump_str_type(const c2_l_t *pleaf);

static void attr_unused c2_dump(c2_ptr_t p);

static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf);

static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond);

/**
 * Parse a condition string.
 */
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) {
	if (!pattern) {
		return NULL;
	}

	// Parse the pattern
	c2_ptr_t result = C2_PTR_INIT;
	int offset = -1;

	if (strlen(pattern) >= 2 && ':' == pattern[1]) {
		offset = c2_parse_legacy(pattern, 0, &result);
	} else {
		offset = c2_parse_grp(pattern, 0, &result, 0);
	}

	if (offset < 0) {
		c2_freep(&result);
		return NULL;
	}

	// Insert to pcondlst
	{
		static const c2_lptr_t lptr_def = C2_LPTR_INIT;
		auto plptr = cmalloc(c2_lptr_t);
		memcpy(plptr, &lptr_def, sizeof(c2_lptr_t));
		plptr->ptr = result;
		plptr->data = data;
		if (pcondlst) {
			plptr->next = *pcondlst;
			*pcondlst = plptr;
		}

#ifdef DEBUG_C2
		log_trace("(\"%s\"): ", pattern);
		c2_dump(plptr->ptr);
		putchar('\n');
#endif

		return plptr;
	}
}

#define c2_error(format, ...)                                                                \
	do {                                                                                 \
		log_error("Pattern \"%s\" pos %d: " format, pattern, offset, ##__VA_ARGS__); \
		goto fail;                                                                   \
	} while (0)

// TODO(yshui) Not a very good macro, should probably be a function
#define C2H_SKIP_SPACES()                                                                \
	{                                                                                \
		while (isspace((unsigned char)pattern[offset]))                          \
			++offset;                                                        \
	}

/**
 * Parse a group in condition string.
 *
 * @return offset of next character in string
 */
static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) {
	// Check for recursion levels
	if (level > C2_MAX_LEVELS) {
		c2_error("Exceeded maximum recursion levels.");
	}

	if (!pattern) {
		return -1;
	}

	// Expected end character
	const char endchar = (offset ? ')' : '\0');

	// We use a system that a maximum of 2 elements are kept. When we find
	// the third element, we combine the elements according to operator
	// precedence. This design limits operators to have at most two-levels
	// of precedence and fixed left-to-right associativity.

	// For storing branch operators. ops[0] is actually unused
	c2_b_op_t ops[3] = {};
	// For storing elements
	c2_ptr_t eles[2] = {C2_PTR_INIT, C2_PTR_INIT};
	// Index of next free element slot in eles
	int elei = 0;
	// Pointer to the position of next element
	c2_ptr_t *pele = eles;
	// Negation flag of next operator
	bool neg = false;
	// Whether we are expecting an element immediately, is true at first, or
	// after encountering a logical operator
	bool next_expected = true;

	// Parse the pattern character-by-character
	for (; pattern[offset]; ++offset) {
		assert(elei <= 2);

		// Jump over spaces
		if (isspace((unsigned char)pattern[offset])) {
			continue;
		}

		// Handle end of group
		if (')' == pattern[offset]) {
			break;
		}

		// Handle "!"
		if ('!' == pattern[offset]) {
			if (!next_expected) {
				c2_error("Unexpected \"!\".");
			}

			neg = !neg;
			continue;
		}

		// Handle AND and OR
		if ('&' == pattern[offset] || '|' == pattern[offset]) {
			if (next_expected) {
				c2_error("Unexpected logical operator.");
			}

			next_expected = true;
			if (!mstrncmp("&&", pattern + offset)) {
				ops[elei] = C2_B_OAND;
				++offset;
			} else if (!mstrncmp("||", pattern + offset)) {
				ops[elei] = C2_B_OOR;
				++offset;
			} else {
				c2_error("Illegal logical operator.");
			}

			continue;
		}

		// Parsing an element
		if (!next_expected) {
			c2_error("Unexpected expression.");
		}

		assert(!elei || ops[elei]);

		// If we are out of space
		if (2 == elei) {
			--elei;
			// If the first operator has higher or equal precedence, combine
			// the first two elements
			if (c2h_b_opcmp(ops[1], ops[2]) >= 0) {
				eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]);
				c2_ptr_reset(&eles[1]);
				pele = &eles[elei];
				ops[1] = ops[2];
			}
			// Otherwise, combine the second and the incoming one
			else {
				eles[1] = c2h_comb_tree(ops[2], eles[1], C2_PTR_NULL);
				assert(eles[1].isbranch);
				pele = &eles[1].b->opr2;
			}
			// The last operator always needs to be reset
			ops[2] = C2_B_OUNDEFINED;
		}

		// It's a subgroup if it starts with '('
		if ('(' == pattern[offset]) {
			if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0) {
				goto fail;
			}
		}
		// Otherwise it's a leaf
		else {
			if ((offset = c2_parse_target(pattern, offset, pele)) < 0) {
				goto fail;
			}

			assert(!pele->isbranch && !c2_ptr_isempty(*pele));

			if ((offset = c2_parse_op(pattern, offset, pele)) < 0) {
				goto fail;
			}

			if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0) {
				goto fail;
			}
		}
		// Decrement offset -- we will increment it in loop update
		--offset;

		// Apply negation
		if (neg) {
			neg = false;
			if (pele->isbranch) {
				pele->b->neg = !pele->b->neg;
			} else {
				pele->l->neg = !pele->l->neg;
			}
		}

		next_expected = false;
		++elei;
		pele = &eles[elei];
	}

	// Wrong end character?
	if (pattern[offset] && !endchar) {
		c2_error("Expected end of string but found '%c'.", pattern[offset]);
	}
	if (!pattern[offset] && endchar) {
		c2_error("Expected '%c' but found end of string.", endchar);
	}

	// Handle end of group
	if (!elei) {
		c2_error("Empty group.");
	} else if (next_expected) {
		c2_error("Missing rule before end of group.");
	} else if (elei > 1) {
		assert(2 == elei);
		assert(ops[1]);
		eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]);
		c2_ptr_reset(&eles[1]);
	}

	*presult = eles[0];

	if (')' == pattern[offset]) {
		++offset;
	}

	return offset;

fail:
	c2_freep(&eles[0]);
	c2_freep(&eles[1]);

	return -1;
}

/**
 * Parse the target part of a rule.
 */
static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) {
	// Initialize leaf
	presult->isbranch = false;
	presult->l = cmalloc(c2_l_t);

	c2_l_t *const pleaf = presult->l;
	memcpy(pleaf, &leaf_def, sizeof(c2_l_t));

	// Parse negation marks
	while ('!' == pattern[offset]) {
		pleaf->neg = !pleaf->neg;
		++offset;
		C2H_SKIP_SPACES();
	}

	// Copy target name out
	int tgtlen = 0;
	for (; pattern[offset] && (isalnum((unsigned char)pattern[offset]) ||
	                           '_' == pattern[offset] || '.' == pattern[offset]);
	     ++offset) {
		++tgtlen;
	}
	if (!tgtlen) {
		c2_error("Empty target.");
	}
	pleaf->tgt = strndup(&pattern[offset - tgtlen], (size_t)tgtlen);

	// Check for predefined targets
	static const int npredefs = (int)(sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0]));
	for (int i = 0; i < npredefs; ++i) {
		if (!strcmp(C2_PREDEFS[i].name, pleaf->tgt)) {
			pleaf->predef = i;
			pleaf->type = C2_PREDEFS[i].type;
			pleaf->format = C2_PREDEFS[i].format;
			break;
		}
	}

	C2H_SKIP_SPACES();

	// Parse target-on-frame flag
	if ('@' == pattern[offset]) {
		pleaf->tgt_onframe = true;
		++offset;
		C2H_SKIP_SPACES();
	}

	// Parse index
	if ('[' == pattern[offset]) {
		if (pleaf->predef != C2_L_PUNDEFINED) {
			c2_error("Predefined targets can't have index.");
		}

		offset++;

		C2H_SKIP_SPACES();

		long index = -1;
		const char *endptr = NULL;

		if ('*' == pattern[offset]) {
			index = -1;
			endptr = pattern + offset + 1;
		} else {
			index = strtol(pattern + offset, (char **)&endptr, 0);
			if (index < 0) {
				c2_error("Index number invalid.");
			}
		}

		if (!endptr || pattern + offset == endptr) {
			c2_error("No index number found after bracket.");
		}

		pleaf->index = to_int_checked(index);
		offset = to_int_checked(endptr - pattern);

		C2H_SKIP_SPACES();

		if (pattern[offset] != ']') {
			c2_error("Index end marker not found.");
		}

		++offset;

		C2H_SKIP_SPACES();
	}

	// Parse target type and format
	if (':' == pattern[offset]) {
		++offset;
		C2H_SKIP_SPACES();

		// Look for format
		bool hasformat = false;
		long format = 0;
		{
			char *endptr = NULL;
			format = strtol(pattern + offset, &endptr, 0);
			assert(endptr);
			if ((hasformat = (endptr && endptr != pattern + offset))) {
				offset = to_int_checked(endptr - pattern);
			}
			C2H_SKIP_SPACES();
		}

		// Look for type
		enum c2_l_type type = C2_L_TUNDEFINED;
		switch (pattern[offset]) {
		case 'w': type = C2_L_TWINDOW; break;
		case 'd': type = C2_L_TDRAWABLE; break;
		case 'c': type = C2_L_TCARDINAL; break;
		case 's': type = C2_L_TSTRING; break;
		case 'a': type = C2_L_TATOM; break;
		default: c2_error("Invalid type character.");
		}

		if (type) {
			if (pleaf->predef != C2_L_PUNDEFINED) {
				log_warn("Type specified for a default target "
				         "will be ignored.");
			} else {
				if (pleaf->type && type != pleaf->type) {
					log_warn("Default type overridden on "
					         "target.");
				}
				pleaf->type = type;
			}
		}

		offset++;
		C2H_SKIP_SPACES();

		// Default format
		if (!pleaf->format) {
			switch (pleaf->type) {
			case C2_L_TWINDOW:
			case C2_L_TDRAWABLE:
			case C2_L_TATOM: pleaf->format = 32; break;
			case C2_L_TSTRING: pleaf->format = 8; break;
			default: break;
			}
		}

		// Write format
		if (hasformat) {
			if (pleaf->predef != C2_L_PUNDEFINED) {
				log_warn("Format \"%ld\" specified on a default target "
				         "will be ignored.",
				         format);
			} else if (pleaf->type == C2_L_TSTRING) {
				log_warn("Format \"%ld\" specified on a string target "
				         "will be ignored.",
				         format);
			} else {
				if (pleaf->format && pleaf->format != format) {
					log_warn("Default format %d overridden on "
					         "target.",
					         pleaf->format);
				}
				pleaf->format = to_int_checked(format);
			}
		}
	}

	if (!pleaf->type) {
		c2_error("Target type cannot be determined.");
	}

	// if (!pleaf->predef && !pleaf->format && C2_L_TSTRING != pleaf->type)
	//   c2_error("Target format cannot be determined.");

	if (pleaf->format && 8 != pleaf->format && 16 != pleaf->format && 32 != pleaf->format) {
		c2_error("Invalid format.");
	}

	return offset;

fail:
	return -1;
}

/**
 * Parse the operator part of a leaf.
 */
static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) {
	c2_l_t *const pleaf = presult->l;

	// Parse negation marks
	C2H_SKIP_SPACES();
	while ('!' == pattern[offset]) {
		pleaf->neg = !pleaf->neg;
		++offset;
		C2H_SKIP_SPACES();
	}

	// Parse qualifiers
	if ('*' == pattern[offset] || '^' == pattern[offset] || '%' == pattern[offset] ||
	    '~' == pattern[offset]) {
		switch (pattern[offset]) {
		case '*': pleaf->match = C2_L_MCONTAINS; break;
		case '^': pleaf->match = C2_L_MSTART; break;
		case '%': pleaf->match = C2_L_MWILDCARD; break;
		case '~': pleaf->match = C2_L_MPCRE; break;
		default: assert(0);
		}
		++offset;
		C2H_SKIP_SPACES();
	}

	// Parse flags
	while ('?' == pattern[offset]) {
		pleaf->match_ignorecase = true;
		++offset;
		C2H_SKIP_SPACES();
	}

	// Parse operator
	while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) {
		if ('=' == pattern[offset] && C2_L_OGT == pleaf->op) {
			pleaf->op = C2_L_OGTEQ;
		} else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op) {
			pleaf->op = C2_L_OLTEQ;
		} else if (pleaf->op) {
			c2_error("Duplicate operator.");
		} else {
			switch (pattern[offset]) {
			case '=': pleaf->op = C2_L_OEQ; break;
			case '>': pleaf->op = C2_L_OGT; break;
			case '<': pleaf->op = C2_L_OLT; break;
			default: assert(0);
			}
		}
		++offset;
		C2H_SKIP_SPACES();
	}

	// Check for problems
	if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase)) {
		c2_error("Exists/greater-than/less-than operators cannot have a "
		         "qualifier.");
	}

	return offset;

fail:
	return -1;
}

/**
 * Parse the pattern part of a leaf.
 */
static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) {
	c2_l_t *const pleaf = presult->l;

	// Exists operator cannot have pattern
	if (!pleaf->op) {
		return offset;
	}

	C2H_SKIP_SPACES();

	char *endptr = NULL;
	if (!strcmp_wd("true", &pattern[offset])) {
		pleaf->ptntype = C2_L_PTINT;
		pleaf->ptnint = true;
		offset += 4;        // length of "true";
	} else if (!strcmp_wd("false", &pattern[offset])) {
		pleaf->ptntype = C2_L_PTINT;
		pleaf->ptnint = false;
		offset += 5;        // length of "false";
	} else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0),
	           pattern + offset != endptr) {
		pleaf->ptntype = C2_L_PTINT;
		offset = to_int_checked(endptr - pattern);
		// Make sure we are stopping at the end of a word
		if (isalnum((unsigned char)pattern[offset])) {
			c2_error("Trailing characters after a numeric pattern.");
		}
	} else {
		// Parse string patterns
		bool raw = false;
		char delim = '\0';

		// String flags
		if (tolower((unsigned char)pattern[offset]) == 'r') {
			raw = true;
			++offset;
			C2H_SKIP_SPACES();
		}

		if (raw == true) {
			log_warn("Raw string patterns has been deprecated. pos %d", offset);
		}

		// Check for delimiters
		if (pattern[offset] == '\"' || pattern[offset] == '\'') {
			pleaf->ptntype = C2_L_PTSTRING;
			delim = pattern[offset];
			++offset;
		}

		if (pleaf->ptntype != C2_L_PTSTRING) {
			c2_error("Invalid pattern type.");
		}

		// Parse the string now
		// We can't determine the length of the pattern, so we use the length
		// to the end of the pattern string -- currently escape sequences
		// cannot be converted to a string longer than itself.
		auto tptnstr = ccalloc((strlen(pattern + offset) + 1), char);
		char *ptptnstr = tptnstr;
		pleaf->ptnstr = tptnstr;
		for (; pattern[offset] && delim != pattern[offset]; ++offset) {
			// Handle escape sequences if it's not a raw string
			if ('\\' == pattern[offset] && !raw) {
				switch (pattern[++offset]) {
				case '\\': *(ptptnstr++) = '\\'; break;
				case '\'': *(ptptnstr++) = '\''; break;
				case '\"': *(ptptnstr++) = '\"'; break;
				case 'a': *(ptptnstr++) = '\a'; break;
				case 'b': *(ptptnstr++) = '\b'; break;
				case 'f': *(ptptnstr++) = '\f'; break;
				case 'n': *(ptptnstr++) = '\n'; break;
				case 'r': *(ptptnstr++) = '\r'; break;
				case 't': *(ptptnstr++) = '\t'; break;
				case 'v': *(ptptnstr++) = '\v'; break;
				case 'o':
				case 'x': {
					scoped_charp tstr = strndup(pattern + offset + 1, 2);
					char *pstr = NULL;
					long val = strtol(
					    tstr, &pstr, ('o' == pattern[offset] ? 8 : 16));
					if (pstr != &tstr[2] || val <= 0) {
						c2_error("Invalid octal/hex escape "
						         "sequence.");
					}
					*(ptptnstr++) = to_char_checked(val);
					offset += 2;
					break;
				}
				default: c2_error("Invalid escape sequence.");
				}
			} else {
				*(ptptnstr++) = pattern[offset];
			}
		}
		if (!pattern[offset]) {
			c2_error("Premature end of pattern string.");
		}
		++offset;
		*ptptnstr = '\0';
		pleaf->ptnstr = strdup(tptnstr);
		free(tptnstr);
	}

	C2H_SKIP_SPACES();

	if (!pleaf->ptntype) {
		c2_error("Invalid pattern type.");
	}

	// Check if the type is correct
	if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) &&
	       C2_L_PTSTRING == pleaf->ptntype) ||
	      ((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type ||
	        C2_L_TDRAWABLE == pleaf->type) &&
	       C2_L_PTINT == pleaf->ptntype))) {
		c2_error("Pattern type incompatible with target type.");
	}

	if (C2_L_PTINT == pleaf->ptntype && pleaf->match) {
		c2_error("Integer/boolean pattern cannot have operator qualifiers.");
	}

	if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase) {
		c2_error("Integer/boolean pattern cannot have flags.");
	}

	if (C2_L_PTSTRING == pleaf->ptntype &&
	    (C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op ||
	     C2_L_OLTEQ == pleaf->op)) {
		c2_error("String pattern cannot have an arithmetic operator.");
	}

	return offset;

fail:
	return -1;
}

/**
 * Parse a condition with legacy syntax.
 */
static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) {
	if (strlen(pattern + offset) < 4 || pattern[offset + 1] != ':' ||
	    !strchr(pattern + offset + 2, ':')) {
		c2_error("Legacy parser: Invalid format.");
	}

	// Allocate memory for new leaf
	auto pleaf = cmalloc(c2_l_t);
	presult->isbranch = false;
	presult->l = pleaf;
	memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
	pleaf->type = C2_L_TSTRING;
	pleaf->op = C2_L_OEQ;
	pleaf->ptntype = C2_L_PTSTRING;

	// Determine the pattern target
#define TGTFILL(pdefid)                                                                  \
	(pleaf->predef = (pdefid), pleaf->type = C2_PREDEFS[pdefid].type,                \
	 pleaf->format = C2_PREDEFS[pdefid].format)
	switch (pattern[offset]) {
	case 'n': TGTFILL(C2_L_PNAME); break;
	case 'i': TGTFILL(C2_L_PCLASSI); break;
	case 'g': TGTFILL(C2_L_PCLASSG); break;
	case 'r': TGTFILL(C2_L_PROLE); break;
	default: c2_error("Target \"%c\" invalid.\n", pattern[offset]);
	}
#undef TGTFILL

	offset += 2;

	// Determine the match type
	switch (pattern[offset]) {
	case 'e': pleaf->match = C2_L_MEXACT; break;
	case 'a': pleaf->match = C2_L_MCONTAINS; break;
	case 's': pleaf->match = C2_L_MSTART; break;
	case 'w': pleaf->match = C2_L_MWILDCARD; break;
	case 'p': pleaf->match = C2_L_MPCRE; break;
	default: c2_error("Type \"%c\" invalid.\n", pattern[offset]);
	}
	++offset;

	// Determine the pattern flags
	while (':' != pattern[offset]) {
		switch (pattern[offset]) {
		case 'i': pleaf->match_ignorecase = true; break;
		default: c2_error("Flag \"%c\" invalid.", pattern[offset]);
		}
		++offset;
	}
	++offset;

	// Copy the pattern
	pleaf->ptnstr = strdup(pattern + offset);

	return offset;

fail:
	return -1;
}

#undef c2_error

/**
 * Do postprocessing on a condition leaf.
 */
static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) {
	// Give a pattern type to a leaf with exists operator, if needed
	if (C2_L_OEXISTS == pleaf->op && !pleaf->ptntype) {
		pleaf->ptntype = (C2_L_TSTRING == pleaf->type ? C2_L_PTSTRING : C2_L_PTINT);
	}

	// Get target atom if it's not a predefined one
	if (pleaf->predef == C2_L_PUNDEFINED) {
		pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt);
		if (!pleaf->tgtatom) {
			log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
			return false;
		}
	}

	// Insert target Atom into atom track list
	if (pleaf->tgtatom) {
		bool found = false;
		for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
			if (pleaf->tgtatom == platom->atom) {
				found = true;
				break;
			}
		}
		if (!found) {
			auto pnew = cmalloc(latom_t);
			pnew->next = ps->track_atom_lst;
			pnew->atom = pleaf->tgtatom;
			ps->track_atom_lst = pnew;
		}
	}

	// Warn about lower case characters in target name
	if (pleaf->predef == C2_L_PUNDEFINED) {
		for (const char *pc = pleaf->tgt; *pc; ++pc) {
			if (islower((unsigned char)*pc)) {
				log_warn("Lowercase character in target name \"%s\".",
				         pleaf->tgt);
				break;
			}
		}
	}

	// PCRE patterns
	if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) {
#ifdef CONFIG_REGEX_PCRE
		int errorcode = 0;
		PCRE2_SIZE erroffset = 0;
		unsigned int options = 0;

		// Ignore case flag
		if (pleaf->match_ignorecase) {
			options |= PCRE2_CASELESS;
		}

		// Compile PCRE expression
		pleaf->regex_pcre =
		    pcre2_compile((PCRE2_SPTR)pleaf->ptnstr, PCRE2_ZERO_TERMINATED,
		                  options, &errorcode, &erroffset, NULL);
		if (pleaf->regex_pcre == NULL) {
			PCRE2_UCHAR buffer[256];
			pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
			log_error("Pattern \"%s\": PCRE regular expression "
			          "parsing "
			          "failed on "
			          "offset %zu: %s",
			          pleaf->ptnstr, erroffset, buffer);
			return false;
		}
		pleaf->regex_pcre_match =
		    pcre2_match_data_create_from_pattern(pleaf->regex_pcre, NULL);

		// Free the target string
		// free(pleaf->tgt);
		// pleaf->tgt = NULL;
#else
		log_error("PCRE regular expression support not compiled in.");
		return false;
#endif
	}

	return true;
}

static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) {
	if (!node.isbranch) {
		return c2_l_postprocess(ps, node.l);
	}
	if (!c2_tree_postprocess(ps, node.b->opr1)) {
		return false;
	}
	return c2_tree_postprocess(ps, node.b->opr2);
}

bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) {
	c2_lptr_t *head = list;
	while (head) {
		if (!c2_tree_postprocess(ps, head->ptr)) {
			return false;
		}
		head = head->next;
	}
	return true;
}
/**
 * Free a condition tree.
 */
static void c2_free(c2_ptr_t p) {
	// For a branch element
	if (p.isbranch) {
		c2_b_t *const pbranch = p.b;

		if (!pbranch) {
			return;
		}

		c2_free(pbranch->opr1);
		c2_free(pbranch->opr2);
		free(pbranch);
	}
	// For a leaf element
	else {
		c2_l_t *const pleaf = p.l;

		if (!pleaf) {
			return;
		}

		free(pleaf->tgt);
		free(pleaf->ptnstr);
#ifdef CONFIG_REGEX_PCRE
		pcre2_code_free(pleaf->regex_pcre);
		pcre2_match_data_free(pleaf->regex_pcre_match);
#endif
		free(pleaf);
	}
}

/**
 * Free a condition tree in c2_lptr_t.
 */
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) {
	if (!lp) {
		return NULL;
	}

	c2_lptr_t *pnext = lp->next;
	if (f) {
		f(lp->data);
	}
	lp->data = NULL;
	c2_free(lp->ptr);
	free(lp);

	return pnext;
}

/**
 * Get a string representation of a rule target.
 */
static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) {
	if (pleaf->predef != C2_L_PUNDEFINED) {
		return C2_PREDEFS[pleaf->predef].name;
	}
	return pleaf->tgt;
}

/**
 * Get a string representation of a target.
 */
static const char *c2h_dump_str_type(const c2_l_t *pleaf) {
	switch (pleaf->type) {
	case C2_L_TWINDOW: return "w";
	case C2_L_TDRAWABLE: return "d";
	case C2_L_TCARDINAL: return "c";
	case C2_L_TSTRING: return "s";
	case C2_L_TATOM: return "a";
	case C2_L_TUNDEFINED: break;
	}

	return NULL;
}

/**
 * Dump a condition tree.
 */
static void c2_dump(c2_ptr_t p) {
	// For a branch
	if (p.isbranch) {
		const c2_b_t *const pbranch = p.b;

		if (!pbranch) {
			return;
		}

		if (pbranch->neg) {
			putchar('!');
		}

		printf("(");
		c2_dump(pbranch->opr1);

		switch (pbranch->op) {
		case C2_B_OAND: printf(" && "); break;
		case C2_B_OOR: printf(" || "); break;
		case C2_B_OXOR: printf(" XOR "); break;
		default: assert(0); break;
		}

		c2_dump(pbranch->opr2);
		printf(") ");
	}
	// For a leaf
	else {
		const c2_l_t *const pleaf = p.l;

		if (!pleaf) {
			return;
		}

		if (C2_L_OEXISTS == pleaf->op && pleaf->neg) {
			putchar('!');
		}

		// Print target name, type, and format
		{
			printf("%s", c2h_dump_str_tgt(pleaf));
			if (pleaf->tgt_onframe) {
				putchar('@');
			}
			if (pleaf->predef == C2_L_PUNDEFINED) {
				if (pleaf->index < 0) {
					printf("[*]");
				} else {
					printf("[%d]", pleaf->index);
				}
			}
			printf(":%d%s", pleaf->format, c2h_dump_str_type(pleaf));
		}

		// Print operator
		putchar(' ');

		if (C2_L_OEXISTS != pleaf->op && pleaf->neg) {
			putchar('!');
		}

		switch (pleaf->match) {
		case C2_L_MEXACT: break;
		case C2_L_MCONTAINS: putchar('*'); break;
		case C2_L_MSTART: putchar('^'); break;
		case C2_L_MPCRE: putchar('~'); break;
		case C2_L_MWILDCARD: putchar('%'); break;
		}

		if (pleaf->match_ignorecase) {
			putchar('?');
		}

		switch (pleaf->op) {
		case C2_L_OEXISTS: break;
		case C2_L_OEQ: fputs("=", stdout); break;
		case C2_L_OGT: fputs(">", stdout); break;
		case C2_L_OGTEQ: fputs(">=", stdout); break;
		case C2_L_OLT: fputs("<", stdout); break;
		case C2_L_OLTEQ: fputs("<=", stdout); break;
		}

		if (C2_L_OEXISTS == pleaf->op) {
			return;
		}

		// Print pattern
		putchar(' ');
		switch (pleaf->ptntype) {
		case C2_L_PTINT: printf("%ld", pleaf->ptnint); break;
		case C2_L_PTSTRING:
			// TODO(yshui) Escape string before printing out?
			printf("\"%s\"", pleaf->ptnstr);
			break;
		default: assert(0); break;
		}
	}
}

/**
 * Get the type atom of a condition.
 */
static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) {
	switch (pleaf->type) {
	case C2_L_TCARDINAL: return XCB_ATOM_CARDINAL;
	case C2_L_TWINDOW: return XCB_ATOM_WINDOW;
	case C2_L_TSTRING: return XCB_ATOM_STRING;
	case C2_L_TATOM: return XCB_ATOM_ATOM;
	case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE;
	default: assert(0); break;
	}
	unreachable();
}

/**
 * Match a window against a single leaf window condition.
 *
 * For internal use.
 */
static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w,
                                      const c2_l_t *pleaf, bool *pres, bool *perr) {
	assert(pleaf);

	const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->base.id);

	// Return if wid is missing
	if (pleaf->predef == C2_L_PUNDEFINED && !wid) {
		return;
	}

	const int idx = (pleaf->index < 0 ? 0 : pleaf->index);

	switch (pleaf->ptntype) {
	// Deal with integer patterns
	case C2_L_PTINT: {
		long long *targets = NULL;
		long long *targets_free = NULL;
		size_t ntargets = 0;

		// Get the value
		// A predefined target
		long long predef_target = 0;
		if (pleaf->predef != C2_L_PUNDEFINED) {
			*perr = false;
			switch (pleaf->predef) {
			case C2_L_PID: predef_target = wid; break;
			case C2_L_PX: predef_target = w->g.x; break;
			case C2_L_PY: predef_target = w->g.y; break;
			case C2_L_PX2: predef_target = w->g.x + w->widthb; break;
			case C2_L_PY2: predef_target = w->g.y + w->heightb; break;
			case C2_L_PWIDTH: predef_target = w->g.width; break;
			case C2_L_PHEIGHT: predef_target = w->g.height; break;
			case C2_L_PWIDTHB: predef_target = w->widthb; break;
			case C2_L_PHEIGHTB: predef_target = w->heightb; break;
			case C2_L_PBDW: predef_target = w->g.border_width; break;
			case C2_L_PFULLSCREEN:
				predef_target = win_is_fullscreen(ps, w);
				break;
			case C2_L_POVREDIR: predef_target = w->a.override_redirect; break;
			case C2_L_PARGB: predef_target = win_has_alpha(w); break;
			case C2_L_PFOCUSED:
				predef_target = win_is_focused_raw(ps, w);
				break;
			case C2_L_PWMWIN: predef_target = w->wmwin; break;
			case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
			case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
			case C2_L_PCLIENT: predef_target = w->client_win; break;
			case C2_L_PLEADER: predef_target = w->leader; break;
			default:
				*perr = true;
				assert(0);
				break;
			}
			ntargets = 1;
			targets = &predef_target;
		}
		// A raw window property
		else {
			int word_count = 1;
			if (pleaf->index < 0) {
				// Get length of property in 32-bit multiples
				auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom);
				word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
			}
			winprop_t prop = x_get_prop_with_offset(
			    &ps->c, wid, pleaf->tgtatom, idx, word_count,
			    c2_get_atom_type(pleaf), pleaf->format);

			ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
			if (ntargets > 0) {
				targets = targets_free = ccalloc(ntargets, long long);
				*perr = false;
				for (size_t i = 0; i < ntargets; ++i) {
					targets[i] = winprop_get_int(prop, i);
				}
			}
			free_winprop(&prop);
		}

		if (*perr) {
			goto fail_int;
		}

		// Do comparison
		bool res = false;
		for (size_t i = 0; i < ntargets; ++i) {
			long long tgt = targets[i];
			switch (pleaf->op) {
			case C2_L_OEXISTS:
				res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true);
				break;
			case C2_L_OEQ: res = (tgt == pleaf->ptnint); break;
			case C2_L_OGT: res = (tgt > pleaf->ptnint); break;
			case C2_L_OGTEQ: res = (tgt >= pleaf->ptnint); break;
			case C2_L_OLT: res = (tgt < pleaf->ptnint); break;
			case C2_L_OLTEQ: res = (tgt <= pleaf->ptnint); break;
			default: *perr = true; assert(0);
			}
			if (res) {
				break;
			}
		}
		*pres = res;

	fail_int:
		// Free property values after usage, if necessary
		if (targets_free) {
			free(targets_free);
		}
	} break;
	// String patterns
	case C2_L_PTSTRING: {
		const char **targets = NULL;
		const char **targets_free = NULL;
		const char **targets_free_inner = NULL;
		size_t ntargets = 0;

		// A predefined target
		const char *predef_target = NULL;
		if (pleaf->predef != C2_L_PUNDEFINED) {
			switch (pleaf->predef) {
			case C2_L_PWINDOWTYPE:
				predef_target = WINTYPES[w->window_type];
				break;
			case C2_L_PNAME: predef_target = w->name; break;
			case C2_L_PCLASSG: predef_target = w->class_general; break;
			case C2_L_PCLASSI: predef_target = w->class_instance; break;
			case C2_L_PROLE: predef_target = w->role; break;
			default: assert(0); break;
			}
			ntargets = 1;
			targets = &predef_target;
		}
		// An atom type property, convert it to string
		else if (pleaf->type == C2_L_TATOM) {
			int word_count = 1;
			if (pleaf->index < 0) {
				// Get length of property in 32-bit multiples
				auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom);
				word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
			}
			winprop_t prop = x_get_prop_with_offset(
			    &ps->c, wid, pleaf->tgtatom, idx, word_count,
			    c2_get_atom_type(pleaf), pleaf->format);

			ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
			targets = targets_free = (const char **)ccalloc(2 * ntargets, char *);
			targets_free_inner = targets + ntargets;

			for (size_t i = 0; i < ntargets; ++i) {
				xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i);
				if (atom) {
					xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
					    ps->c.c, xcb_get_atom_name(ps->c.c, atom), NULL);
					if (reply) {
						targets[i] = targets_free_inner[i] = strndup(
						    xcb_get_atom_name_name(reply),
						    (size_t)xcb_get_atom_name_name_length(reply));
						free(reply);
					}
				}
			}
			free_winprop(&prop);
		}
		// Not an atom type, just fetch the string list
		else {
			char **strlst = NULL;
			int nstr = 0;
			if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) {
				if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) {
					ntargets = to_u32_checked(nstr);
					targets = (const char **)strlst;
				} else if (nstr > idx) {
					ntargets = 1;
					targets = (const char **)strlst + idx;
				}
			}
			if (strlst) {
				targets_free = (const char **)strlst;
			}
		}

		if (ntargets == 0) {
			goto fail_str;
		}
		for (size_t i = 0; i < ntargets; ++i) {
			if (!targets[i]) {
				goto fail_str;
			}
		}
		*perr = false;

		// Actual matching
		bool res = false;
		for (size_t i = 0; i < ntargets; ++i) {
			const char *tgt = targets[i];
			switch (pleaf->op) {
			case C2_L_OEXISTS: res = true; break;
			case C2_L_OEQ:
				switch (pleaf->match) {
				case C2_L_MEXACT:
					if (pleaf->match_ignorecase) {
						res = !strcasecmp(tgt, pleaf->ptnstr);
					} else {
						res = !strcmp(tgt, pleaf->ptnstr);
					}
					break;
				case C2_L_MCONTAINS:
					if (pleaf->match_ignorecase) {
						res = strcasestr(tgt, pleaf->ptnstr);
					} else {
						res = strstr(tgt, pleaf->ptnstr);
					}
					break;
				case C2_L_MSTART:
					if (pleaf->match_ignorecase) {
						res = !strncasecmp(tgt, pleaf->ptnstr,
						                   strlen(pleaf->ptnstr));
					} else {
						res = !strncmp(tgt, pleaf->ptnstr,
						               strlen(pleaf->ptnstr));
					}
					break;
				case C2_L_MWILDCARD: {
					int flags = 0;
					if (pleaf->match_ignorecase) {
						flags |= FNM_CASEFOLD;
					}
					res = !fnmatch(pleaf->ptnstr, tgt, flags);
				} break;
				case C2_L_MPCRE:
#ifdef CONFIG_REGEX_PCRE
					assert(strlen(tgt) <= INT_MAX);
					assert(pleaf->regex_pcre);
					res = (pcre2_match(pleaf->regex_pcre, (PCRE2_SPTR)tgt,
					                   strlen(tgt), 0, 0,
					                   pleaf->regex_pcre_match, NULL) > 0);
#else
					assert(0);
#endif
					break;
				}
				break;
			default: *perr = true; assert(0);
			}
			if (res) {
				break;
			}
		}
		*pres = res;

	fail_str:
		// Free the string after usage, if necessary
		if (targets_free_inner) {
			for (size_t i = 0; i < ntargets; ++i) {
				if (targets_free_inner[i]) {
					free((void *)targets_free_inner[i]);
				}
			}
		}
		// Free property values after usage, if necessary
		if (targets_free) {
			free(targets_free);
		}
	} break;
	default: assert(0); break;
	}
}

/**
 * Match a window against a single window condition.
 *
 * @return true if matched, false otherwise.
 */
static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond) {
	bool result = false;
	bool error = true;

	// Handle a branch
	if (cond.isbranch) {
		const c2_b_t *pb = cond.b;

		if (!pb) {
			return false;
		}

		error = false;

		switch (pb->op) {
		case C2_B_OAND:
			result = (c2_match_once(ps, w, pb->opr1) &&
			          c2_match_once(ps, w, pb->opr2));
			break;
		case C2_B_OOR:
			result = (c2_match_once(ps, w, pb->opr1) ||
			          c2_match_once(ps, w, pb->opr2));
			break;
		case C2_B_OXOR:
			result = (c2_match_once(ps, w, pb->opr1) !=
			          c2_match_once(ps, w, pb->opr2));
			break;
		default: error = true; assert(0);
		}

#ifdef DEBUG_WINMATCH
		log_trace("(%#010x): branch: result = %d, pattern = ", w->base.id, result);
		c2_dump(cond);
		putchar('\n');
#endif
	}
	// Handle a leaf
	else {
		const c2_l_t *pleaf = cond.l;

		if (!pleaf) {
			return false;
		}

		c2_match_once_leaf(ps, w, pleaf, &result, &error);

		// For EXISTS operator, no errors are fatal
		if (C2_L_OEXISTS == pleaf->op && error) {
			result = false;
			error = false;
		}

#ifdef DEBUG_WINMATCH
		log_trace("(%#010x): leaf: result = %d, error = %d, "
		          "client = %#010x,  pattern = ",
		          w->base.id, result, error, w->client_win);
		c2_dump(cond);
		putchar('\n');
#endif
	}

	// Postprocess the result
	if (error) {
		result = false;
	}

	if (cond.isbranch ? cond.b->neg : cond.l->neg) {
		result = !result;
	}

	return result;
}

/**
 * Match a window against a condition linked list.
 *
 * @param cache a place to cache the last matched condition
 * @param pdata a place to return the data
 * @return true if matched, false otherwise.
 */
bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst,
              void **pdata) {
	assert(ps->server_grabbed);
	// Then go through the whole linked list
	for (; condlst; condlst = condlst->next) {
		if (c2_match_once(ps, w, condlst->ptr)) {
			if (pdata) {
				*pdata = condlst->data;
			}
			return true;
		}
	}

	return false;
}

/// Iterate over all conditions in a condition linked list. Call the callback for each of
/// the conditions. If the callback returns true, the iteration stops early.
///
/// Returns whether the iteration was stopped early.
bool c2_list_foreach(const c2_lptr_t *condlist, c2_list_foreach_cb_t cb, void *data) {
	for (auto i = condlist; i; i = i->next) {
		if (cb(i, data)) {
			return true;
		}
	}
	return false;
}

/// Return user data stored in a condition.
void *c2_list_get_data(const c2_lptr_t *condlist) {
	return condlist->data;
}
