/* 
 * Copyright (C) 2004-2009 Georgy Yunaev gyunaev@ulduzsoft.com
 *
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser 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 Lesser General Public 
 * License for more details.
 */

#include <ctype.h>

#define LIBIRC_COLORPARSER_BOLD			(1<<1)
#define LIBIRC_COLORPARSER_UNDERLINE	(1<<2)
#define LIBIRC_COLORPARSER_REVERSE		(1<<3)
#define LIBIRC_COLORPARSER_COLOR		(1<<4)

#define LIBIRC_COLORPARSER_MAXCOLORS	15


static const char * color_replacement_table[] =
{
	"WHITE",
	"BLACK",
	"DARKBLUE",
	"DARKGREEN",
	"RED",
	"BROWN",
	"PURPLE",
	"OLIVE",
	"YELLOW",
	"GREEN",
	"TEAL",
	"CYAN",
	"BLUE",
	"MAGENTA",
	"DARKGRAY",
	"LIGHTGRAY",
	0
};


static inline void libirc_colorparser_addorcat (char ** destline, unsigned int * destlen, const char * str)
{
	unsigned int len = strlen(str);

	if ( *destline )
	{
		strcpy (*destline, str);
		*destline += len;
	}
	else
		*destlen += len;
}


static void libirc_colorparser_applymask (unsigned int * mask, 
		char ** destline, unsigned int * destlen,
		unsigned int bitmask, const char * start, const char * end)
{
	if ( (*mask & bitmask) != 0 )
	{
		*mask &= ~bitmask;
		libirc_colorparser_addorcat (destline, destlen, end);
	}
	else
	{
		*mask |= bitmask;
		libirc_colorparser_addorcat (destline, destlen, start);
	}
}


static void libirc_colorparser_applycolor (unsigned int * mask, 
		char ** destline, unsigned int * destlen,
		unsigned int colorid, unsigned int bgcolorid)
{
	const char * end = "[/COLOR]";
	char startbuf[64];

	if ( bgcolorid != 0 )
		sprintf (startbuf, "[COLOR=%s/%s]", color_replacement_table[colorid], color_replacement_table[bgcolorid]);
	else
		sprintf (startbuf, "[COLOR=%s]", color_replacement_table[colorid]);

	if ( (*mask & LIBIRC_COLORPARSER_COLOR) != 0 )
		libirc_colorparser_addorcat (destline, destlen, end);

	*mask |= LIBIRC_COLORPARSER_COLOR;
	libirc_colorparser_addorcat (destline, destlen, startbuf);
}


static void libirc_colorparser_closetags (unsigned int * mask, 
		char ** destline, unsigned int * destlen)
{
	if ( *mask & LIBIRC_COLORPARSER_BOLD )
		libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_BOLD, 0, "[/B]");

	if ( *mask & LIBIRC_COLORPARSER_UNDERLINE )
		libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_UNDERLINE, 0, "[/U]");

	if ( *mask & LIBIRC_COLORPARSER_REVERSE )
		libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_REVERSE, 0, "[/I]");

	if ( *mask & LIBIRC_COLORPARSER_COLOR )
		libirc_colorparser_applymask (mask, destline, destlen, LIBIRC_COLORPARSER_COLOR, 0, "[/COLOR]");
}



/*
 * IRC to [code] color conversion. Or strip.
 */
static char * libirc_colorparser_irc2code (const char * source, int strip)
{
	unsigned int mask = 0, destlen = 0;
	char * destline = 0, *d = 0;
	const char *p;
	int current_color = 1, current_bg = 0;

    /*
     * There will be two passes. First pass calculates the total length of
     * the destination string. The second pass allocates memory for the string,
     * and fills it.
     */
	while ( destline == 0 ) // destline will be set after the 2nd pass
	{
		if ( destlen > 0 )
		{
			// This is the 2nd pass; allocate memory.
			if ( (destline = malloc (destlen)) == 0 )
				return 0;

			d = destline;
		}

		for ( p = source; *p; p++ )
		{
			switch (*p)
			{
			case 0x02:	// bold
				if ( strip )
					continue;

				libirc_colorparser_applymask (&mask, &d, &destlen, LIBIRC_COLORPARSER_BOLD, "[B]", "[/B]");
				break;
				
			case 0x1F:	// underline
				if ( strip )
					continue;

				libirc_colorparser_applymask (&mask, &d, &destlen, LIBIRC_COLORPARSER_UNDERLINE, "[U]", "[/U]");
				break;

			case 0x16:	// reverse
				if ( strip )
					continue;

				libirc_colorparser_applymask (&mask, &d, &destlen, LIBIRC_COLORPARSER_REVERSE, "[I]", "[/I]");
				break;

			case 0x0F:	// reset colors
				if ( strip )
					continue;

				libirc_colorparser_closetags (&mask, &d, &destlen);
				break;

			case 0x03:	// set color
				if ( isdigit (p[1]) )
				{
					// Parse 
					int bgcolor = -1, color = p[1] - 0x30;
					p++;

					if ( isdigit (p[1]) )
					{
						color = color * 10 + (p[1] - 0x30);
						p++;
					}

					// If there is a comma, search for the following 
					// background color
					if ( p[1] == ',' && isdigit (p[2]) )
					{
						bgcolor = p[2] - 0x30;
						p += 2;

						if ( isdigit (p[1]) )
						{
							bgcolor = bgcolor * 10 + (p[1] - 0x30);
							p++;
						}
					}

					// Check for range
					if ( color <= LIBIRC_COLORPARSER_MAXCOLORS 
					&& bgcolor <= LIBIRC_COLORPARSER_MAXCOLORS )
					{
						if ( strip )
							continue;

						current_color = color;
						
						if ( bgcolor != -1 )
							current_bg = bgcolor;

						libirc_colorparser_applycolor (&mask, &d, &destlen, color, current_bg);
					}
				}
				break;

			default:
				if ( destline )
					*d++ = *p;
				else
					destlen++;
				break;
			}
		}

		// Close all the opened tags
		libirc_colorparser_closetags (&mask, &d, &destlen);
		destlen++; // for 0-terminator
	}

	*d = '\0';
	return destline;
}


static int libirc_colorparser_colorlookup (const char * color)
{
	int i;
	for ( i = 0; color_replacement_table[i]; i++ )
		if ( !strcmp (color, color_replacement_table[i]) )
			return i;

	return -1;
}


/*
 * [code] to IRC color conversion.
 */
char * irc_color_convert_to_mirc (const char * source)
{
	unsigned int destlen = 0;
	char * destline = 0, *d = 0;
	const char *p1, *p2, *cur;

    /*
     * There will be two passes. First pass calculates the total length of
     * the destination string. The second pass allocates memory for the string,
     * and fills it.
     */
	while ( destline == 0 ) // destline will be set after the 2nd pass
	{
		if ( destlen > 0 )
		{
			// This is the 2nd pass; allocate memory.
			if ( (destline = malloc (destlen)) == 0 )
				return 0;

			d = destline;
		}

		cur = source;
		while ( (p1 = strchr (cur, '[')) != 0 )
		{
			const char * replacedval = 0;
			p2 = 0;

			// Check if the closing bracket is available after p1
			// and the tag length is suitable
			if ( p1[1] != '\0' 
			&& (p2 = strchr (p1, ']')) != 0
			&& (p2 - p1) > 1
			&& (p2 - p1) < 31 )
			{
				// Get the tag
				char tagbuf[32];
				int taglen = p2 - p1 - 1;

				memcpy (tagbuf, p1 + 1, taglen);
				tagbuf[taglen] = '\0';

				if ( !strcmp (tagbuf, "/COLOR") )
					replacedval = "\x0F";
				else if ( strstr (tagbuf, "COLOR=") == tagbuf )
				{
					int color, bgcolor = -2;
					char * bcol;

					bcol = strchr (tagbuf + 6, '/');

					if ( bcol )
					{
						*bcol++ = '\0';
						bgcolor = libirc_colorparser_colorlookup (bcol);
					}

					color = libirc_colorparser_colorlookup (tagbuf + 6);

					if ( color != -1 && bgcolor == -2 )
					{
						sprintf (tagbuf, "\x03%02d", color);
						replacedval = tagbuf;
					}
					else if ( color != -1 && bgcolor >= 0 )
					{
						sprintf (tagbuf, "\x03%02d,%02d", color, bgcolor);
						replacedval = tagbuf;
					}
				}
				else if ( !strcmp (tagbuf, "B") || !strcmp (tagbuf, "/B") )
					replacedval = "\x02";
				else if ( !strcmp (tagbuf, "U") || !strcmp (tagbuf, "/U") )
					replacedval = "\x1F";
				else if ( !strcmp (tagbuf, "I") || !strcmp (tagbuf, "/I") )
					replacedval = "\x16";
			}

			if ( replacedval )
			{
				// add a part before the tag
				int partlen = p1 - cur;

				if ( destline )
				{
					memcpy (d, cur, partlen);
					d += partlen;
				}
				else
					destlen += partlen;

				// Add the replacement
				libirc_colorparser_addorcat (&d, &destlen, replacedval);

				// And move the pointer
				cur = p2 + 1;
			}
			else
			{
				// add a whole part before the end tag
				int partlen;

				if ( !p2 )
					p2 = cur + strlen(cur);

				partlen = p2 - cur + 1;

				if ( destline )
				{
					memcpy (d, cur, partlen);
					d += partlen;
				}
				else
					destlen += partlen;

				// And move the pointer
				cur = p2 + 1;
			}
		}

		// Add the rest of string
		libirc_colorparser_addorcat (&d, &destlen, cur);
		destlen++; // for 0-terminator
	}

	*d = '\0';
	return destline;
}


char * irc_color_strip_from_mirc (const char * message)
{
	return libirc_colorparser_irc2code (message, 1);
}


char * irc_color_convert_from_mirc (const char * message)
{
	return libirc_colorparser_irc2code (message, 0);
}
