/* ====================================================================
 * FTD4Linux Software License, Version 1.0
 *
 * Copyright (c) 2000-2004 The FTD4Linux Development Team.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        FTD4Linux Development Team (http://www.ftd4linux.2y.net/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "FTD" and "FTD4Linux" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact alguno@ftd4linux.2y.net.
 *
 * 5. Products derived from this software may not be called "FTD4Linux",
 *    nor may "FTD4Linux" appear in their name, without prior written
 *    permission of the FTD4Linux Development Team.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE FTD4LINUX DEVELOPMENT TEAM OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <glib.h>
#include <errno.h>
#include <glib-object.h>

#include "ftd4linux.h"
#include "ftd_irc.h"
#include "ftd_irc_socket.h"

#define DEBUG 0

typedef struct FTD_IRCObjectClass FTD_IRCObjectClass;

FTD_IRCObject *ftd_irc_get_object(void);
GType ftd_irc_object_get_type (void);

static void parse_irc_message (const char *regel, int length, gboolean is_error, gpointer data);

struct FTD_IRCObject
{
    GObject parent;

    GStaticRWLock rwlock;

    FtdIRCState state;
    FTD_IRCSocket *socket;
    char *errmsg;

    char *nickname;
    char *altNickname;
    char *autoJoinChannel;
    char *server;
    gboolean isUsingAltNickname;

    // FIXME: onderstaande omdopen tot GObject signal's
    gpointer data;

    void (*state_changed_cb) (FTD_IRCObject *obj, FtdIRCState old_state, const char *errmsg, gpointer data);
    void (*on_msg_received_cb) (FTD_IRCObject *obj, const char *source, const char *channel_name, const char *msg, gboolean is_action, gboolean is_privmsg, gpointer data);
    void (*on_join_cb) (FTD_IRCObject *obj, const char *channel_name, const FtdList *users, const char *topic, gpointer data);
    void (*on_user_join_part_cb) (FTD_IRCObject *irc_data, const char *username, const char *channel_name, const char *reason, gboolean has_joined);
    void (*on_mode_change_cb) (FTD_IRCObject *irc_data, const char *change_performed_by, const char *username, char *mode);
    void (*on_nick_change_cb) (FTD_IRCObject *irc_data, const char *old_nickname, const char *new_nickname);
};

struct FTD_IRCObjectClass
{
    GObjectClass parent;
};

enum
{
  BRING_TO_FRONT_SIGNAL,
  OPEN_POSTING_SIGNAL,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(FTD_IRCObject, ftd_irc_object, G_TYPE_OBJECT)

static void
ftd_irc_object_init (FTD_IRCObject *obj)
{
    g_static_rw_lock_init(&obj->rwlock);

    obj->server = g_strdup("irc.ftd.nu");
    obj->nickname = NULL;
    obj->altNickname = NULL;
    obj->autoJoinChannel = g_strdup("#ftdhulp");
    obj->isUsingAltNickname = FALSE;
    obj->state = IRC_STATE_DISCONNECTED;
    obj->socket = ftd_irc_socket_new();
    obj->errmsg = NULL;

    ftd_irc_socket_set_read_callback(obj->socket, parse_irc_message, obj);
}

static void
irc_finalize (GObject *obj)
{
    FTD_IRCObject *irc = FTD_IRC_OBJECT(obj);

    if (irc->server) {
        g_free(irc->server);
        irc->server = NULL;
    }

    if (irc->nickname) {
        g_free(irc->nickname);
        irc->nickname = NULL;
    }

    if (irc->altNickname) {
        g_free(irc->altNickname);
        irc->altNickname = NULL;
    }

    if (irc->autoJoinChannel) {
        g_free(irc->autoJoinChannel);
        irc->autoJoinChannel = NULL;
    }

    ftd_irc_socket_destroy(irc->socket);

    g_static_rw_lock_free(&irc->rwlock);
}

static void
ftd_irc_object_class_init (FTD_IRCObjectClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->finalize = irc_finalize;

#if 0
    signals[BRING_TO_FRONT_SIGNAL] = g_signal_new  ("bring_to_front",
                                                    G_OBJECT_CLASS_TYPE (klass),
                                                    G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                                                    0,
                                                    NULL, NULL,
                                                    g_cclosure_marshal_VOID__VOID,
                                                    G_TYPE_NONE, 0);

    signals[OPEN_POSTING_SIGNAL] = g_signal_new    ("open_posting",
                                                    G_OBJECT_CLASS_TYPE (klass),
                                                    G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                                                    0,
                                                    NULL, NULL,
                                                    g_cclosure_marshal_VOID__INT,
                                                    G_TYPE_NONE, 1, G_TYPE_INT);
#endif
}


static char *strip_color (char *text)
{
    int nc = 0;
    int i = 0;
    int col = 0;
    int len = strlen (text);
    char *new_str = g_malloc (len + 2);

    while (len > 0)
    {
        if ((col && isdigit (*text) && nc < 2) ||
            (col && *text == ',' && isdigit (*(text+1)) && nc < 3)) {

            nc++;
            if (*text == ',') {
                nc = 0;
            }
        } else {
            col = 0;
            switch (*text) {
                case '\003':                      /*ATTR_COLOR: */
                    col = 1;
                    nc = 0;
                    break;

                case '\007':                      /*ATTR_BEEP: */
                case '\017':                      /*ATTR_RESET: */
                case '\026':                      /*ATTR_REVERSE: */
                case '\002':                      /*ATTR_BOLD: */
                case '\037':                      /*ATTR_UNDERLINE: */
                    break;

                default:
                    new_str[i] = *text;
                    i++;
            }
        }

        text++;
        len--;
    }

    new_str[i] = 0;

    return new_str;
}

static void
parse_irc_message (const char *regel, int length, gboolean is_error, gpointer data)
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(data);
    char *response;
    char PONG[256];
    char msg[4096];
    char nickname[256];
    char channel_name[256];
    char mode[256];
    int ret = 0;
    int num;

    memset(msg, 0, sizeof(msg));

    if (is_error) {
        irc_data->errmsg = g_strdup(regel);
        ftd_irc_object_disconnect(irc_data);
        return;
    }

#ifdef DEBUG
    g_print("received data = %s\n", regel);
#endif

    ret = sscanf(regel, "%*s %i %*s", &num);
    if (ret == 1) {
        static char *subject = NULL;
        static FtdList *users = NULL;
        gchar **values;
        int i;
        FtdList *list;

        response = NULL;

        switch(num) {
            case 001:
                // :terneuzen.irc.ftd4linux.nl 001 Alguno_FTD4Linux :Welcome to the FTD4Linux IRC Network
                // We kunnen joinen
                if (irc_data->autoJoinChannel) {
                    response = g_strdup_printf("JOIN %s\r\n", irc_data->autoJoinChannel);
                }

                irc_data->state = IRC_STATE_CONNECTED;
                if (irc_data->state_changed_cb) {
                    irc_data->state_changed_cb(irc_data, IRC_STATE_CONNECTING, NULL, irc_data->data);
                }

                break;

            case 332:
                // :terneuzen.irc.ftd4linux.nl 332 Alguno_FTD4Linux #tlx :Dit is een subject
                ret = sscanf(regel, "%*s %*i %*s %*s :%255[^\n]", &msg);
                subject = g_strdup(msg);
                break;

            case 353:
                // :terneuzen.irc.ftd4linux 353 Alguno_FTD4Linux = #tlx :Alguno_FTD4Linux Squall[PG] |Marnux| @Icheb n0ne X @Alguno FrodoBaggins slazh @FTD_Bot
                ret = sscanf(regel, "%*s %*i %*s = %*s :%4095[^\n]", &msg);
                values = g_strsplit(msg, " ", 0);
                i = 0;
                while (values[i]) {
                    if (strlen(values[i]) > 0) {
                        users = ftd_list_append(users, g_strdup(values[i]));
                    }

                    i++;
                }
                g_strfreev(values);
                break;

            case 366:
                // :terneuzen.irc.ftd4linux 366 Alguno_FTD4Linux #tlx: End of /NAMES list.
                ret = sscanf(regel, "%*s %*i %*s %s: %s[^\n]", &channel_name);
                if (irc_data->on_join_cb) {
                    irc_data->on_join_cb(irc_data, channel_name, users, subject, irc_data->data);
                }

                list = users;
                while (list) {
                    g_free(ftd_list_value(list));
                    list = ftd_list_next(list);
                }
                ftd_list_free(users);
                users = NULL;

                g_free(subject);
                subject = NULL;

                break;

            case 433:   // Nickname is already in use
                if (!irc_data->isUsingAltNickname) {
                    irc_data->isUsingAltNickname = TRUE;
                    response = g_strdup_printf("NICK %s\n", irc_data->altNickname);
                } else {
                    irc_data->state = IRC_STATE_DISCONNECTED;
                    if (irc_data->state_changed_cb) {
                        regel = g_strdup_printf("Nicknames '%s' en '%s' zijn reeds in gebruik", irc_data->nickname, irc_data->altNickname);
                        irc_data->state_changed_cb(irc_data, IRC_STATE_CONNECTING, regel, irc_data->data);
                    }
                }

                break;
        }

        if (response) {
#ifdef DEBUG
            printf("response = %s\n", response);
#endif

            ftd_irc_socket_send_msg(irc_data->socket, response, strlen(response));
            g_free(response);
        }
    }

// PING :14CE04
    ret = sscanf(regel, "PING :%s", &PONG);
    if (ret == 1) {
        // Ping-Pong effect
        response = g_strdup_printf("PONG :%s\r\n", PONG);

#ifdef DEBUG
        printf("response = %s\n", response);
#endif

        // En versturen maar
        ftd_irc_socket_send_msg(irc_data->socket, response, strlen(response));
        g_free(response);
    }

// :Alguno!me@ftd4linux.2y.net JOIN :#tlx
    memset(nickname, 0, sizeof(nickname));
    memset(msg, 0, sizeof(msg));
    ret = sscanf(regel, "%s JOIN :%s[^\n]", &nickname, &channel_name);
    if (ret == 2) {
        char *tmp;
        int i, len;
        char *clean_msg;

        // Sloop de nickname
        // :Alguno!me@ftd4linux.xs4all.nl
        tmp = strstr(nickname, "!");
        if (tmp) {
            len = tmp - nickname;
            for (i = 1; i < len; i++) {
                nickname[i - 1] = nickname[i];
            }
            nickname[len - 1] = '\0';
        }

        if (irc_data->on_user_join_part_cb) {
            irc_data->on_user_join_part_cb(irc_data, nickname, channel_name, NULL, TRUE);
        }
    }

// :Alguno!me@ftd4linux.2y.net PART #tlx :Ik ga weg
    memset(nickname, 0, sizeof(nickname));
    memset(msg, 0, sizeof(msg));
    ret = sscanf(regel, "%s PART %s :%255[^\n]", &nickname, &channel_name, &msg);
    if (ret == 3) {
        char *tmp;
        int i, len;
        char *clean_msg;

        // Sloop de nickname
        // :Alguno!me@ftd4linux.xs4all.nl
        tmp = strstr(nickname, "!");
        if (tmp) {
            len = tmp - nickname;
            for (i = 1; i < len; i++) {
                nickname[i - 1] = nickname[i];
            }
            nickname[len - 1] = '\0';
        }

        if (irc_data->on_user_join_part_cb) {
            irc_data->on_user_join_part_cb(irc_data, nickname, channel_name, msg, FALSE);
        }
    }

// :Alguno!me@ftd4linux.2y.net QUIT :Quit:
    memset(nickname, 0, sizeof(nickname));
    memset(msg, 0, sizeof(msg));
    ret = sscanf(regel, "%s QUIT :%255[^\n]", &nickname, &msg);
    if (ret == 3) {
        char *tmp;
        int i, len;
        char *clean_msg;

        // Sloop de nickname
        // :Alguno!me@ftd4linux.xs4all.nl
        tmp = strstr(nickname, "!");
        if (tmp) {
            len = tmp - nickname;
            for (i = 1; i < len; i++) {
                nickname[i - 1] = nickname[i];
            }
            nickname[len - 1] = '\0';
        }

        if (irc_data->on_user_join_part_cb) {
            irc_data->on_user_join_part_cb(irc_data, nickname, NULL, msg, FALSE);
        }
    }

// :Alguno!me@ftd4linux.2y.net NICK :Alguno-laptop
    memset(nickname, 0, sizeof(nickname));
    memset(msg, 0, sizeof(msg));
    ret = sscanf(regel, "%s NICK :%255[^\n]", &nickname, &msg);
    if (ret == 2) {
        char *tmp;
        int i, len;
        char *clean_msg;

        // Sloop de nickname
        // :Alguno!me@ftd4linux.xs4all.nl
        tmp = strstr(nickname, "!");
        if (tmp) {
            len = tmp - nickname;
            for (i = 1; i < len; i++) {
                nickname[i - 1] = nickname[i];
            }
            nickname[len - 1] = '\0';
        }

        if (irc_data->on_nick_change_cb) {
            irc_data->on_nick_change_cb(irc_data, nickname, msg);
        }
    }

// :FTD_Bot!FTD_Bot@localhost MODE #tlx +o Alguno
    memset(nickname, 0, sizeof(nickname));
    memset(msg, 0, sizeof(msg));
    memset(mode, 0, sizeof(mode));
    ret = sscanf(regel, "%s MODE %s %s %255[^\n]", &nickname, &channel_name, &mode, &msg);
    if (ret == 4) {
        char *tmp;
        int i, len;
        char *clean_msg;

        // Sloop de nickname
        // :Alguno!me@ftd4linux.xs4all.nl
        tmp = strstr(nickname, "!");
        if (tmp) {
            len = tmp - nickname;
            for (i = 1; i < len; i++) {
                nickname[i - 1] = nickname[i];
            }
            nickname[len - 1] = '\0';
        }

        if (irc_data->on_mode_change_cb) {
            irc_data->on_mode_change_cb (irc_data, nickname, msg, mode);
        }
    }

// :Alguno!me@ftd4linux.2y.net PRIVMSG #ftd4linux :blaat
    memset(nickname, 0, sizeof(nickname));
    memset(msg, 0, sizeof(msg));
    ret = sscanf(regel, "%s PRIVMSG %s :%255[^\n]", &nickname, &channel_name, &msg);
    if (ret == 3) {
        char *tmp;
        int i, len;
        gboolean is_action = FALSE;
        gboolean is_privmsg;
        char *clean_msg;

        // Is dit bericht een pm bericht ?
        if (channel_name[0] == '#') {
            is_privmsg = FALSE;
        } else {
            is_privmsg = TRUE;
        }

        // Sloop ACTION's
        if (msg[0] == '\001')
        {
            is_action = TRUE;
            for (i = 8; i < strlen(msg) - 1; i++)
                msg[i - 8] = msg[i];

            // Aan het einde van msg staat nog een niet-afdrukbaar teken, vervang deze gelijk door \0
            msg[strlen(msg) - 9] = '\0';
        }

        // Sloop de nickname
        // :Alguno!me@ftd4linux.xs4all.nl
        tmp = strstr(nickname, "!");
        if (tmp) {
            len = tmp - nickname;
            for (i = 1; i < len; i++) {
                nickname[i - 1] = nickname[i];
            }
            nickname[len - 1] = '\0';
        }

        // Ontdoe de message van allerlei exotische opmaak
        clean_msg = strip_color(msg);
        if (irc_data->on_msg_received_cb) {
            irc_data->on_msg_received_cb(irc_data, (is_privmsg ? channel_name : nickname), (is_privmsg ? nickname : channel_name), clean_msg, is_action, is_privmsg, irc_data->data);
        }
        g_free(clean_msg);
    }
}

void
ftd_irc_object_send_msg(FTD_IRCObject *obj, const char *dest, const char *msg, gboolean is_action)
{
    char *command;
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    g_assert(irc_data);

    if (is_action) {
        command = g_strdup_printf("PRIVMSG %s :\001ACTION %s\001\n", dest, msg);
    } else {
        command = g_strdup_printf("PRIVMSG %s %s\n", dest, msg);
    }

    ftd_irc_socket_send_msg(irc_data->socket, command, strlen(command));

    g_free(command);
}

gboolean
ftd_irc_object_connect(FTD_IRCObject *obj)
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);
    char *msg = NULL;

    g_assert(irc_data);
    g_assert(irc_data->nickname);
    g_assert(irc_data->altNickname);
    g_assert(irc_data->server);

    irc_data->state = IRC_STATE_CONNECTING;
    if (irc_data->state_changed_cb) {
        irc_data->state_changed_cb(irc_data, IRC_STATE_DISCONNECTED, NULL, irc_data->data);
    }

    if (!ftd_irc_socket_connect(irc_data->socket, irc_data->server, 6667, &msg)) {
        irc_data->state = IRC_STATE_DISCONNECTED;
        if (irc_data->state_changed_cb) {
            irc_data->state_changed_cb(irc_data, IRC_STATE_CONNECTING, msg, irc_data->data);
        }

        g_free(msg);

        return FALSE;
    }

    msg = g_strdup_printf("NICK %s\nUSER openftd openftd openftd :OpenFTD\r\n", irc_data->nickname);
    ftd_irc_socket_send_msg(irc_data->socket, msg, strlen(msg));
    g_free(msg);

    return TRUE;
}

void
ftd_irc_object_join(FTD_IRCObject *obj, const char *channel_name)
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);
    char *cmd;

    cmd = g_strdup_printf("JOIN %s\r\n", channel_name);
    ftd_irc_socket_send_msg(irc_data->socket, cmd, strlen(cmd));
    g_free(cmd);
}

void
ftd_irc_object_disconnect(FTD_IRCObject *obj)
{
    FtdIRCState old_state;
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    g_assert(irc_data);

    ftd_irc_socket_disconnect(irc_data->socket);

    old_state = irc_data->state;
    irc_data->state = IRC_STATE_DISCONNECTED;

    if (irc_data->state_changed_cb) {
        irc_data->state_changed_cb(irc_data, old_state, irc_data->errmsg, irc_data->data);
    }

    if (irc_data->errmsg) {
        g_free(irc_data->errmsg);
        irc_data->errmsg = NULL;
    }
}

FTD_IRCObject *
ftd_irc_object_new(void)
{
    return g_object_new(FTD_IRC_TYPE_OBJECT, NULL);
}

void
ftd_irc_object_destroy(FTD_IRCObject *obj)
{
    g_object_unref(obj);
}

const char *
ftd_irc_object_get_active_nickname(FTD_IRCObject *obj)
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    if (irc_data->isUsingAltNickname) {
        return irc_data->altNickname;
    } else {
        return irc_data->nickname;
    }
}

void
ftd_irc_object_set_nickname(FTD_IRCObject *obj, const char *nickname)
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    if (irc_data->nickname) {
        g_free(irc_data->nickname);
    }

    irc_data->nickname = g_strdup(nickname);
}

void
ftd_irc_object_set_alt_nickname(FTD_IRCObject *obj, const char *nickname)
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    if (irc_data->altNickname) {
        g_free(irc_data->altNickname);
    }

    irc_data->altNickname = g_strdup(nickname);
}

FtdIRCState
ftd_irc_object_get_state(FTD_IRCObject *obj)
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    return irc_data->state;
}

// FIXME: Moeten GObject signals worden
void
ftd_irc_object_set_state_changed_cb(FTD_IRCObject *obj, void (*state_changed_cb) (FTD_IRCObject *obj, FtdIRCState old_state, const char *errmsg, gpointer data))
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    irc_data->state_changed_cb = state_changed_cb;
}

void
ftd_irc_object_set_msg_received_cb(FTD_IRCObject *obj, void (*on_msg_received_cb) (FTD_IRCObject *obj, const char *source, const char *channel_name, const char *msg, gboolean is_action, gboolean is_privmsg, gpointer data))
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    irc_data->on_msg_received_cb = on_msg_received_cb;
}

void
ftd_irc_object_set_join_cb(FTD_IRCObject *obj, void (*on_join_cb) (FTD_IRCObject *obj, const char *channel_name, const FtdList *users, const char *topic, gpointer data))
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    irc_data->on_join_cb = on_join_cb;
}

void
ftd_irc_object_set_user_join_part_cb(FTD_IRCObject *obj, void (*on_user_join_part_cb) (FTD_IRCObject *irc_data, const char *username, const char *channel, const char *reason, gboolean has_joined))
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    irc_data->on_user_join_part_cb = on_user_join_part_cb;
}

void
ftd_irc_object_set_mode_change_cb(FTD_IRCObject *obj, void (*on_mode_change_cb) (FTD_IRCObject *irc_data, const char *change_performed_by, const char *username, char *mode))
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    irc_data->on_mode_change_cb = on_mode_change_cb;
}

void
ftd_irc_object_set_nick_change_cb(FTD_IRCObject *obj, void (*on_nick_change_cb) (FTD_IRCObject *irc_data, const char *old_nickname, const char *new_nickname))
{
    FTD_IRCObject *irc_data = FTD_IRC_OBJECT(obj);

    irc_data->on_nick_change_cb = on_nick_change_cb;
}
