/*
 * Copyright © 2014 Ran Benita <ran234@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "config.h"

#include "xkbcommon/xkbcommon-compose.h"

#include "test.h"

static const char *
compose_status_string(enum xkb_compose_status status)
{
    switch (status) {
    case XKB_COMPOSE_NOTHING:
        return "nothing";
    case XKB_COMPOSE_COMPOSING:
        return "composing";
    case XKB_COMPOSE_COMPOSED:
        return "composed";
    case XKB_COMPOSE_CANCELLED:
        return "cancelled";
    }

    return "<invalid-status>";
}

static const char *
feed_result_string(enum xkb_compose_feed_result result)
{
    switch (result) {
    case XKB_COMPOSE_FEED_IGNORED:
        return "ignored";
    case XKB_COMPOSE_FEED_ACCEPTED:
        return "accepted";
    }

    return "<invalid-result>";
}

/*
 * Feed a sequence of keysyms to a fresh compose state and test the outcome.
 *
 * The varargs consists of lines in the following format:
 *      <input keysym> <expected feed result> <expected status> <expected string> <expected keysym>
 * Terminated by a line consisting only of XKB_KEY_NoSymbol.
 */
static bool
test_compose_seq_va(struct xkb_compose_table *table, va_list ap)
{
    int ret;
    struct xkb_compose_state *state;
    char buffer[64];

    state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
    assert(state);

    for (int i = 1; ; i++) {
        xkb_keysym_t input_keysym;
        enum xkb_compose_feed_result result, expected_result;
        enum xkb_compose_status status, expected_status;
        const char *expected_string;
        xkb_keysym_t keysym, expected_keysym;

        input_keysym = va_arg(ap, xkb_keysym_t);
        if (input_keysym == XKB_KEY_NoSymbol)
            break;

        expected_result = va_arg(ap, enum xkb_compose_feed_result);
        expected_status = va_arg(ap, enum xkb_compose_status);
        expected_string = va_arg(ap, const char *);
        expected_keysym = va_arg(ap, xkb_keysym_t);

        result = xkb_compose_state_feed(state, input_keysym);

        if (result != expected_result) {
            fprintf(stderr, "after feeding %d keysyms:\n", i);
            fprintf(stderr, "expected feed result: %s\n",
                    feed_result_string(expected_result));
            fprintf(stderr, "got feed result: %s\n",
                    feed_result_string(result));
            goto fail;
        }

        status = xkb_compose_state_get_status(state);
        if (status != expected_status) {
            fprintf(stderr, "after feeding %d keysyms:\n", i);
            fprintf(stderr, "expected status: %s\n",
                    compose_status_string(expected_status));
            fprintf(stderr, "got status: %s\n",
                    compose_status_string(status));
            goto fail;
        }

        ret = xkb_compose_state_get_utf8(state, buffer, sizeof(buffer));
        if (ret < 0 || (size_t) ret >= sizeof(buffer)) {
            fprintf(stderr, "after feeding %d keysyms:\n", i);
            fprintf(stderr, "expected string: %s\n", expected_string);
            fprintf(stderr, "got error: %d\n", ret);
            goto fail;
        }
        if (!streq(buffer, expected_string)) {
            fprintf(stderr, "after feeding %d keysyms:\n", i);
            fprintf(stderr, "expected string: %s\n", strempty(expected_string));
            fprintf(stderr, "got string: %s\n", buffer);
            goto fail;
        }

        keysym = xkb_compose_state_get_one_sym(state);
        if (keysym != expected_keysym) {
            fprintf(stderr, "after feeding %d keysyms:\n", i);
            xkb_keysym_get_name(expected_keysym, buffer, sizeof(buffer));
            fprintf(stderr, "expected keysym: %s\n", buffer);
            xkb_keysym_get_name(keysym, buffer, sizeof(buffer));
            fprintf(stderr, "got keysym (%#x): %s\n", keysym, buffer);
            goto fail;
        }
    }

    xkb_compose_state_unref(state);
    return true;

fail:
    xkb_compose_state_unref(state);
    return false;
}

static bool
test_compose_seq(struct xkb_compose_table *table, ...)
{
    va_list ap;
    bool ok;
    va_start(ap, table);
    ok = test_compose_seq_va(table, ap);
    va_end(ap);
    return ok;
}

static bool
test_compose_seq_buffer(struct xkb_context *ctx, const char *buffer, ...)
{
    va_list ap;
    bool ok;
    struct xkb_compose_table *table;
    table = xkb_compose_table_new_from_buffer(ctx, buffer, strlen(buffer), "",
                                              XKB_COMPOSE_FORMAT_TEXT_V1,
                                              XKB_COMPOSE_COMPILE_NO_FLAGS);
    assert(table);
    va_start(ap, buffer);
    ok = test_compose_seq_va(table, ap);
    va_end(ap);
    xkb_compose_table_unref(table);
    return ok;
}

static void
test_seqs(struct xkb_context *ctx)
{
    struct xkb_compose_table *table;
    char *path;
    FILE *file;

    path = test_get_path("compose/en_US.UTF-8/Compose");
    file = fopen(path, "rb");
    assert(file);
    free(path);

    table = xkb_compose_table_new_from_file(ctx, file, "",
                                            XKB_COMPOSE_FORMAT_TEXT_V1,
                                            XKB_COMPOSE_COMPILE_NO_FLAGS);
    assert(table);
    fclose(file);

    assert(test_compose_seq(table,
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_space,          XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "~",    XKB_KEY_asciitilde,
        XKB_KEY_NoSymbol));

    assert(test_compose_seq(table,
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_space,          XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "~",    XKB_KEY_asciitilde,
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_space,          XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "~",    XKB_KEY_asciitilde,
        XKB_KEY_NoSymbol));

    assert(test_compose_seq(table,
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "~",    XKB_KEY_asciitilde,
        XKB_KEY_NoSymbol));

    assert(test_compose_seq(table,
        XKB_KEY_dead_acute,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_space,          XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "'",    XKB_KEY_apostrophe,
        XKB_KEY_Caps_Lock,      XKB_COMPOSE_FEED_IGNORED,   XKB_COMPOSE_COMPOSED,   "'",    XKB_KEY_apostrophe,
        XKB_KEY_NoSymbol));

    assert(test_compose_seq(table,
        XKB_KEY_dead_acute,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_dead_acute,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "´",    XKB_KEY_acute,
        XKB_KEY_NoSymbol));

    assert(test_compose_seq(table,
        XKB_KEY_Multi_key,      XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_Shift_L,        XKB_COMPOSE_FEED_IGNORED,   XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_Caps_Lock,      XKB_COMPOSE_FEED_IGNORED,   XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_Control_L,      XKB_COMPOSE_FEED_IGNORED,   XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_T,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "@",    XKB_KEY_at,
        XKB_KEY_NoSymbol));

    assert(test_compose_seq(table,
        XKB_KEY_7,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,    "",     XKB_KEY_NoSymbol,
        XKB_KEY_a,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,    "",     XKB_KEY_NoSymbol,
        XKB_KEY_b,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,    "",     XKB_KEY_NoSymbol,
        XKB_KEY_NoSymbol));

    assert(test_compose_seq(table,
        XKB_KEY_Multi_key,      XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_apostrophe,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_7,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_CANCELLED,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_7,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,    "",     XKB_KEY_NoSymbol,
        XKB_KEY_Caps_Lock,      XKB_COMPOSE_FEED_IGNORED,   XKB_COMPOSE_NOTHING,    "",     XKB_KEY_NoSymbol,
        XKB_KEY_NoSymbol));

    xkb_compose_table_unref(table);

    /* Make sure one-keysym sequences work. */
    assert(test_compose_seq_buffer(ctx,
        "<A>          :  \"foo\"  X \n"
        "<B> <A>      :  \"baz\"  Y \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,  "foo",   XKB_KEY_X,
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,  "foo",   XKB_KEY_X,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,   "",      XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING, "",      XKB_KEY_NoSymbol,
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,  "baz",   XKB_KEY_Y,
        XKB_KEY_NoSymbol));

    /* No sequences at all. */
    assert(test_compose_seq_buffer(ctx,
        "",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,   "",      XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,   "",      XKB_KEY_NoSymbol,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,   "",      XKB_KEY_NoSymbol,
        XKB_KEY_Multi_key,      XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,   "",      XKB_KEY_NoSymbol,
        XKB_KEY_dead_acute,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,   "",      XKB_KEY_NoSymbol,
        XKB_KEY_NoSymbol));

    /* Only keysym - string derived from keysym. */
    assert(test_compose_seq_buffer(ctx,
        "<A> <B>     :  X \n"
        "<B> <A>     :  dollar \n"
        "<C>         :  dead_acute \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING, "",      XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,  "X",     XKB_KEY_X,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING, "",      XKB_KEY_NoSymbol,
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,  "$",     XKB_KEY_dollar,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,  "",      XKB_KEY_dead_acute,
        XKB_KEY_NoSymbol));

    /* Make sure a cancelling keysym doesn't start a new sequence. */
    assert(test_compose_seq_buffer(ctx,
        "<A> <B>     :  X \n"
        "<C> <D>     :  Y \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING, "",      XKB_KEY_NoSymbol,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_CANCELLED, "",      XKB_KEY_NoSymbol,
        XKB_KEY_D,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,   "",      XKB_KEY_NoSymbol,
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING, "",      XKB_KEY_NoSymbol,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_CANCELLED, "",      XKB_KEY_NoSymbol,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING, "",      XKB_KEY_NoSymbol,
        XKB_KEY_D,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,  "Y",     XKB_KEY_Y,
        XKB_KEY_NoSymbol));
}

static void
test_conflicting(struct xkb_context *ctx)
{
    // new is prefix of old
    assert(test_compose_seq_buffer(ctx,
        "<A> <B> <C>  :  \"foo\"  A \n"
        "<A> <B>      :  \"bar\"  B \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "foo",  XKB_KEY_A,
        XKB_KEY_NoSymbol));

    // old is a prefix of new
    assert(test_compose_seq_buffer(ctx,
        "<A> <B>      :  \"bar\"  B \n"
        "<A> <B> <C>  :  \"foo\"  A \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "foo",  XKB_KEY_A,
        XKB_KEY_NoSymbol));

    // new duplicate of old
    assert(test_compose_seq_buffer(ctx,
        "<A> <B>      :  \"bar\"  B \n"
        "<A> <B>      :  \"bar\"  B \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "bar",  XKB_KEY_B,
        XKB_KEY_C,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_NOTHING,    "",     XKB_KEY_NoSymbol,
        XKB_KEY_NoSymbol));

    // new same length as old #1
    assert(test_compose_seq_buffer(ctx,
        "<A> <B>      :  \"foo\"  A \n"
        "<A> <B>      :  \"bar\"  B \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "bar",  XKB_KEY_B,
        XKB_KEY_NoSymbol));

    // new same length as old #2
    assert(test_compose_seq_buffer(ctx,
        "<A> <B>      :  \"foo\"  A \n"
        "<A> <B>      :  \"foo\"  B \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "foo",  XKB_KEY_B,
        XKB_KEY_NoSymbol));

    // new same length as old #3
    assert(test_compose_seq_buffer(ctx,
        "<A> <B>      :  \"foo\"  A \n"
        "<A> <B>      :  \"bar\"  A \n",
        XKB_KEY_A,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_B,              XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "bar",  XKB_KEY_A,
        XKB_KEY_NoSymbol));
}

static void
test_state(struct xkb_context *ctx)
{
    struct xkb_compose_table *table;
    struct xkb_compose_state *state;
    char *path;
    FILE *file;

    path = test_get_path("compose/en_US.UTF-8/Compose");
    file = fopen(path, "rb");
    assert(file);
    free(path);

    table = xkb_compose_table_new_from_file(ctx, file, "",
                                            XKB_COMPOSE_FORMAT_TEXT_V1,
                                            XKB_COMPOSE_COMPILE_NO_FLAGS);
    assert(table);
    fclose(file);

    state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
    assert(state);

    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
    xkb_compose_state_reset(state);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
    xkb_compose_state_feed(state, XKB_KEY_NoSymbol);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
    xkb_compose_state_feed(state, XKB_KEY_Multi_key);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
    xkb_compose_state_reset(state);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
    xkb_compose_state_feed(state, XKB_KEY_Multi_key);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
    xkb_compose_state_feed(state, XKB_KEY_Multi_key);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_CANCELLED);
    xkb_compose_state_feed(state, XKB_KEY_Multi_key);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
    xkb_compose_state_feed(state, XKB_KEY_Multi_key);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_CANCELLED);
    xkb_compose_state_reset(state);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
    xkb_compose_state_feed(state, XKB_KEY_dead_acute);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
    xkb_compose_state_feed(state, XKB_KEY_A);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSED);
    xkb_compose_state_reset(state);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
    xkb_compose_state_feed(state, XKB_KEY_dead_acute);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
    xkb_compose_state_feed(state, XKB_KEY_A);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSED);
    xkb_compose_state_reset(state);
    xkb_compose_state_feed(state, XKB_KEY_NoSymbol);
    assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);

    xkb_compose_state_unref(state);
    xkb_compose_table_unref(table);
}

static void
test_XCOMPOSEFILE(struct xkb_context *ctx)
{
    struct xkb_compose_table *table;
    char *path;

    path = test_get_path("compose/en_US.UTF-8/Compose");
    setenv("XCOMPOSEFILE", path, 1);
    free(path);

    table = xkb_compose_table_new_from_locale(ctx, "blabla",
                                              XKB_COMPOSE_COMPILE_NO_FLAGS);
    assert(table);

    assert(test_compose_seq(table,
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_space,          XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "~",    XKB_KEY_asciitilde,
        XKB_KEY_NoSymbol));

    xkb_compose_table_unref(table);
}

static void
test_modifier_syntax(struct xkb_context *ctx)
{
    const char *table_string;

    /* We don't do anything with the modifiers, but make sure we can parse
     * them. */

    assert(test_compose_seq_buffer(ctx,
        "None <A>          : X \n"
        "Shift <B>         : Y \n"
        "Ctrl <C>          : Y \n"
        "Alt <D>           : Y \n"
        "Caps <E>          : Y \n"
        "Lock <F>          : Y \n"
        "Shift Ctrl <G>    : Y \n"
        "~Shift <H>        : Y \n"
        "~Shift Ctrl <I>   : Y \n"
        "Shift ~Ctrl <J>   : Y \n"
        "Shift ~Ctrl ~Alt <K> : Y \n"
        "! Shift <B>       : Y \n"
        "! Ctrl <C>        : Y \n"
        "! Alt <D>         : Y \n"
        "! Caps <E>        : Y \n"
        "! Lock <F>        : Y \n"
        "! Shift Ctrl <G>  : Y \n"
        "! ~Shift <H>      : Y \n"
        "! ~Shift Ctrl <I> : Y \n"
        "! Shift ~Ctrl <J> : Y \n"
        "! Shift ~Ctrl ~Alt <K> : Y \n"
        "<L> ! Shift <M>   : Y \n"
        "None <N> ! Shift <O> : Y \n"
        "None <P> ! Shift <Q> : Y \n",
        XKB_KEY_NoSymbol));

    fprintf(stderr, "<START bad input string>\n");
    table_string =
        "! None <A>        : X \n"
        "! Foo <B>         : X \n"
        "None ! Shift <C>  : X \n"
        "! ! <D>           : X \n"
        "! ~ <E>           : X \n"
        "! ! <F>           : X \n"
        "! Ctrl ! Ctrl <G> : X \n"
        "<H> !             : X \n"
        "<I> None          : X \n"
        "None None <J>     : X \n"
        "<K>               : !Shift X \n";
    assert(!xkb_compose_table_new_from_buffer(ctx, table_string,
                                              strlen(table_string), "C",
                                              XKB_COMPOSE_FORMAT_TEXT_V1,
                                              XKB_COMPOSE_COMPILE_NO_FLAGS));
    fprintf(stderr, "<END bad input string>\n");
}

static void
test_include(struct xkb_context *ctx)
{
    char *path, *table_string;
    int ret;

    path = test_get_path("compose/en_US.UTF-8/Compose");
    assert(path);

    /* We don't have a mechanism to change the include paths like we
     * have for keymaps. So we must include the full path. */
    ret = asprintf(&table_string,
        "<dead_tilde> <space>   : \"foo\" X\n"
        "include \"%s\"\n"
        "<dead_tilde> <dead_tilde> : \"bar\" Y\n", path);
    assert(ret >= 0);

    assert(test_compose_seq_buffer(ctx, table_string,
        /* No conflict. */
        XKB_KEY_dead_acute,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_dead_acute,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "´",    XKB_KEY_acute,

        /* Comes before - doesn't override. */
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_space,          XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "~",    XKB_KEY_asciitilde,

        /* Comes after - does override. */
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSING,  "",     XKB_KEY_NoSymbol,
        XKB_KEY_dead_tilde,     XKB_COMPOSE_FEED_ACCEPTED,  XKB_COMPOSE_COMPOSED,   "bar",  XKB_KEY_Y,

        XKB_KEY_NoSymbol));

    free(path);
    free(table_string);
}

int
main(int argc, char *argv[])
{
    struct xkb_context *ctx;

    ctx = test_get_context(CONTEXT_NO_FLAG);
    assert(ctx);

    test_seqs(ctx);
    test_conflicting(ctx);
    test_XCOMPOSEFILE(ctx);
    test_state(ctx);
    test_modifier_syntax(ctx);
    test_include(ctx);

    xkb_context_unref(ctx);
    return 0;
}
