/*
 * For HPND
 * Copyright (c) 1994 by Silicon Graphics Computer Systems, Inc.
 *
 * For MIT:
 * Copyright © 2012 Intel Corporation
 * Copyright © 2012 Ran Benita <ran234@gmail.com>
 *
 * SPDX-License-Identifier: HPND AND MIT
 *
 * Author: Daniel Stone <daniel@fooishbar.org>
 * Author: Ran Benita <ran234@gmail.com>
 */

#include "config.h"

#include <stdint.h>

#include "xkbcommon/xkbcommon-keysyms.h"
#include "messages-codes.h"
#include "xkbcomp-priv.h"
#include "ast.h"
#include "ast-build.h"
#include "include.h"
#include "keysym.h"
#include "utf8-decoding.h"

static ExprDef *
ExprCreate(enum stmt_type op)
{
    ExprDef *expr = malloc(sizeof(*expr));
    if (!expr)
        return NULL;

    expr->common.type = op;
    expr->common.next = NULL;

    return expr;
}

ExprDef *
ExprCreateString(xkb_atom_t str)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_STRING_LITERAL);
    if (!expr)
        return NULL;
    expr->string.str = str;
    return expr;
}

ExprDef *
ExprCreateInteger(int64_t ival)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_INTEGER_LITERAL);
    if (!expr)
        return NULL;
    expr->integer.ival = ival;
    return expr;
}

ExprDef *
ExprCreateFloat(void)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_FLOAT_LITERAL);
    if (!expr)
        return NULL;
    return expr;
}

ExprDef *
ExprCreateBoolean(bool set)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_BOOLEAN_LITERAL);
    if (!expr)
        return NULL;
    expr->boolean.set = set;
    return expr;
}

ExprDef *
ExprCreateKeyName(xkb_atom_t key_name)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_KEYNAME_LITERAL);
    if (!expr)
        return NULL;
    expr->key_name.key_name = key_name;
    return expr;
}

ExprDef *
ExprCreateKeySym(xkb_keysym_t keysym)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_KEYSYM_LITERAL);
    if (!expr)
        return NULL;
    expr->keysym.keysym = keysym;
    return expr;
}

ExprDef *
ExprCreateIdent(xkb_atom_t ident)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_IDENT);
    if (!expr)
        return NULL;
    expr->ident.ident = ident;
    return expr;
}

ExprDef *
ExprCreateUnary(enum stmt_type op, ExprDef *child)
{
    ExprDef *expr = ExprCreate(op);
    if (!expr)
        return NULL;
    expr->unary.child = child;
    return expr;
}

ExprDef *
ExprCreateBinary(enum stmt_type op, ExprDef *left, ExprDef *right)
{
    ExprDef *expr = ExprCreate(op);
    if (!expr)
        return NULL;

    expr->binary.left = left;
    expr->binary.right = right;

    return expr;
}

ExprDef *
ExprCreateFieldRef(xkb_atom_t element, xkb_atom_t field)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_FIELD_REF);
    if (!expr)
        return NULL;
    expr->field_ref.element = element;
    expr->field_ref.field = field;
    return expr;
}

ExprDef *
ExprCreateArrayRef(xkb_atom_t element, xkb_atom_t field, ExprDef *entry)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_ARRAY_REF);
    if (!expr)
        return NULL;
    expr->array_ref.element = element;
    expr->array_ref.field = field;
    expr->array_ref.entry = entry;
    return expr;
}

ExprDef *
ExprEmptyList(void)
{
    return ExprCreate(STMT_EXPR_EMPTY_LIST);
}

ExprDef *
ExprCreateAction(xkb_atom_t name, ExprDef *args)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_ACTION_DECL);
    if (!expr)
        return NULL;
    expr->action.name = name;
    expr->action.args = args;
    return expr;
}

ExprDef *
ExprCreateActionList(ExprDef *actions)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_ACTION_LIST);
    if (!expr)
        return NULL;
    expr->actions.actions = actions;
    return expr;
}

ExprDef *
ExprCreateKeySymList(xkb_keysym_t sym)
{
    ExprDef *expr = ExprCreate(STMT_EXPR_KEYSYM_LIST);
    if (!expr)
        return NULL;
    darray_init(expr->keysym_list.syms);
    if (sym == XKB_KEY_NoSymbol) {
        /* Discard NoSymbol */
    } else {
        darray_append(expr->keysym_list.syms, sym);
    }
    return expr;
}

ExprDef *
ExprAppendKeySymList(ExprDef *expr, xkb_keysym_t sym)
{
    if (sym == XKB_KEY_NoSymbol) {
        /* Discard NoSymbol */
    } else {
        darray_append(expr->keysym_list.syms, sym);
    }
    return expr;
}

ExprDef *
ExprKeySymListAppendString(struct scanner *scanner,
                           ExprDef *expr, const char *string)
{
    /* TODO: use strnlen with max len = 4 * MAX_KEYSYMS_LIST_LENGTH */
    const size_t len = strlen(string);
    size_t idx = 0;
    size_t idx_cp = 1;
    while (idx < len) {
        size_t count = 0;
        uint32_t cp = utf8_next_code_point(string + idx, len - idx, &count);
        if (cp == INVALID_UTF8_CODE_POINT) {
            scanner_err(scanner, XKB_ERROR_INVALID_FILE_ENCODING,
                        "Cannot convert string to keysyms: "
                        "Invalid UTF-8 encoding starting at byte position %zu "
                        "(code point position: %zu).",
                        idx + 1, idx_cp);
            goto error;
        }
        const xkb_keysym_t sym = xkb_utf32_to_keysym(cp);
        if (sym == XKB_KEY_NoSymbol) {
            scanner_err(scanner, XKB_LOG_MESSAGE_NO_ID,
                        "Cannot convert string to keysyms: Unicode code point "
                        "U+04%"PRIX32" has no keysym equivalent"
                        "(byte position: %zu, code point position: %zu).",
                        cp, idx + 1, idx_cp);
            goto error;
        }
        darray_append(expr->keysym_list.syms, sym);
        idx += count;
        idx_cp++;
    }
    assert(string[idx] == '\0');
    return expr;
error:
    FreeStmt((ParseCommon*) expr);
    return NULL;
}

xkb_keysym_t
KeysymParseString(struct scanner *scanner, const char *string)
{
    const size_t len = strlen(string);
    if (len == 0) {
        scanner_err(scanner, XKB_LOG_MESSAGE_NO_ID,
                    "Cannot convert string to single keysym: empty string.");
        return XKB_KEY_NoSymbol;
    }
    size_t count = 0;
    const uint32_t cp = utf8_next_code_point(string, len, &count);
    if (cp == INVALID_UTF8_CODE_POINT) {
        scanner_err(scanner, XKB_ERROR_INVALID_FILE_ENCODING,
                    "Cannot convert string to single keysym: "
                    "Invalid UTF-8 encoding.");
        return XKB_KEY_NoSymbol;
    } else if (count != len) {
        scanner_err(scanner, XKB_ERROR_INVALID_FILE_ENCODING,
                    "Cannot convert string to single keysym: "
                    "Expected a single Unicode code point, got: \"%s\".",
                    string);
        return XKB_KEY_NoSymbol;
    }
    const xkb_keysym_t sym = xkb_utf32_to_keysym(cp);
    if (sym == XKB_KEY_NoSymbol) {
        scanner_err(scanner, XKB_LOG_MESSAGE_NO_ID,
                    "Cannot convert string to single keysym: Unicode "
                    "code point U+%04"PRIX32" has no keysym equivalent.",
                    cp);
    }
    return sym;
}

KeycodeDef *
KeycodeCreate(xkb_atom_t name, int64_t value)
{
    KeycodeDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_KEYCODE;
    def->common.next = NULL;
    def->name = name;
    def->value = value;

    return def;
}

KeyAliasDef *
KeyAliasCreate(xkb_atom_t alias, xkb_atom_t real)
{
    KeyAliasDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_ALIAS;
    def->common.next = NULL;
    def->alias = alias;
    def->real = real;

    return def;
}

VModDef *
VModCreate(xkb_atom_t name, ExprDef *value)
{
    VModDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_VMOD;
    def->common.next = NULL;
    def->name = name;
    def->value = value;

    return def;
}

VarDef *
VarCreate(ExprDef *name, ExprDef *value)
{
    VarDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_VAR;
    def->common.next = NULL;
    def->name = name;
    def->value = value;

    return def;
}

VarDef *
BoolVarCreate(xkb_atom_t ident, bool set)
{
    ExprDef *name, *value;
    VarDef *def;
    if (!(name = ExprCreateIdent(ident))) {
        return NULL;
    }
    if (!(value = ExprCreateBoolean(set))) {
        FreeStmt((ParseCommon *) name);
        return NULL;
    }
    if (!(def = VarCreate(name, value))) {
        FreeStmt((ParseCommon *) name);
        FreeStmt((ParseCommon *) value);
        return NULL;
    }
    return def;
}

InterpDef *
InterpCreate(xkb_keysym_t sym, ExprDef *match)
{
    InterpDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_INTERP;
    def->common.next = NULL;
    def->sym = sym;
    def->match = match;
    def->def = NULL;

    return def;
}

KeyTypeDef *
KeyTypeCreate(xkb_atom_t name, VarDef *body)
{
    KeyTypeDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_TYPE;
    def->common.next = NULL;
    def->merge = MERGE_DEFAULT;
    def->name = name;
    def->body = body;

    return def;
}

SymbolsDef *
SymbolsCreate(xkb_atom_t keyName, VarDef *symbols)
{
    SymbolsDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_SYMBOLS;
    def->common.next = NULL;
    def->merge = MERGE_DEFAULT;
    def->keyName = keyName;
    def->symbols = symbols;

    return def;
}

GroupCompatDef *
GroupCompatCreate(int64_t group, ExprDef *val)
{
    GroupCompatDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_GROUP_COMPAT;
    def->common.next = NULL;
    def->merge = MERGE_DEFAULT;
    def->group = group;
    def->def = val;

    return def;
}

ModMapDef *
ModMapCreate(xkb_atom_t modifier, ExprDef *keys)
{
    ModMapDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_MODMAP;
    def->common.next = NULL;
    def->merge = MERGE_DEFAULT;
    def->modifier = modifier;
    def->keys = keys;

    return def;
}

LedMapDef *
LedMapCreate(xkb_atom_t name, VarDef *body)
{
    LedMapDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_LED_MAP;
    def->common.next = NULL;
    def->merge = MERGE_DEFAULT;
    def->name = name;
    def->body = body;

    return def;
}

LedNameDef *
LedNameCreate(int64_t ndx, ExprDef *name, bool virtual)
{
    LedNameDef *def = malloc(sizeof(*def));
    if (!def)
        return NULL;

    def->common.type = STMT_LED_NAME;
    def->common.next = NULL;
    def->merge = MERGE_DEFAULT;
    def->ndx = ndx;
    def->name = name;
    def->virtual = virtual;

    return def;
}

static void
FreeInclude(IncludeStmt *incl);

IncludeStmt *
IncludeCreate(struct xkb_context *ctx, char *str, enum merge_mode merge)
{
    IncludeStmt *incl, *first;
    char *stmt, *tmp;
    char nextop;

    incl = first = NULL;
    tmp = str;
    stmt = strdup_safe(str);
    while (tmp && *tmp)
    {
        char *file = NULL, *map = NULL, *extra_data = NULL;

        if (!ParseIncludeMap(&tmp, &file, &map, &nextop, &extra_data))
            goto err;

        /*
         * Given an RMLVO (here layout) like 'us,,fr', the rules parser
         * will give out something like 'pc+us+:2+fr:3+inet(evdev)'.
         * We should just skip the ':2' in this case and leave it to the
         * appropriate section to deal with the empty group.
         */
        if (isempty(file)) {
            free(file);
            free(map);
            free(extra_data);
            continue;
        }

        if (first == NULL) {
            first = incl = malloc(sizeof(*first));
        } else {
            incl->next_incl = malloc(sizeof(*first));
            incl = incl->next_incl;
        }

        if (!incl) {
            free(file);
            free(map);
            free(extra_data);
            break;
        }

        incl->common.type = STMT_INCLUDE;
        incl->common.next = NULL;
        incl->merge = merge;
        incl->stmt = NULL;
        incl->file = file;
        incl->map = map;
        incl->modifier = extra_data;
        incl->next_incl = NULL;

        switch (nextop) {
        case MERGE_AUGMENT_PREFIX:
            merge = MERGE_AUGMENT;
            break;
        case MERGE_REPLACE_PREFIX:
            merge = MERGE_REPLACE;
            break;
        default:
            merge = MERGE_OVERRIDE;
        }
    }

    if (first)
        first->stmt = stmt;
    else
        free(stmt);

    return first;

err:
    log_err(ctx, XKB_ERROR_INVALID_INCLUDE_STATEMENT,
            "Illegal include statement \"%s\"; Ignored\n", stmt);
    FreeInclude(first);
    free(stmt);
    return NULL;
}

XkbFile *
XkbFileCreate(enum xkb_file_type type, char *name, ParseCommon *defs,
              enum xkb_map_flags flags)
{
    XkbFile *file;

    file = calloc(1, sizeof(*file));
    if (!file)
        return NULL;

    XkbEscapeMapName(name);
    file->file_type = type;
    file->name = name;
    file->defs = defs;
    file->flags = flags;

    return file;
}

XkbFile *
XkbFileFromComponents(struct xkb_context *ctx,
                      const struct xkb_component_names *kkctgs)
{
    char *const components[] = {
        kkctgs->keycodes, kkctgs->types,
        kkctgs->compatibility, kkctgs->symbols,
    };
    enum xkb_file_type type;
    IncludeStmt *include = NULL;
    XkbFile *file = NULL;
    ParseCommon *defs = NULL, *defsLast = NULL;

    for (type = FIRST_KEYMAP_FILE_TYPE; type <= LAST_KEYMAP_FILE_TYPE; type++) {
        include = IncludeCreate(ctx, components[type], MERGE_DEFAULT);
        if (!include)
            goto err;

        file = XkbFileCreate(type, NULL, (ParseCommon *) include, 0);
        if (!file) {
            FreeInclude(include);
            goto err;
        }

        if (!defs)
            defsLast = defs = &file->common;
        else
            defsLast = defsLast->next = &file->common;
    }

    file = XkbFileCreate(FILE_TYPE_KEYMAP, NULL, defs, 0);
    if (!file)
        goto err;

    return file;

err:
    FreeXkbFile((XkbFile *) defs);
    return NULL;
}

static void
FreeInclude(IncludeStmt *incl)
{
    IncludeStmt *next;

    while (incl)
    {
        next = incl->next_incl;

        free(incl->file);
        free(incl->map);
        free(incl->modifier);
        free(incl->stmt);

        free(incl);
        incl = next;
    }
}

void
FreeStmt(ParseCommon *stmt)
{
    ParseCommon *next;

    while (stmt)
    {
        next = stmt->next;

        switch (stmt->type) {
        case STMT_INCLUDE:
            FreeInclude((IncludeStmt *) stmt);
            /* stmt is already free'd here. */
            stmt = NULL;
            break;

        case STMT_EXPR_NEGATE:
        case STMT_EXPR_UNARY_PLUS:
        case STMT_EXPR_NOT:
        case STMT_EXPR_INVERT:
            FreeStmt((ParseCommon *) ((ExprUnary *) stmt)->child);
            break;

        case STMT_EXPR_DIVIDE:
        case STMT_EXPR_ADD:
        case STMT_EXPR_SUBTRACT:
        case STMT_EXPR_MULTIPLY:
        case STMT_EXPR_ASSIGN:
            FreeStmt((ParseCommon *) ((ExprBinary *) stmt)->left);
            FreeStmt((ParseCommon *) ((ExprBinary *) stmt)->right);
            break;

        case STMT_EXPR_ACTION_DECL:
            FreeStmt((ParseCommon *) ((ExprAction *) stmt)->args);
            break;

        case STMT_EXPR_ACTION_LIST:
            FreeStmt((ParseCommon *) ((ExprActionList *) stmt)->actions);
            break;

        case STMT_EXPR_ARRAY_REF:
            FreeStmt((ParseCommon *) ((ExprArrayRef *) stmt)->entry);
            break;

        case STMT_EXPR_KEYSYM_LIST:
            darray_free(((ExprKeysymList *) stmt)->syms);
            break;

        case STMT_VAR:
            FreeStmt((ParseCommon *) ((VarDef *) stmt)->name);
            FreeStmt((ParseCommon *) ((VarDef *) stmt)->value);
            break;
        case STMT_TYPE:
            FreeStmt((ParseCommon *) ((KeyTypeDef *) stmt)->body);
            break;
        case STMT_INTERP:
            FreeStmt((ParseCommon *) ((InterpDef *) stmt)->match);
            FreeStmt((ParseCommon *) ((InterpDef *) stmt)->def);
            break;
        case STMT_VMOD:
            FreeStmt((ParseCommon *) ((VModDef *) stmt)->value);
            break;
        case STMT_SYMBOLS:
            FreeStmt((ParseCommon *) ((SymbolsDef *) stmt)->symbols);
            break;
        case STMT_MODMAP:
            FreeStmt((ParseCommon *) ((ModMapDef *) stmt)->keys);
            break;
        case STMT_GROUP_COMPAT:
            FreeStmt((ParseCommon *) ((GroupCompatDef *) stmt)->def);
            break;
        case STMT_LED_MAP:
            FreeStmt((ParseCommon *) ((LedMapDef *) stmt)->body);
            break;
        case STMT_LED_NAME:
            FreeStmt((ParseCommon *) ((LedNameDef *) stmt)->name);
            break;
        default:
            break;
        }

        free(stmt);
        stmt = next;
    }
}

void
FreeXkbFile(XkbFile *file)
{
    XkbFile *next;

    while (file)
    {
        next = (XkbFile *) file->common.next;

        switch (file->file_type) {
        case FILE_TYPE_KEYMAP:
            FreeXkbFile((XkbFile *) file->defs);
            break;

        case FILE_TYPE_TYPES:
        case FILE_TYPE_COMPAT:
        case FILE_TYPE_SYMBOLS:
        case FILE_TYPE_KEYCODES:
        case FILE_TYPE_GEOMETRY:
            FreeStmt(file->defs);
            break;

        default:
            break;
        }

        free(file->name);
        free(file);
        file = next;
    }
}

static const char *xkb_file_type_strings[_FILE_TYPE_NUM_ENTRIES] = {
    [FILE_TYPE_KEYCODES] = "xkb_keycodes",
    [FILE_TYPE_TYPES] = "xkb_types",
    [FILE_TYPE_COMPAT] = "xkb_compatibility",
    [FILE_TYPE_SYMBOLS] = "xkb_symbols",
    [FILE_TYPE_GEOMETRY] = "xkb_geometry",
    [FILE_TYPE_KEYMAP] = "xkb_keymap",
    [FILE_TYPE_RULES] = "rules",
};

const char *
xkb_file_type_to_string(enum xkb_file_type type)
{
    if (type >= _FILE_TYPE_NUM_ENTRIES)
        return "unknown";
    return xkb_file_type_strings[type];
}

static const char *stmt_type_strings[_STMT_NUM_VALUES] = {
    [STMT_UNKNOWN] = "unknown statement",
    [STMT_INCLUDE] = "include statement",
    [STMT_KEYCODE] = "key name definition",
    [STMT_ALIAS] = "key alias definition",
    [STMT_EXPR_STRING_LITERAL] = "string literal expression",
    [STMT_EXPR_INTEGER_LITERAL] = "integer literal expression",
    [STMT_EXPR_FLOAT_LITERAL] = "float literal expression",
    [STMT_EXPR_BOOLEAN_LITERAL] = "boolean literal expression",
    [STMT_EXPR_KEYNAME_LITERAL] = "key name expression",
    [STMT_EXPR_KEYSYM_LITERAL] = "keysym expression",
    [STMT_EXPR_IDENT] = "identifier expression",
    [STMT_EXPR_ACTION_DECL] = "action declaration expression",
    [STMT_EXPR_FIELD_REF] = "field reference expression",
    [STMT_EXPR_ARRAY_REF] = "array reference expression",
    [STMT_EXPR_EMPTY_LIST] = "empty list expression",
    [STMT_EXPR_KEYSYM_LIST] = "keysym list expression",
    [STMT_EXPR_ACTION_LIST] = "action list expression",
    [STMT_EXPR_ADD] = "addition expression",
    [STMT_EXPR_SUBTRACT] = "substraction expression",
    [STMT_EXPR_MULTIPLY] = "multiplication expression",
    [STMT_EXPR_DIVIDE] = "division expression",
    [STMT_EXPR_ASSIGN] = "assignment expression",
    [STMT_EXPR_NOT] = "logical negation expression",
    [STMT_EXPR_NEGATE] = "arithmetic negation expression",
    [STMT_EXPR_INVERT] = "bitwise inversion expression",
    [STMT_EXPR_UNARY_PLUS] = "unary plus expression",
    [STMT_VAR] = "variable definition",
    [STMT_TYPE] = "key type definition",
    [STMT_INTERP] = "symbol interpretation definition",
    [STMT_VMOD] = "virtual modifiers definition",
    [STMT_SYMBOLS] = "key symbols definition",
    [STMT_MODMAP] = "modifier map declaration",
    [STMT_GROUP_COMPAT] = "group declaration",
    [STMT_LED_MAP] = "indicator map declaration",
    [STMT_LED_NAME] = "indicator name declaration",
};

const char *
stmt_type_to_string(enum stmt_type type)
{
    if (type >= _STMT_NUM_VALUES)
        return NULL;
    return stmt_type_strings[type];
}

char
stmt_type_to_operator_char(enum stmt_type type)
{
    switch (type) {
    case STMT_EXPR_ADD:
        return '+';
    case STMT_EXPR_SUBTRACT:
        return '-';
    case STMT_EXPR_MULTIPLY:
        return '*';
    case STMT_EXPR_DIVIDE:
        return '/';
    case STMT_EXPR_NOT:
        return '!';
    case STMT_EXPR_NEGATE:
        return '-';
    case STMT_EXPR_INVERT:
        return '~';
    case STMT_EXPR_UNARY_PLUS:
        return '+';
    default:
        return '\0';
    }
}
