/*
 * Adds a command to roll an arbitrary number of dice with an arbitrary 
 * number of sides
 * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
 * Copyright (C) 2007 Lucas <reilithion@gmail.com>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
 */

/* If you can't figure out what this line is for, DON'T TOUCH IT. */
#include "../common/pp_internal.h"

#include <time.h>
#include <stdlib.h>

#include <cmds.h>
#include <conversation.h>
#include <debug.h>
#include <plugin.h>

#define DEFAULT_DICE	2
#define DEFAULT_SIDES	6

#define BOUNDS_CHECK(var, min, min_def, max, max_def) { \
	if((var) < (min)) \
		(var) = (min_def); \
	else if((var) > (max)) \
		(var) = (max_def); \
}

#define ROUND(val) ((gdouble)(val) + 0.5f)

static PurpleCmdId dice_cmd_id = 0;

static gchar *
old_school_roll(gint dice, gint sides) {
	GString *str = g_string_new("");
	gchar *ret = NULL;
	gint c = 0, v = 0;

	BOUNDS_CHECK(dice, 1, 2, 15, 15);
	BOUNDS_CHECK(sides, 2, 2, 999, 999);

	g_string_append_printf(str, "%d %d-sided %s:",
						   dice, sides,
						   (dice == 1) ? "die" : "dice");

	for(c = 0; c < dice; c++) {
		v = rand() % sides + 1;

		g_string_append_printf(str, " %d", v);
	}

	ret = str->str;
	g_string_free(str, FALSE);

	return ret;
}

static inline gboolean
is_dice_notation(const gchar *str) {
	return (g_utf8_strchr(str, -1, 'd') != NULL);
}

static gchar *
dice_notation_roll_helper(const gchar *dn, gint *value) {
	GString *str = g_string_new("");
	gchar *ret = NULL, *ms = NULL;
	gchar op = '\0';
	gint dice = 0, sides = 0, i = 0, t = 0, v = 0;
	gdouble multiplier = 1.0;

	if(!dn || *dn == '\0')
		return NULL;

	/* at this point, all we have is +/- number for our bonus, so we add it to
	 * our value
	 */
	if(!is_dice_notation(dn)) {
		gint bonus = atoi(dn);

		*value += bonus;

		/* the + makes sure we always have a + or - */
		g_string_append_printf(str, "%s %d",
							   (bonus < 0) ? "-" : "+",
							   ABS(bonus));

		ret = str->str;
		g_string_free(str, FALSE);

		return ret;
	}

	/**************************************************************************
	 * Process our block
	 *************************************************************************/
	purple_debug_info("dice", "processing '%s'\n", dn);

	/* get the number of dice */
	dice = atoi(dn);
	BOUNDS_CHECK(dice, 1, 1, 999, 999);

	/* find and move to the character after the d */
	dn = g_utf8_strchr(dn, -1, 'd');
	dn++;

	/* get the number of sides */
	sides = atoi(dn);
	BOUNDS_CHECK(sides, 2, 2, 999, 999);

	/* i've struggled with a better way to determine the next operator, i've 
	 * opted for this.
	 */
	for(t = sides; t > 0; t /= 10) {
		dn++;
		purple_debug_info("dice", "looking for the next operator: %s\n", dn);
	}

	purple_debug_info("dice", "next operator: %s\n", dn);

	/* check if we're multiplying or dividing this block */
	if(*dn == 'x' || *dn == '/') {
		op = *dn;
		dn++;

		multiplier = v = atof(dn);

		ms = g_strdup_printf("%d", (gint)multiplier);

		/* move past our multiplier */
		for(t = v; t > 0; t /= 10) {
			purple_debug_info("dice", "moving past the multiplier: %s\n", dn);
			dn++;
		}

		if(op == '/')
			multiplier = 1 / multiplier;
	}

	purple_debug_info("dice", "d=%d;s=%d;m=%f;\n", dice, sides, multiplier);

	/* calculate and output our block */
	g_string_append_printf(str, " (");
	for(i = 0; i < dice; i++) {
		t = rand() % sides + 1;
		v = ROUND(t * multiplier);

		g_string_append_printf(str, "%s%d", (i > 0) ? " " : "", t);
		
		purple_debug_info("dice", "die %d: %d(%d)\n", i, v, t);

		*value += v;
	}

	g_string_append_printf(str, ")");

	/* if we have a multiplier, we need to output it as well */
	if(multiplier != 1.0)
		g_string_append_printf(str, "%c(%s)", op, ms);

	/* free our string of the multiplier */
	g_free(ms);

	purple_debug_info("dice", "value=%d;str=%s\n", *value, str->str);

	/* we have more in our string, recurse! */
	if(*dn != '\0') {
		gchar *s = dice_notation_roll_helper(dn, value);
		
		if(s)
			str = g_string_append(str, s);

		g_free(s);
	}

	ret = str->str;
	g_string_free(str, FALSE);

	return ret;
}

static gchar *
dice_notation_roll(const gchar *dn) {
	GString *str = g_string_new("");
	gchar *ret = NULL, *normalized = NULL;
	gint value = 0;

	g_string_append_printf(str, "%s:", dn);

	/* normalize the input and process it */
	normalized = g_utf8_strdown(dn, -1);
	g_string_append_printf(str, "%s",
						   dice_notation_roll_helper(normalized, &value));
	g_free(normalized);

	g_string_append_printf(str, " = %d", value);

	ret = str->str;
	g_string_free(str, FALSE);

	return ret;
}

static PurpleCmdRet
roll(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar *error,
	 void *data)
{
	PurpleCmdStatus ret;
	gchar *str = NULL, *newcmd = NULL;

	if(!args[0]) {
		str = old_school_roll(DEFAULT_DICE, DEFAULT_SIDES);
	} else {
		if(is_dice_notation(args[0])) {
			str = dice_notation_roll(args[0]);
		} else {
			gint dice, sides;
			
			dice = atoi(args[0]);
			sides = (args[1]) ? atoi(args[1]) : DEFAULT_SIDES;

			str = old_school_roll(dice, sides);
		}
	}

#if 0
			i = 1;	/* Abuse that iterator!  We're saying "We think this is dice notation!" */
			splitted = g_strsplit(args[0], "d", 2);	/* Split the description into two parts: (1)d(20+5); discard the 'd'. */
			dice = atoi(splitted[0]);	/* We should have the number of dice easily now. */
			if(g_strstr_len(splitted[1], -1, "+") != NULL)	/* If our second half contained a '+' (20+5) */
			{
				resplitted = g_strsplit(splitted[1], "+", 2);	/* Split again: (20)+(5); discard the '+'. */
				sides = atoi(resplitted[0]);	/* Number of sides on the left. */
				bonus += atoi(resplitted[1]);	/* Bonus on the right. */
				g_strfreev(resplitted);			/* Free memory from the split. */
			}
			else if(g_strstr_len(splitted[1], -1, "-") != NULL)	/* If our second half contained a '-' (20-3) */
			{
				resplitted = g_strsplit(splitted[1], "-", 2);	/* Split again: (20)-(3); discard the '-'. */
				sides = atoi(resplitted[0]);	/* Number of sides on the left. */
				bonus -= atoi(resplitted[1]);	/* Penalty on the right. */
				g_strfreev(resplitted);			/* Free memory from the split. */
			}
			else	/* There was neither a '+' nor a '-' in the second half. */
				sides = atoi(splitted[1]);	/* We're assuming it's just a number, then.  Number of sides. */
			g_strfreev(splitted);	/* Free the original split. */
		}
	}

	if(args[1] && i == 0)	/* If there was a second argument, and we care about it (not dice notation) */
		sides = atoi(args[1]);	/* Grab it and make it the number of sides the dice have. */

	str = g_string_new("");
	if(i)	/* Show the output in dice notation format. */
	{
		g_string_append_printf(str, "%dd%d", dice, sides);	/* For example, 1d20 */
		if(bonus > 0)
			g_string_append_printf(str, "+%d", bonus);	/* 1d20+5 */
		else if(bonus < 0)
			g_string_append_printf(str, "%d", bonus);	/* 1d20-3 (saying "-%d" would be redundant, since the '-' gets output with bonus automatically) */
		g_string_append_printf(str, ":");	/* Final colon.  1d20-4: */
	}

	for(i = 0; i < dice; i++)	/* For each die... */
	{
		roll = rand() % sides + 1;	/* Roll, and add bonus. */
		accumulator += roll;			/* Accumulate our rolls */
		g_string_append_printf(str, " %d", roll);	/* Append the result of our roll to our output string. */
	}
	
	if(bonus != 0)	/* If we had a bonus */
	{
		accumulator += bonus;	/* Accumulate our bonus/penalty */
		g_string_append_printf(str, " %s%d = %d", (bonus < 0) ? "penalty " : "bonus +", bonus, accumulator); /* Append our bonus/penalty to the output string */
	}
	else if(dice > 1)	/* Or if we had more than one die */
	{
		g_string_append_printf(str, " = %d", accumulator);	/* Append our accumulator */
	}
#endif

	newcmd = g_strdup_printf("me rolls %s", str);

	ret = purple_cmd_do_command(conv, newcmd, newcmd, &error);

	g_free(str);
	g_free(newcmd);

	return ret;
}

static gboolean
plugin_load(PurplePlugin *plugin)
{
	const gchar *help;

	help = _("dice [dice] [sides]:  rolls dice number of sides sided dice OR\n"
			 "dice [XdY+-Z]:  rolls X number of Y sided dice, giving a Z "
			 "bonus/penalty to each.  e.g. 1d20+2");

	dice_cmd_id = purple_cmd_register("dice", "wws", PURPLE_CMD_P_PLUGIN,
									PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
									PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
									NULL, PURPLE_CMD_FUNC(roll),
									help, NULL);

	/* we only want to seed this off of the seconds since the epoch once.  If
	 * we do it every time, we'll give the same results for each time we
	 * process a roll within the same second.  This is bad because it's not
	 * really random then.
	 */
	srand(time(NULL));

	return TRUE;
}

static gboolean
plugin_unload(PurplePlugin *plugin)
{
	purple_cmd_unregister(dice_cmd_id);

	return TRUE;
}

static PurplePluginInfo info =
{
	PURPLE_PLUGIN_MAGIC,
	PURPLE_MAJOR_VERSION,
	PURPLE_MINOR_VERSION,
	PURPLE_PLUGIN_STANDARD,	
	NULL,
	0,
	NULL,
	PURPLE_PRIORITY_DEFAULT,

	"core-plugin_pack-dice",
	NULL,
	PP_VERSION,	
	NULL,
	NULL,
	"Gary Kramlich <grim@reaperworld.com>",	
	PP_WEBSITE,	

	plugin_load,
	plugin_unload,
	NULL,

	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};

static void
init_plugin(PurplePlugin *plugin)
{
#ifdef ENABLE_NLS
	bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
#endif /* ENABLE_NLS */

	info.name = _("Dice");
	info.summary = _("Rolls dice in a chat or im");
	info.description = _("Adds a command (/dice) to roll an arbitrary "
						 "number of dice with an arbitrary number of sides. "
						 "Now supports dice notation!  /help dice for details");

}

PURPLE_INIT_PLUGIN(dice, init_plugin, info)
