/*
 * SPDX-FileCopyrightText: 2003-2026 Sébastien Helleu <flashcode@flashtux.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * This file is part of WeeChat, the extensible chat client.
 *
 * WeeChat 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 3 of the License, or
 * (at your option) any later version.
 *
 * WeeChat 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 WeeChat.  If not, see <https://www.gnu.org/licenses/>.
 */

/* Nick functions (used by all GUI) */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include "../core/weechat.h"
#include "../core/core-config.h"
#include "../core/core-hashtable.h"
#include "../core/core-string.h"
#include "../core/core-utf8.h"
#include "gui-nick.h"
#include "gui-color.h"


/*
 * Hash a string with a variant of djb2 hash, using 64-bit integer.
 *
 * Number pointed by *color_64 is updated by the function.
 */

void
gui_nick_hash_djb2_64 (const char *nickname, uint64_t *color_64)
{
    while (nickname && nickname[0])
    {
        *color_64 ^= (*color_64 << 5) + (*color_64 >> 2) +
            utf8_char_int (nickname);
        nickname = utf8_next_char (nickname);
    }
}

/*
 * Hash a string with a variant of djb2 hash, using 32-bit integer.
 *
 * Number pointed by *color_32 is updated by the function.
 */

void
gui_nick_hash_djb2_32 (const char *nickname, uint32_t *color_32)
{
    while (nickname && nickname[0])
    {
        *color_32 ^= (*color_32 << 5) + (*color_32 >> 2) +
            utf8_char_int (nickname);
        nickname = utf8_next_char (nickname);
    }
}

/*
 * Hash a string with sum of letters, using 64-bit integer.
 *
 * Number pointed by *color_64 is updated by the function.
 */

void
gui_nick_hash_sum_64 (const char *nickname, uint64_t *color_64)
{
    while (nickname && nickname[0])
    {
        *color_64 += utf8_char_int (nickname);
        nickname = utf8_next_char (nickname);
    }
}

/*
 * Hash a string with sum of letters, using 32-bit integer.
 *
 * Number pointed by *color_32 is updated by the function.
 */

void
gui_nick_hash_sum_32 (const char *nickname, uint32_t *color_32)
{
    while (nickname && nickname[0])
    {
        *color_32 += utf8_char_int (nickname);
        nickname = utf8_next_char (nickname);
    }
}

/*
 * Hash a nickname to find color.
 *
 * Return a number which is between 0 and num_colors - 1 (inclusive).
 *
 * num_colors is commonly the number of colors in the option
 * "weechat.color.chat_nick_colors".
 * If num_colors is < 0, the hash itself is returned (64-bit unsigned number).
 */

uint64_t
gui_nick_hash_color (const char *nickname, int num_colors)
{
    const char *ptr_salt;
    uint64_t color_64;
    uint32_t color_32;

    if (!nickname || !nickname[0])
        return 0;

    if (num_colors == 0)
        return 0;

    ptr_salt = CONFIG_STRING(config_look_nick_color_hash_salt);

    color_64 = 0;

    switch (CONFIG_ENUM(config_look_nick_color_hash))
    {
        case CONFIG_LOOK_NICK_COLOR_HASH_DJB2:
            /* variant of djb2 hash, using 64-bit integer */
            color_64 = 5381;
            gui_nick_hash_djb2_64 (ptr_salt, &color_64);
            gui_nick_hash_djb2_64 (nickname, &color_64);
            break;
        case CONFIG_LOOK_NICK_COLOR_HASH_SUM:
            /* sum of letters, using 64-bit integer  */
            color_64 = 0;
            gui_nick_hash_sum_64 (ptr_salt, &color_64);
            gui_nick_hash_sum_64 (nickname, &color_64);
            break;
        case CONFIG_LOOK_NICK_COLOR_HASH_DJB2_32:
            /* variant of djb2 hash, using 32-bit integer */
            color_32 = 5381;
            gui_nick_hash_djb2_32 (ptr_salt, &color_32);
            gui_nick_hash_djb2_32 (nickname, &color_32);
            color_64 = color_32;
            break;
        case CONFIG_LOOK_NICK_COLOR_HASH_SUM_32:
            /* sum of letters, using 32-bit integer */
            color_32 = 0;
            gui_nick_hash_sum_32 (ptr_salt, &color_32);
            gui_nick_hash_sum_32 (nickname, &color_32);
            color_64 = color_32;
            break;
    }

    return (num_colors > 0) ? color_64 % num_colors : color_64;
}

/*
 * Get forced color for a nick.
 *
 * Return the name of color (for example: "green"), NULL if no color is forced
 * for nick.
 */

const char *
gui_nick_get_forced_color (const char *nickname)
{
    const char *forced_color;
    char *nick_lower;

    if (!nickname || !nickname[0])
        return NULL;

    forced_color = hashtable_get (config_hashtable_nick_color_force, nickname);
    if (forced_color)
        return forced_color;

    nick_lower = string_tolower (nickname);
    if (nick_lower)
    {
        forced_color = hashtable_get (config_hashtable_nick_color_force,
                                      nick_lower);
        free (nick_lower);
    }

    return forced_color;
}

/*
 * Duplicate a nick and stops at first char in list (using option
 * weechat.look.nick_color_stop_chars).
 *
 * Note: result must be freed after use.
 */

char *
gui_nick_strdup_for_color (const char *nickname)
{
    int char_size, other_char_seen;
    char *result, *pos, utf_char[16];

    if (!nickname)
        return NULL;

    result = malloc (strlen (nickname) + 1);
    pos = result;
    other_char_seen = 0;
    while (nickname[0])
    {
        char_size = utf8_char_size (nickname);
        memcpy (utf_char, nickname, char_size);
        utf_char[char_size] = '\0';

        if (strstr (CONFIG_STRING(config_look_nick_color_stop_chars),
                    utf_char))
        {
            if (other_char_seen)
            {
                pos[0] = '\0';
                return result;
            }
        }
        else
        {
            other_char_seen = 1;
        }
        memcpy (pos, utf_char, char_size);
        pos += char_size;

        nickname += char_size;
    }
    pos[0] = '\0';
    return result;
}

/*
 * Find a color name for a nick (according to nick letters).
 *
 * If case_range < 0, nick is case-sensitive.
 * If case_range == 0, nick is converted to lower case (with string_tolower).
 * If case_range > 0, nick is converted to lower case (with string_tolower_range).
 *
 * If colors is NULL (most common case), the color returned is either a forced
 * color (from option "weechat.look.nick_color_force") or a color from option
 * "weechat.color.chat_nick_colors".
 *
 * If colors is set and not empty, a color from this list is returned
 * (format of argument: comma-separated list of colors, a background is
 * allowed with format "fg:bg", for example: "blue,yellow:red" for blue and
 * yellow on red).
 *
 * Return the name of a color (for example: "green").
 *
 * Note: result must be freed after use.
 */

char *
gui_nick_find_color_name (const char *nickname, int case_range,
                          const char *colors)
{
    int color, num_colors;
    char *nickname2, *nickname3, **list_colors, *result;
    const char *forced_color, *ptr_result;
    static char *default_color = "default";

    list_colors = NULL;
    num_colors = 0;
    nickname2 = NULL;
    nickname3 = NULL;
    ptr_result = NULL;

    if (!nickname || !nickname[0])
        goto end;

    if (colors && colors[0])
    {
        list_colors = string_split (colors, ",", NULL, 0, 0, &num_colors);
        if (!list_colors || (num_colors == 0))
            goto end;
    }

    nickname2 = gui_nick_strdup_for_color (nickname);
    if (!nickname2)
        goto end;

    if (!list_colors)
    {
        /* look if color is forced for the nick */
        forced_color = gui_nick_get_forced_color (nickname2);
        if (forced_color)
        {
            ptr_result = forced_color;
            goto end;
        }
        /* ensure nick colors are properly set */
        if (!config_nick_colors)
            config_set_nick_colors ();
        if (config_num_nick_colors == 0)
            goto end;
    }

    if (case_range < 0)
        nickname3 = strdup (nickname2);
    else if (case_range == 0)
        nickname3 = string_tolower (nickname2);
    else
        nickname3 = string_tolower_range (nickname2, case_range);
    if (!nickname3)
        goto end;

    /* hash nickname to get color */
    color = gui_nick_hash_color (
        nickname3,
        (list_colors) ? num_colors : config_num_nick_colors);
    ptr_result = (list_colors) ?
        list_colors[color] : config_nick_colors[color];

end:
    result = strdup ((ptr_result) ? ptr_result : default_color);
    string_free_split (list_colors);
    free (nickname2);
    free (nickname3);
    return result;
}

/*
 * Find a color code for a nick (according to nick letters).
 *
 * If case_range < 0, nick is case-sensitive.
 * If case_range == 0, nick is converted to lower case (with string_tolower).
 * If case_range > 0, nick is converted to lower case (with string_tolower_range).
 *
 * If colors is NULL (most common case), the color returned is either a forced
 * color (from option "weechat.look.nick_color_force") or a color from option
 * "weechat.color.chat_nick_colors".
 *
 * If colors is set and not empty, a color from this list is returned
 * (format of argument: comma-separated list of colors, a background is
 * allowed with format "fg:bg", for example: "blue,yellow:red" for blue and
 * yellow on red).
 *
 * Return a WeeChat color code (that can be used for display).
 *
 * Note: result must be freed after use.
 */

char *
gui_nick_find_color (const char *nickname, int case_range, const char *colors)
{
    char *color;
    const char *ptr_result;

    color = gui_nick_find_color_name (nickname, case_range, colors);
    ptr_result = gui_color_get_custom (color);
    free (color);
    return (ptr_result) ? strdup (ptr_result) : NULL;
}
