/*
 * Copyright (c) 2003-2007 by FlashCode <flashcode@flashtux.org>
 * See README for License detail, AUTHORS for developers list.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

/* weechat-charset.c: Charset plugin for WeeChat */

#include <stdio.h>
#include <stdlib.h>
#define __USE_GNU
#include <string.h>
#include <iconv.h>

#include "../weechat-plugin.h"
#include "weechat-charset.h"


char *weechat_charset_terminal = NULL;
char *weechat_charset_internal = NULL;

/* set to 1 by /charset debug (hidden option) */
int weechat_charset_debug = 0;


/*
 * weechat_charset_strndup: define strndup function if not existing (FreeBSD and maybe other)
 */

char *
weechat_charset_strndup (char *string, int length)
{
    char *result;
    
    if ((int)strlen (string) < length)
        return strdup (string);
    
    result = (char *)malloc (length + 1);
    if (!result)
        return NULL;
    
    memcpy (result, string, length);
    result[length] = '\0';
    
    return result;
}

/*
 * weechat_charset_default_decode: set "global.decode" option if needed
 */

void
weechat_charset_default_decode (t_weechat_plugin *plugin)
{
    char *global_decode;
    int rc;
    
    global_decode = plugin->get_plugin_config (plugin, "global.decode");
    
    /* if global decode is not set, we may set it, depending on terminal charset */
    if (!global_decode || !global_decode[0])
    {
        /* set global decode charset to terminal if different from internal */
        /* otherwise use ISO-8859-1 */
        if (weechat_charset_terminal && weechat_charset_internal
            && (strcasecmp (weechat_charset_terminal, weechat_charset_internal) != 0))
            rc = plugin->set_plugin_config (plugin,
                                            "global.decode",
                                            weechat_charset_terminal);
        else
            rc = plugin->set_plugin_config (plugin,
                                            "global.decode",
                                            "ISO-8859-1");
        if (rc)
            plugin->print_server (plugin,
                                  "Charset: setting \"charset.global.decode\" to %s",
                                  weechat_charset_terminal);
        else
            plugin->print_server (plugin,
                                  "Charset: failed to set \"charset.global.decode\" option.");
    }
    if (global_decode)
        free (global_decode);
}

/*
 * weechat_charset_check: check if a charset is valid
 *                        if a charset is NULL, internal charset is used
 */

int
weechat_charset_check (char *charset)
{
    iconv_t cd;

    if (!charset || !charset[0])
        return 0;
    
    cd = iconv_open (charset, weechat_charset_internal);
    if (cd == (iconv_t)(-1))
        return 0;
    
    iconv_close (cd);
    return 1;
}

/*
 * weechat_charset_get_config: read a charset in config file
 *                             we first in this order: channel, server, global
 */

char *
weechat_charset_get_config (t_weechat_plugin *plugin,
                            char *type, char *server, char *channel)
{
    static char option[1024];
    char *value;

    /* we try channel first */
    if (server && channel)
    {
        snprintf (option, sizeof (option) - 1, "%s.%s.%s", type, server, channel);
        value = plugin->get_plugin_config (plugin, option);
        if (value && value[0])
            return value;
        if (value)
            free (value);
    }
    
    /* channel not found, we try server only */
    if (server)
    {
        snprintf (option, sizeof (option) - 1, "%s.%s", type, server);
        value = plugin->get_plugin_config (plugin, option);
        if (value && value[0])
            return value;
        if (value)
            free (value);
    }
    
    /* nothing found, we try global charset */
    snprintf (option, sizeof (option) - 1, "global.%s", type);
    value = plugin->get_plugin_config (plugin, option);
    if (value && value[0])
        return value;
    if (value)
        free (value);
    
    /* nothing found => no decode/encode for this message! */
    return NULL;
}

/*
 * weechat_charset_set_config: set a charset in config file
 */

void
weechat_charset_set_config (t_weechat_plugin *plugin,
                            char *type, char *server, char *channel, char *value)
{
    static char option[1024];
    
    if (server && channel)
        snprintf (option, sizeof (option) - 1, "%s.%s.%s", type, server, channel);
    else if (server)
        snprintf (option, sizeof (option) - 1, "%s.%s", type, server);
    else
        return;

    plugin->set_plugin_config (plugin, option, value);
}

/*
 * weechat_charset_parse_irc_msg: return nick, command, channel and position
 *                                of arguments in IRC message
 */

void
weechat_charset_parse_irc_msg (char *message, char **nick, char **command,
                               char **channel, char **pos_args)
{
    char *pos, *pos2, *pos3, *pos4, *pos_tmp;
    
    *nick = NULL;
    *command = NULL;
    *channel = NULL;
    *pos_args = NULL;
    
    if (message[0] == ':')
    {
        pos = message + 1;
        pos_tmp = strchr (pos, ' ');
        if (pos_tmp)
            pos_tmp[0] = '\0';
        pos2 = strchr (pos, '!');
        if (pos2)
            *nick = weechat_charset_strndup (pos, pos2 - pos);
        else
        {
            pos2 = strchr (pos, ' ');
            if (pos2)
                *nick = weechat_charset_strndup (pos, pos2 - pos);
        }
        if (pos_tmp)
            pos_tmp[0] = ' ';
        pos = strchr (message, ' ');
        if (!pos)
            pos = message;
    }
    else
        pos = message;
    
    if (pos && pos[0])
    {
        while (pos[0] == ' ')
            pos++;
        pos2 = strchr (pos, ' ');
        if (pos2)
        {
            *command = weechat_charset_strndup (pos, pos2 - pos);
            pos2++;
            while (pos2[0] == ' ')
                pos2++;
            *pos_args = pos2;
            if (pos2[0] != ':')
            {
                if ((pos2[0] == '#') || (pos2[0] == '&')
                    || (pos2[0] == '+') || (pos2[0] == '!'))
                {
                    pos3 = strchr (pos2, ' ');
                    if (pos3)
                        *channel = weechat_charset_strndup (pos2, pos3 - pos2);
                    else
                        *channel = strdup (pos2);
                }
                else
                {
                    pos3 = strchr (pos2, ' ');
                    if (!*nick)
                    {
                        if (pos3)
                            *nick = weechat_charset_strndup (pos2, pos3 - pos2);
                        else
                            *nick = strdup (pos2);
                    }
                    if (pos3)
                    {
                        pos3++;
                        while (pos3[0] == ' ')
                            pos3++;
                        if ((pos3[0] == '#') || (pos3[0] == '&')
                            || (pos3[0] == '+') || (pos3[0] == '!'))
                        {
                            pos4 = strchr (pos3, ' ');
                            if (pos4)
                                *channel = weechat_charset_strndup (pos3, pos4 - pos3);
                            else
                                *channel = strdup (pos3);
                        }
                    }
                }
            }
        }
    }
}

/*
 * weechat_charset_irc_in: transform charset for incoming messages
 *                         convert from any charset to WeeChat internal
 */

char *
weechat_charset_irc_in (t_weechat_plugin *plugin, int argc, char **argv,
                        char *handler_args, void *handler_pointer)
{
    char *nick, *command, *channel, *charset, *ptr_args;
    char *output;
    
    /* make C compiler happy */
    (void) argc;
    (void) handler_args;
    (void) handler_pointer;
    
    output = NULL;
    
    weechat_charset_parse_irc_msg (argv[1], &nick, &command, &channel, &ptr_args);
    
    charset = weechat_charset_get_config (plugin,
                                          "decode", argv[0],
                                          (channel) ? channel : nick);
    
    if (weechat_charset_debug)
        plugin->print(plugin, NULL, NULL,
                      "Charset IN: srv='%s', nick='%s', chan='%s', "
                      "msg='%s', ptr_args='%s' => charset: %s",
                      argv[0], nick, channel, argv[1], ptr_args, charset);
    
    if (charset)
    {
        output = plugin->iconv_to_internal (plugin, charset, argv[1]);
        if (charset)
            free (charset);
    }
    
    if (nick)
        free (nick);
    if (command)
        free (command);
    if (channel)
        free (channel);
    
    return output;
}

/*
 * weechat_charset_irc_out: transform charset for outgoing messages
 *                          convert from WeeChat internal charset to other
 */

char *
weechat_charset_irc_out (t_weechat_plugin *plugin, int argc, char **argv,
                         char *handler_args, void *handler_pointer)
{
    char *nick, *command, *channel, *charset, *ptr_args;
    char *output;
    
    /* make C compiler happy */
    (void) argc;
    (void) handler_args;
    (void) handler_pointer;
    
    output = NULL;
    
    weechat_charset_parse_irc_msg (argv[1], &nick, &command, &channel, &ptr_args);
    
    charset = weechat_charset_get_config (plugin,
                                          "encode", argv[0],
                                          (channel) ? channel : nick);
    
    if (weechat_charset_debug)
        plugin->print(plugin, NULL, NULL,
                      "Charset OUT: srv='%s', nick='%s', chan='%s', "
                      "msg='%s', ptr_args='%s' => charset: %s",
                      argv[0], nick, channel, argv[1], ptr_args, charset);

    if (charset)
    {
        output = plugin->iconv_from_internal (plugin, charset, argv[1]);
        if (charset)
            free (charset);
    }
    
    if (nick)
        free (nick);
    if (command)
        free (command);
    if (channel)
        free (channel);
    
    return output;
}

/*
 * weechat_charset_display: display charsets (global/server/channel)
 */

void
weechat_charset_display (t_weechat_plugin *plugin,
                         int display_on_server, char *server, char *channel)
{
    char *decode, *encode;
    static char option[1024];

    decode = NULL;
    encode = NULL;
    
    /* display global settings */
    if (!server && !channel)
    {
        decode = plugin->get_plugin_config (plugin, "global.decode");
        encode = plugin->get_plugin_config (plugin, "global.encode");

        if (display_on_server)
            plugin->print_server (plugin,
                                  "Charset: global charsets: decode = %s, encode = %s",
                                  (decode) ? decode : "(none)",
                                  (encode) ? encode : "(none)");
        else
            plugin->print (plugin, NULL, NULL,
                           "Charset: global charsets: decode = %s, encode = %s",
                           (decode) ? decode : "(none)",
                           (encode) ? encode : "(none)");
    }
    
    /* display server settings */
    if (server && !channel)
    {
        snprintf (option, sizeof (option) - 1, "decode.%s", server);
        decode = plugin->get_plugin_config (plugin, option);
        snprintf (option, sizeof (option) - 1, "encode.%s", server);
        encode = plugin->get_plugin_config (plugin, option);

        if (display_on_server)
            plugin->print_server (plugin,
                                  "Charset: decode / encode charset for server %s: %s / %s",
                                  server,
                                  (decode) ? decode : "(none)",
                                  (encode) ? encode : "(none)");
        else
            plugin->print (plugin, NULL, NULL,
                           "Charset: decode / encode charset for server %s: %s / %s",
                           server,
                           (decode) ? decode : "(none)",
                           (encode) ? encode : "(none)");
    }
    
    /* display chan/nick settings */
    if (server && channel)
    {
        snprintf (option, sizeof (option) - 1, "decode.%s.%s", server, channel);
        decode = plugin->get_plugin_config (plugin, option);
        snprintf (option, sizeof (option) - 1, "encode.%s.%s", server, channel);
        encode = plugin->get_plugin_config (plugin, option);

        if (display_on_server)
            plugin->print_server (plugin,
                                  "Charset: decode / encode charset for %s/%s: %s / %s",
                                  server, channel,
                                  (decode) ? decode : "(none)",
                                  (encode) ? encode : "(none)");
        else
            plugin->print (plugin, NULL, NULL,
                           "Charset: decode / encode charset for %s/%s: %s / %s",
                           server, channel,
                           (decode) ? decode : "(none)",
                           (encode) ? encode : "(none)");
    }
    
    if (decode)
        free (decode);
    if (encode)
        free (encode);
}

/*
 * weechat_charset_cmd: /charset command
 */

int
weechat_charset_cmd (t_weechat_plugin *plugin,
                     int cmd_argc, char **cmd_argv,
                     char *handler_args, void *handler_pointer)
{
    int argc;
    char **argv, *server, *channel;
    
    if (cmd_argc < 3)
        return PLUGIN_RC_KO;
    
    /* make C compiler happy */
    (void) handler_args;
    (void) handler_pointer;
    
    if (cmd_argv[2])
        argv = plugin->explode_string (plugin, cmd_argv[2], " ", 0, &argc);
    else
    {
        argv = NULL;
        argc = 0;
    }
    
    /* get command context */
    server = plugin->get_info (plugin, "server", NULL);
    channel = plugin->get_info (plugin, "channel", NULL);
    
    switch (argc)
    {
        case 0:
            plugin->print_server (plugin, "");
            weechat_charset_display (plugin, 1, NULL, NULL);
            weechat_charset_display (plugin, 1, server, NULL);
            if (channel)
                weechat_charset_display (plugin, 1, server, channel);
            break;
        case 1:
            if (strcasecmp (argv[0], "decode") == 0)
            {
                weechat_charset_set_config (plugin, "decode", server, channel, NULL);
                weechat_charset_display (plugin, 0, server, channel);
            }
            else if (strcasecmp (argv[0], "encode") == 0)
            {
                weechat_charset_set_config (plugin, "encode", server, channel, NULL);
                weechat_charset_display (plugin, 0, server, channel);
            }
            else if (strcasecmp (argv[0], "debug") == 0)
            {
                weechat_charset_debug ^= 1;
                plugin->print (plugin, NULL, NULL,
                               "Charset: debug [%s].",
                               (weechat_charset_debug) ? "ON" : "off");
            }
            else if (strcasecmp (argv[0], "reset") == 0)
            {
                weechat_charset_set_config (plugin, "decode", server, channel, NULL);
                weechat_charset_set_config (plugin, "encode", server, channel, NULL);
                weechat_charset_display (plugin, 0, server, channel);
            }
            else
            {
                if (!weechat_charset_check (argv[0]))
                    plugin->print_server (plugin,
                                          "Charset error: invalid charset \"%s\"",
                                          argv[0]);
                else
                {
                    weechat_charset_set_config (plugin, "decode", server, channel, argv[0]);
                    weechat_charset_set_config (plugin, "encode", server, channel, argv[0]);
                    weechat_charset_display (plugin, 0, server, channel);
                }
            }
            break;
        case 2:
            if (!weechat_charset_check (argv[1]))
                plugin->print_server (plugin,
                                      "Charset error: invalid charset \"%s\"",
                                      argv[1]);
            else
            {
                if (strcasecmp (argv[0], "decode") == 0)
                {
                    weechat_charset_set_config (plugin, "decode", server, channel, argv[1]);
                    weechat_charset_display (plugin, 0, server, channel);
                }
                else if (strcasecmp (argv[0], "encode") == 0)
                {
                    weechat_charset_set_config (plugin, "encode", server, channel, argv[1]);
                    weechat_charset_display (plugin, 0, server, channel);
                }
                else
                    plugin->print_server (plugin,
                                          "Charset error: unknown option \"%s\"",
                                          argv[0]);
            }
            break;
    }

    if (argv)
        plugin->free_exploded_string (plugin, argv);
    if (server)
        free (server);
    if (channel)
        free (channel);
    
    return PLUGIN_RC_OK;
}
    
/*
 * weechat_plugin_init: init charset plugin
 */

int
weechat_plugin_init (t_weechat_plugin *plugin)
{
    t_plugin_modifier *msg_irc_in, *msg_irc_out;
    t_plugin_handler *cmd_handler;

    /* get terminal & internal charsets */
    weechat_charset_terminal = plugin->get_info (plugin, "charset_terminal", NULL);
    weechat_charset_internal = plugin->get_info (plugin, "charset_internal", NULL);

    /* display message */
    plugin->print_server (plugin, "Charset plugin starting, terminal charset: %s (WeeChat internal: %s)",
                          weechat_charset_terminal, weechat_charset_internal);
    
    /* set global default decode charset */
    weechat_charset_default_decode (plugin);
    
    /* add command handler */
    cmd_handler = plugin->cmd_handler_add (plugin, "charset",
                                           "Charset management by server or channel",
                                           "[[decode | encode] charset] | [reset]",
                                           " decode: set a decoding charset for server/channel\n"
                                           " encode: set an encofing charset for server/channel\n"
                                           "charset: the charset for decoding or encoding messages\n"
                                           "  reset: reset charsets for server/channel\n\n"
                                           "To set global decode/encode charset (for all servers), use /setp charset.global.decode "
                                           "or /setp charset.global.encode\n"
                                           "To see all charsets for all servers, use /setp charset",
                                           "decode|encode|reset",
                                           &weechat_charset_cmd,
                                           NULL, NULL);
    
    /* add messge modifier */
    msg_irc_in = plugin->modifier_add (plugin, "irc_in", "*",
                                       &weechat_charset_irc_in,
                                       NULL, NULL);
    msg_irc_out = plugin->modifier_add (plugin, "irc_out", "*",
                                        &weechat_charset_irc_out,
                                        NULL, NULL);
    
    return PLUGIN_RC_OK;
}

/*
 * weechat_plugin_end: end charset plugin
 */

void
weechat_plugin_end (t_weechat_plugin *plugin)
{
    /* make C compiler happy */
    (void) plugin;
    
    if (weechat_charset_terminal)
        free (weechat_charset_terminal);
    if (weechat_charset_internal)
        free (weechat_charset_internal);
}


syntax highlighted by Code2HTML, v. 0.9.1