/*
 * Ibis - IRCv3 Library
 * Copyright (C) 2022-2025 Ibis Developers <devel@pidgin.im>
 *
 * Ibis is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library 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 library 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 library; if not, see <https://www.gnu.org/licenses/>.
 */

#include "ibischanmodes.h"

#include "ibisstring.h"

struct _IbisChanmodes {
	GObject parent;

	GHashTable *cache;
};

/******************************************************************************
 * Helpers
 *****************************************************************************/
static inline void
ibis_chanmodes_add_mode(IbisChanmodes *chanmodes, char mode,
                        IbisChanmodeType type)
{
	g_hash_table_insert(chanmodes->cache, GINT_TO_POINTER(mode),
	                    GINT_TO_POINTER(type));
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(IbisChanmodes, ibis_chanmodes, G_TYPE_OBJECT)

static void
ibis_chanmodes_finalize(GObject *obj) {
	IbisChanmodes *chanmodes = IBIS_CHANMODES(obj);

	g_clear_pointer(&chanmodes->cache, g_hash_table_unref);

	G_OBJECT_CLASS(ibis_chanmodes_parent_class)->finalize(obj);
}

static void
ibis_chanmodes_init(IbisChanmodes *chanmodes) {
	chanmodes->cache = g_hash_table_new(g_direct_hash, g_direct_equal);
}

static void
ibis_chanmodes_class_init(IbisChanmodesClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->finalize = ibis_chanmodes_finalize;
}

/******************************************************************************
 * Public API
 *****************************************************************************/
IbisChanmodes *
ibis_chanmodes_new(void) {
	IbisChanmodes *chanmodes = g_object_new(IBIS_TYPE_CHANMODES, NULL);

	/* This list is based on the chanmodes as defined in RFCs 1459 and 2811. */
	ibis_chanmodes_add_mode(chanmodes, 'a', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 'b', IBIS_CHANMODE_TYPE_A);
	ibis_chanmodes_add_mode(chanmodes, 'I', IBIS_CHANMODE_TYPE_A);
	ibis_chanmodes_add_mode(chanmodes, 'i', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 'k', IBIS_CHANMODE_TYPE_B);
	ibis_chanmodes_add_mode(chanmodes, 'l', IBIS_CHANMODE_TYPE_C);
	ibis_chanmodes_add_mode(chanmodes, 'm', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 'n', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 'o', IBIS_CHANMODE_TYPE_B);
	ibis_chanmodes_add_mode(chanmodes, 'p', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 'q', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 'r', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 's', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 't', IBIS_CHANMODE_TYPE_D);
	ibis_chanmodes_add_mode(chanmodes, 'v', IBIS_CHANMODE_TYPE_B);

	return chanmodes;
}

IbisChanmodes *
ibis_chanmodes_parse(const char *chanmodes, const char *prefix_modes) {
	IbisChanmodes *instance = NULL;
	IbisChanmodeType type = IBIS_CHANMODE_TYPE_A;
	GStrv types = NULL;

	g_return_val_if_fail(!ibis_str_is_empty(chanmodes), NULL);
	g_return_val_if_fail(!ibis_str_is_empty(prefix_modes), NULL);

	instance = g_object_new(IBIS_TYPE_CHANMODES, NULL);

	/* Run through the chanmodes value. */
	types = g_strsplit(chanmodes, ",", -1);
	for(guint i = 0; types[i] != NULL; i++) {
		for(char *p = types[i]; *p != '\0'; p++) {
			ibis_chanmodes_add_mode(instance, *p, type);
		}

		if(type == IBIS_CHANMODE_TYPE_D) {
			break;
		}
		type += 1;
	}

	g_strfreev(types);

	/* Run through the prefix modes. */
	for(const char *p = prefix_modes; *p != '\0'; p++) {
		ibis_chanmodes_add_mode(instance, *p, IBIS_CHANMODE_TYPE_B);
	}

	return instance;
}

IbisChanmodeType
ibis_chanmodes_get_chanmode_type(IbisChanmodes *chanmodes, char mode) {
	gpointer value = NULL;

	g_return_val_if_fail(IBIS_IS_CHANMODES(chanmodes),
	                     IBIS_CHANMODE_TYPE_UNKNOWN);

	value = g_hash_table_lookup(chanmodes->cache, GINT_TO_POINTER(mode));

	return GPOINTER_TO_INT(value);
}
