/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * See the COPYING file for license information.
 *
 * Guillaume Chazarain <guichaz@yahoo.fr>
 */

/*****************************************
 * Try to add unique mnemonics to labels *
 *****************************************/

#include <string.h>             /* strlen(), memcpy() */

#include "gliv.h"
#include "mnemonics.h"
#include "str_utils.h"

static GSList *stack = NULL;
static GHashTable *mnemonics = NULL;

static gint count_underscores(const gchar * str, gint len)
{
    gint nb_underscores = 0;
    gulong himagic, lomagic, magic_bits;
    const gulong *long_ptr;
    gulong mask;

    while ((gulong) str & (sizeof(gulong) - 1))
        switch (*str) {
        case '\0':
            return nb_underscores;

        case '_':
            nb_underscores++;
            /* Fall through */

        default:
            str++;
            len--;
        }

    long_ptr = (gulong *) str;
    INIT_MAGIC();

    /* '_' == 0x5F */
    mask = 0x5F5F5F5F;
    if (sizeof(gulong) > 4)
        /* 64-bit */
        mask = ((mask << 16) << 16) | mask;

    while (len >= sizeof(gulong)) {
        gulong masked;

        masked = *long_ptr ^ mask;
        if (HAS_ZERO(masked)) {
            /* A '_' has been detected. */
            gchar *char_ptr = (gchar *) & masked;
            nb_underscores += (*char_ptr++ == 0);
            nb_underscores += (*char_ptr++ == 0);
            nb_underscores += (*char_ptr++ == 0);
            nb_underscores += (*char_ptr++ == 0);
            if (sizeof(gulong) > 4) {
                /* 64-bit */
                nb_underscores += (*char_ptr++ == 0);
                nb_underscores += (*char_ptr++ == 0);
                nb_underscores += (*char_ptr++ == 0);
                nb_underscores += (*char_ptr++ == 0);
            }
        }

        long_ptr++;
        len -= sizeof(gulong);
    }

    str = (const gchar *) long_ptr;
    while (len > 0) {
        nb_underscores += (*str == '_');
        str++;
        len--;
    }

    return nb_underscores;
}

static const gchar *find_mnemonic_position(const gchar * str)
{
    const gchar *ptr;

    for (ptr = str; *ptr != '\0'; ptr = g_utf8_next_char(ptr)) {
        gunichar ch = g_unichar_tolower(g_utf8_get_char(ptr));
        gpointer ch_key = GINT_TO_POINTER(ch);

        if (g_unichar_isalnum(ch)) {
            if (mnemonics == NULL) {
                mnemonics = g_hash_table_new(g_direct_hash, g_direct_equal);
                g_hash_table_insert(mnemonics, ch_key, mnemonics);
                return ptr;
            }

            if (g_hash_table_lookup(mnemonics, ch_key) == NULL) {
                g_hash_table_insert(mnemonics, ch_key, mnemonics);
                return ptr;
            }
        }
    }

    /* No position found, add in front. */
    return str;
}

/*
 * Underscores in labels are replaced with mnemonics, so we duplicate them.
 * The caller has to determine whether the returned string has to be freed.
 */
static gchar *duplicate_underscores(const gchar * orig)
{
    gint nb_underscores, len;
    const gchar *ptr_orig;
    gchar *new, *ptr_new;

    /* How many underscores? */
    len = strlen(orig);
    nb_underscores = count_underscores(orig, len);
    len += nb_underscores;

    if (nb_underscores == 0)
        return (gchar *) orig;

    ptr_new = new = g_new(gchar, len + 1);

    for (ptr_orig = orig; *ptr_orig != '\0'; ptr_orig++) {
        *ptr_new = *ptr_orig;
        if (*ptr_orig == '_') {
            /* Duplicate this one. */
            ptr_new++;
            *ptr_new = '_';
        }
        ptr_new++;
    }

    *ptr_new = '\0';

    return new;
}

/*
 * The returned string should not be freed.  We keep track of already used
 * mnemonics to avoid giving twice the same letter.
 */
const gchar *add_mnemonic(const gchar * str)
{
    static gchar *result = NULL;
    static gint size = 0;
    const gchar *mnemonic_pos, *end;
    gint new_size, offset;
    gchar *work;

    work = duplicate_underscores(str);
    mnemonic_pos = find_mnemonic_position(work);
    end = mnemonic_pos + strlen(mnemonic_pos);

    /* + 2: '_' and '\0'. */
    new_size = end - work + 2;
    if (new_size > size) {
        size = new_size;
        g_free(result);

        result = g_new(gchar, size);
    }

    offset = mnemonic_pos - work;
    memcpy(result, work, offset);
    result[offset] = '_';
    memcpy(result + offset + 1, mnemonic_pos, end - mnemonic_pos + 1);

    if (work != str)
        g_free(work);

    return result;
}

void reset_mnemonics(void)
{
    if (mnemonics != NULL) {
        g_hash_table_destroy(mnemonics);
        mnemonics = NULL;
    }
}

void push_mnemonics(void)
{
    stack = g_slist_prepend(stack, mnemonics);
    mnemonics = NULL;
}

void pop_mnemonics(void)
{
    reset_mnemonics();
    mnemonics = g_slist_nth_data(stack, 0);
    stack = g_slist_delete_link(stack, stack);
}
