/*
 * Copyright (c) 1994 by Silicon Graphics Computer Systems, Inc.
 * SPDX-License-Identifier: HPND
 */

#include "config.h"

#include <stdint.h>

#include "xkbcommon/xkbcommon.h"
#include "atom.h"
#include "keymap.h"
#include "text.h"
#include "expr.h"
#include "xkbcomp/ast.h"
#include "vmod.h"

void
InitVMods(struct xkb_mod_set *info, const struct xkb_mod_set *mods, bool reset)
{
    *info = *mods;

    if (!reset)
        return;

    struct xkb_mod *mod;
    xkb_mod_index_t vmod = 0;
    xkb_mods_enumerate(vmod, mod, info) {
        mod->mapping = 0;
    }
    info->explicit_vmods = 0;
}

void
MergeModSets(struct xkb_context *ctx, struct xkb_mod_set *into,
             const struct xkb_mod_set *from, enum merge_mode merge) {
    const bool clobber = (merge != MERGE_AUGMENT);
    xkb_mod_index_t vmod;
    const struct xkb_mod *mod;
    assert(into->num_mods <= from->num_mods);
    xkb_mods_enumerate(vmod, mod, from) {
        const xkb_mod_mask_t mask = UINT32_C(1) << vmod;

        if ((mod->type != MOD_VIRT)) {
            /* No modifier in `from` or real modifier: nothing to do */
            assert((mod->type == 0 &&
                    mod->name == XKB_ATOM_NONE) ||
                   ((mod->type & MOD_REAL) &&
                    into->mods[vmod].type == mod->type &&
                    mod->name != XKB_ATOM_NONE &&
                    into->mods[vmod].name == mod->name));
            continue;
        } else if (into->mods[vmod].type == 0) {
            /* No modifier in `into`: copy the whole modifier definition */
            assert(into->mods[vmod].name == XKB_ATOM_NONE);
            assert(mod->type == MOD_VIRT);
            assert(mod->name != XKB_ATOM_NONE);
            assert(vmod >= into->num_mods);
            into->mods[vmod] = *mod;
            if (from->explicit_vmods & mask)
                into->explicit_vmods |= mask;
        } else {
            /* Modifier exists in both */
            assert(mod->type == MOD_VIRT);
            assert(mod->name != XKB_ATOM_NONE);
            assert(into->mods[vmod].type == mod->type);
            assert(into->mods[vmod].name == mod->name);

            if (!(from->explicit_vmods & mask)) {
                /* Implicit mapping in `from`: do nothing */
                assert(mod->mapping == 0);
            } else if (!(into->explicit_vmods & mask)) {
                /* Implicit mapping in `into`: replace */
                assert(into->mods[vmod].mapping == 0);
                into->mods[vmod].mapping = mod->mapping;
                into->explicit_vmods |= mask;
            } else if (mod->mapping != into->mods[vmod].mapping) {
                /* Handle conflicting mappings */
                const xkb_mod_mask_t use = (clobber)
                    ? mod->mapping
                    : into->mods[vmod].mapping;
                const xkb_mod_mask_t ignore = (clobber)
                    ? into->mods[vmod].mapping
                    : mod->mapping;

                log_warn(ctx, XKB_LOG_MESSAGE_NO_ID,
                         "Virtual modifier %s mapping defined multiple times; "
                         "Using %s, ignoring %s\n",
                         xkb_atom_text(ctx, mod->name),
                         ModMaskText(ctx, MOD_REAL, from, use),
                         ModMaskText(ctx, MOD_REAL, from, ignore));

                into->mods[vmod].mapping = use;
            }
        }
    }
    into->num_mods = from->num_mods;
}

bool
HandleVModDef(struct xkb_context *ctx, struct xkb_mod_set *mods, VModDef *stmt)
{
    xkb_mod_mask_t mapping = 0;
    if (stmt->value) {
        /*
         * This is a statement such as 'virtualModifiers NumLock = Mod1';
         * it initialize the vmod-to-real-mod[s] mapping before going
         * through modifier_map.
         */
        if (!ExprResolveModMask(ctx, stmt->value, MOD_REAL, mods, &mapping)) {
            log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
                    "Declaration of %s ignored\n",
                    xkb_atom_text(ctx, stmt->name));
            return false;
        }
    }

    xkb_mod_index_t vmod;
    struct xkb_mod *mod;
    xkb_mods_enumerate(vmod, mod, mods) {
        if (mod->name == stmt->name) {
            if (mod->type != MOD_VIRT) {
                log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
                        "Can't add a virtual modifier named \"%s\"; there is "
                        "already a non-virtual modifier with this name! "
                        "Ignored\n",
                        xkb_atom_text(ctx, mod->name));
                return false;
            }

            const xkb_mod_mask_t mask = UINT32_C(1) << vmod;

            if (!stmt->value) {
                /* No new explicit mapping: do nothing */
                return true;
            } else if (!(mods->explicit_vmods & mask)) {
                /* No previous explicit mapping: add mapping */
                mod->mapping = mapping;
            } else if (mod->mapping != mapping) {
                /* Handle conflicting mappings */
                const bool clobber = (stmt->merge != MERGE_AUGMENT);
                const xkb_mod_mask_t use =
                    (clobber ? mapping : mod->mapping);
                const xkb_mod_mask_t ignore =
                    (clobber ? mod->mapping : mapping);

                log_warn(ctx, XKB_LOG_MESSAGE_NO_ID,
                         "Virtual modifier %s mapping defined multiple times; "
                         "Using %s, ignoring %s\n",
                         xkb_atom_text(ctx, stmt->name),
                         ModMaskText(ctx, MOD_REAL, mods, use),
                         ModMaskText(ctx, MOD_REAL, mods, ignore));

                mod->mapping = use;
            }

            mods->explicit_vmods |= mask;

            return true;
        }
    }

    if (mods->num_mods >= XKB_MAX_MODS) {
        log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
                "Cannot define virtual modifier %s: "
                "too many modifiers defined (maximum %u)\n",
                xkb_atom_text(ctx, stmt->name), XKB_MAX_MODS);
        return false;
    }

    mods->mods[mods->num_mods].name = stmt->name;
    mods->mods[mods->num_mods].type = MOD_VIRT;
    mods->mods[mods->num_mods].mapping = mapping;
    if (stmt->value) {
        const xkb_mod_mask_t mask = UINT32_C(1) << mods->num_mods;
        mods->explicit_vmods |= mask;
    }
    mods->num_mods++;
    return true;
}
