#include <common.h>

#include "core/network.h"

#define FLOOD_TIMEOUT 1

typedef struct
{
    gchar *name;
    GList *nicks;
}
CHANNEL_REC;

GList *channels;
gchar *clientnick, clienthost[MAX_IP_LEN];

int clienth;

/* Read a line */
gint read_line(gboolean socket, gint handle, GString *output, GString *buffer)
{
    gchar tmpbuf[512];
    gint recvlen, pos;

    g_return_val_if_fail(handle != -1, -1);
    g_return_val_if_fail(output != NULL, -1);
    g_return_val_if_fail(buffer != NULL, -1);

    g_string_truncate(output, 0);

    recvlen = socket ?
        net_receive(handle, tmpbuf, sizeof(tmpbuf)-1) :
        read(handle, tmpbuf, sizeof(tmpbuf)-1);

    if (recvlen <= 0)
    {
        if (buffer->len > 0)
        {
            /* no new data got but still something in buffer.. */
            for (pos = 0; pos < buffer->len; pos++)
            {
                if (buffer->str[pos] == 13 || buffer->str[pos] == 10)
                {
                    recvlen = 0;
                    break;
                }
            }
            if (recvlen < 0 && buffer->len > 0)
            {
                /* connection closed and last line is missing \n ..
                   just add it so we can see if it had anything useful.. */
                recvlen = 0;
                g_string_append_c(buffer, '\n');
            }
        }

        if (recvlen < 0) return -1;
    }
    else
    {
        /* append received data to buffer */
        tmpbuf[recvlen] = '\0';
        g_string_append(buffer, tmpbuf);
    }

    for (pos = 0; pos < buffer->len; pos++)
    {
        if (buffer->str[pos] == 13 || buffer->str[pos] == 10)
        {
            /* end of line */
            buffer->str[pos] = '\0';
            g_string_assign(output, buffer->str);

            if (buffer->str[pos] == 13 && buffer->str[pos+1] == 10)
            {
                /* skip \n too */
                pos++;
            }

            g_string_erase(buffer, 0, pos+1);
            return 1;
        }
    }

    /* EOL wasn't found, wait for more data.. */
    return 0;
}
void client_send(char *text)
{
    write(clienth, text, strlen(text));
    write(clienth, "\r\n", 2);
}

void makerand(char *str, int len)
{
    for (; len > 0; len--)
        *str++ = (rand() % 20)+'A';
}

void makerand2(char *str, int len)
{
#if 0
    gchar c;

    while (len > 0)
    {
	c = rand() & 255;
	if (c != 0 && c != 13 && c != 10)
	{
	    *str++ = c;
	    len--;
	}
    }
#else
    makerand(str, len);
#endif
}

void send_cmd(void)
{
    static gint nicks = 0;
    GList *tmp;
    char str[512];
    int pos;

    /* send msg to every channel */
    str[511] = '\0';
    for (tmp = g_list_first(channels); tmp != NULL; tmp = tmp->next)
    {
        CHANNEL_REC *rec = tmp->data;

        makerand(str, 511);
        str[0] = ':';
        str[10] = '!';
        str[20] = '@';

        switch (rand() % 10)
	{
            case 0:
                /* join */
                pos = 2+sprintf(str+2, "%d", nicks++); /* don't use same nick twice */
                str[pos] = '-';
                str[10] = '\0';
                g_list_append(rec->nicks, g_strdup(str+1));
                str[10] = '!';
                sprintf(str+30, " JOIN :%s", rec->name);
		break;
            case 1:
                /* part */
                if (g_list_length(rec->nicks) > 1 && rand() % 3 == 0)
                {
                    gchar *nick;

                    nick = g_list_nth(rec->nicks, rand()%(g_list_length(rec->nicks)-1)+1)->data;
                    if (rand() % 3 == 0)
                        sprintf(str, ":kicker!some@where KICK %s %s :go away", rec->name, nick);
                    else if (rand() % 3 == 0)
                        sprintf(str, ":%s!dunno@where QUIT %s :i'm outta here", nick, rec->name);
                    else
                        sprintf(str, ":%s!dunno@where PART %s", nick, rec->name);
                    rec->nicks = g_list_remove(rec->nicks, nick);
                    g_free(nick);
                }
                else
                    str[0] = '\0';
                break;
            case 2:
                /* nick change */
                if (g_list_length(rec->nicks) > 1)
                {
                    gchar *nick;

                    nick = g_list_nth(rec->nicks, rand()%(g_list_length(rec->nicks)-1)+1)->data;
                    pos = sprintf(str, ":%s!dunno@where NICK ", nick);
                    str[pos] = '_';
                    str[50] = '\0';
                    rec->nicks = g_list_remove(rec->nicks, nick);
                    rec->nicks = g_list_append(rec->nicks, g_strdup(str+pos));
                    g_free(nick);
                }
                else
                    str[0] = '\0';
                break;
            case 3:
                /* topic */
                pos = 30+sprintf(str+30, " TOPIC %s :", rec->name);
                str[pos] = 'x';
                break;
            case 4:
                /* mode */
                sprintf(str+30, " MODE %s :%cnt", rec->name, (rand() & 1) ? '+' : '-');
                break;
            case 5:
                /* notice */
                pos = 30+sprintf(str+30, " NOTICE %s :", rec->name);
                str[pos] = 'X';
		break;
            case 6:
                /* nick mode change */
                if (g_list_length(rec->nicks) > 1)
                {
                    gchar *nick;

                    nick = g_list_nth(rec->nicks, rand()%(g_list_length(rec->nicks)-1)+1)->data;
		    pos = sprintf(str, ":server MODE %s +%c %s", rec->name, rand()&1 ? 'o' : 'v', nick);
                    str[pos] = '_';
                    str[50] = '\0';
                    rec->nicks = g_list_remove(rec->nicks, nick);
                    rec->nicks = g_list_append(rec->nicks, g_strdup(str+pos));
                    g_free(nick);
                }
                else
                    str[0] = '\0';
                break;
            default:
		pos = 30+sprintf(str+30, " PRIVMSG %s :", rec->name);
		makerand2(str+pos, 511-pos);
                if (rand() % 4 == 0)
                {
                    pos += sprintf(str+pos, "\001ACTION ");
                    str[510] = 1;
                }
                else if (rand() % 10 == 0)
                {
                    pos += sprintf(str+pos, "\001VERSION\001");
                    pos++;
                }
                else if (rand() % 2 == 0)
                {
                    pos += sprintf(str+pos, "%s: ", clientnick);
		}
                str[pos] = 'X';
		break;
        }

        client_send(str);
    }
    makerand(str, 511);
    str[0] = ':';
    str[10] = '!';
    str[20] = '@';
    switch (rand() % 11)
    {
	case 0:
            /* join */
            if (g_list_length(channels) < 20)
            {
                CHANNEL_REC *rec;
                int n, pos;

                n = (rand()%20)+25;
                pos = sprintf(str, ":%s!%s JOIN :", clientnick, clienthost);
                str[pos] = '#';
                str[n] = '\0';

                rec = g_new(CHANNEL_REC, 1);
                rec->name = g_strdup(str+pos);
                rec->nicks = g_list_append(NULL, g_strdup(clientnick));

                channels = g_list_append(channels, rec);
                client_send(str);

                sprintf(str, ":server 353 %s = %s :@%s", clientnick, rec->name, clientnick);
                client_send(str);
                sprintf(str, ":server 366 %s %s :End of /NAMES list.", clientnick, rec->name);
            }
            else
                str[0] = '\0';
            break;
	case 1:
            /* leave channel (by kick) */
            if (g_list_length(channels) > 3)
            {
                CHANNEL_REC *chan;

                chan = g_list_nth(channels, rand()%g_list_length(channels))->data;
                if (rand() % 3 != 0)
                {
                    pos = sprintf(str, ":%s!%s PART %s :", clientnick, clienthost, chan->name);
                    str[pos] = 'x';
                }
                else
                {
                    str[0] = ':';
                    sprintf(str+30, " KICK %s %s :byebye", chan->name, clientnick);
                }

                g_free(chan->name);
                g_list_foreach(chan->nicks, (GFunc) g_free, NULL); g_list_free(chan->nicks);
                g_free(chan);
                channels = g_list_remove(channels, chan);
            }
            else
                str[0] = '\0';
            break;
        case 2:
            /* ctcp version */
            sprintf(str+30, " PRIVMSG %s :\001VERSION\001", clientnick);
            break;
        case 3:
            /* ctcp ping */
            sprintf(str+30, " PRIVMSG %s :\001PING\001", clientnick);
            break;
        case 4:
            /* user mode */
            sprintf(str+30, " MODE %s :%ciw", clientnick, (rand() & 1) ? '+' : '-');
            break;
        case 5:
            /* msg */
            pos = 30+sprintf(str+30, " PRIVMSG %s :", clientnick);
            str[pos] = 'X';
            break;
        case 6:
            /* notice */
            pos = 30+sprintf(str+30, " NOTICE %s :", clientnick);
            str[pos] = 'X';
            break;
        case 7:
            /* invite */
            pos = 30+sprintf(str+30, " INVITE %s ", clientnick);
            str[pos] = 'X';
            break;
        case 8:
            /* error */
            pos = sprintf(str, ":server ERROR :");
            str[pos] = 'X';
            break;
        case 9:
            /* wallops */
            pos = sprintf(str, ":server WALLOPS :");
            str[pos] = 'X';
            break;
        case 10:
            /* ping */
            pos = sprintf(str, ":server PING :");
            str[pos] = 'X';
	    break;
    }
    client_send(str);
}

void handle_command(char *str)
{
    if (strncmp(str, "NICK ", 5) == 0)
    {
        clientnick = g_strdup(str+5); /* got the nick */
    }
}

int main(void)
{
    static fd_set fdset;
    struct timeval tv;
    int serverh, port;
    IPADDR ip;

#ifdef HAVE_IPV6
    if (net_gethostname("::1", &ip) != 0)
    {
	printf("net_gethostname()\n");
	return 1;
    }
#else
    if (net_gethostname("127.0.0.1", &ip) != 0)
    {
	printf("net_gethostname()\n");
	return 1;
    }
#endif

    srand(0);
    port = 6660;
    serverh = net_listen(&ip, &port);
    if (serverh == -1)
    {
	printf("listen()\n");
	return 1;
    }

    clienth = -1; channels = NULL;
    for (;;)
    {
        FD_ZERO(&fdset);
        if (clienth != -1) FD_SET(clienth, &fdset);
        FD_SET(serverh, &fdset);

        tv.tv_sec = 0;
        tv.tv_usec = FLOOD_TIMEOUT;
        if (select((serverh > clienth ? serverh : clienth)+1, &fdset, NULL, NULL, &tv) <= 0)
        {
		/* nothing happened, bug the client with some commands.. */
		if (clienth != -1 && clientnick != NULL) send_cmd();
        }
        else
	{
            if (FD_ISSET(serverh, &fdset))
            {
                /* client connecting! */
                if (clienth != -1)
                {
                    /* only one client allowed.. */
                    int handle;

                    handle = net_accept(serverh, NULL, &port);
                    if (handle != -1)
                    {
                        client_send("Only one client allowed");
                        close(handle);
                        continue;
                    }
                }
                else
		{
		    IPADDR clientip;

                    clienth = net_accept(serverh, &clientip, &port);
                    if (clienth != -1)
		    {
			net_ip2host(&clientip, clienthost);
                        client_send(":server 001 pla");
                        client_send(":server 002 plapla");
                        client_send(":server 003 plaplapla");
                        client_send(":server 004 connected!");
                    }
                }
            }
            else
            {
                /* clients sending something.. */
                GString *str, *buf;
                int ret;

                str = g_string_new(NULL);
                buf = g_string_new(NULL);
                do
                {
                    ret = read_line(TRUE, clienth, str, buf);
                    if (ret == -1)
                    {
                        /* client disconnected */
                        close(clienth);
                        clienth = -1;
                        break;
                    }
                    if (ret == 1) handle_command(str->str);
                }
                while (ret == 1);
                g_string_free(str, TRUE);
                g_string_free(buf, TRUE);
            }
        }
    }

    return 0;
}