/*
 * Compare project/version strings of the form:
 *
 * PROJECT v<num>.<num>...[ <flags>]
 *
 * Pattern is of the form:
 *
 * PROJECT[ v[<num>.<num>...][-[<num>.<num>...]] ][+-]flags...
 *
 * Chunks must be separated by exactly one space. Flags are A-Za-z0-9.
 * Any control character is treated as end of string.
 */

#include "matchver.h"

static int flag_val(char c)
{
    if (c < '0')
	return -1;
    else if (c <= '9')
	return c-'0';
    else if (c < 'A')
	return -1;
    else if (c <= 'Z')
	return c-'A'+10;
    else if (c < 'a')
	return -1;
    else if (c <= 'z')
	return c-'a'+36;
    else
	return -1;
}

static uint64_t flag_mask(char c)
{
    int v = flag_val(c);
    return (v < 0) ? 0 : UINT64_C(1) << v;
}

static inline bool is_digit(char c)
{
    return (unsigned int)(c - '0') < 10;
}

/*
 * Compare numeric strings with components separated by dots, return the end
 * of each string.
 */
static int compare_numbers(const char **ap, const char **bp,
			   unsigned long bmissing)
{
    bool adig, bdig;
    unsigned long an, bn;
    int result = 0;
    const char *a = *ap, *b = *bp;

    for (;;) {
	adig = is_digit(*a);
	bdig = is_digit(*b);

	if (!adig && !bdig)
	    break;

	if (adig) {
	    an = strtoul(a, (char **)&a, 10);
	    if (*a == '.')
		a++;
	} else {
	    an = 0;
	}

	if (bdig) {
	    bn = strtoul(b, (char **)&b, 10);
	    if (*b == '.')
		b++;
	} else {
	    bn = bmissing;
	}

	/* If result is set, the answer is already known, just find the end */
	if (!result) {
	    result = (an < bn) ? -1 : (an > bn) ? 1 : 0;
	}
    }

    *ap = a; *bp = b;
    return result;
}

static const char *parse_flags(const char *str, uint64_t *flags, uint64_t *mask)
{
    uint64_t polarity = -1;
    uint64_t f = 0, m = 0;


    while (1) {
	char c = *str++;
	uint64_t bit;

	if (c == '+') {
	    polarity = -1;
	} else if (c == '-') {
	    polarity = 0;
	} else {
	    bit = flag_mask(c);
	    if (!bit)
		break;

	    m |= bit;
	    f = (f & ~bit) | (polarity & bit);
	}
    }

    *flags = f; *mask = m;
    return str;
}

bool match_version(const char *version, const char *pattern)
{
    char v, p;
    const char *vstart, *pstart;
    uint64_t vflags, pflags, pmask;

    while (1) {
	v = *version++;
	p = *pattern++;

	if (v <= ' ' && p <= ' ')
	    break;

	if (v != p)
	    return false;
    }

    if (p < ' ')
	return true;		/* Project-only pattern */

    if (v != ' ' || *version++ != 'v')
	return false;		/* Invalid/unparsable version string */

    if (*pattern++ != 'v')
	return false;		/* Invalid pattern */

    vstart = version;
    pstart = pattern;
    if (compare_numbers(&version, &pattern, 0UL) < 0)
	return false;

    if (*pattern == '-')
	pattern++;
    else
	pattern = pstart;
    if (compare_numbers(&vstart, &pattern, -1UL) > 0)
	return false;

    v = *version++;
    vflags = 0;
    if (v == ' ') {
	uint64_t dummy;
	parse_flags(version, &vflags, &dummy);
    } else if (v > ' ') {
	return false;
    }

    p = *pattern++;
    pflags = pmask = 0;
    if (p == ' ') {
	parse_flags(pattern, &pflags, &pmask);
    } else if (p > ' ') {
	return false;
    }

    return (vflags & pmask) == pflags;
}

#ifdef COMMAND

#include <stdio.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
	fprintf(stderr, "Usage: %s version pattern\n", argv[0]);
	return 127;
    }

    return !match_version(argv[1], argv[2]);
}

#endif
