/*
 * vchat-client - alpha version
 * vchat-protocol.c - handling of server messages
 *
 * Copyright (C) 2001 Andreas Kotes <count@flatline.de>
 *
 * This program is free software. It can be redistributed and/or modified,
 * provided that this copyright notice is kept intact. 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. In no event shall the copyright holder be liable for
 * any direct, indirect, incidental or special damages arising in any way out
 * of the use of this software. 
 *
 */

/* general includes */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <readline/readline.h>
#include <locale.h>
#include <langinfo.h>

#ifdef DEBUG
FILE * dumpfile;
#endif

/* local includes */
#include "vchat.h"
#include "vchat-user.h"
#include "vchat-connection.h"

/* version of this module */
const char *vchat_io_version = "vchat-protocol.c $Id$";

/* declaration of local helper functions */
static void usersignon (char *);
static void usersignoff (char *);
static void usernickchange (char *);
static void userjoin (char *);
static void userleave (char *);
static void receivenicks (char *message);
static void justloggedin (char *message);
static void nickerr (char *message);
static void login (char *message);
static void anonlogin (char *message);
static void topicinfo (char *message);
static void pubaction (char *message);
static void pubthoughts (char *message);
static void serverlogin (char *message);
static void idleprompt (char *message);
static void topicchange (char *message);
static void pmnotsent (char *message);

/* declaration of server message array */
#include "vchat-messages.h"

char *encoding;

/* handle a pm not sent error
 *  format: 412 %s */
static void
pmnotsent (char *message)
{
  while(*message && *message!=' ') message++;
  snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_ERR),message+1);
  writepriv( tmpstr, 0);

}

/* parse and handle an action string
 *  format: 118 %s %s
 *    vars: %s nick
 *          %s action */
static void
pubaction (char *message)
{
  char *nick = NULL, *action = NULL;
  nick = strchr (message, ' ');
  nick[0] = '\0';
  nick++;

  action = strchr (nick, ' ');
  action[0] = '\0';
  action++;

  ul_public_action(nick);
  snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_PUBACTION),nick,action);
  writechan (tmpstr);
}

/* parse and handle an thought string
 *  format: 124 %s %s
 *    vars: %s nick
 *          %s thought */
static void
pubthoughts (char *message)
{
  char *nick = NULL, *thoughts = NULL;
  nick = strchr (message, ' ');
  nick[0] = '\0';
  nick++;

  thoughts = strchr (nick, ' ');
  thoughts[0] = '\0';
  thoughts++;

  ul_public_action(nick);
  snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_PUBTHOUGHT),nick,thoughts);
  writechan (tmpstr);
}

/* parse and handle server logon */
static void
serverlogin (char *message)
{
  (void)message;
  int utf8 = !strcmp(nl_langinfo(CODESET), "UTF-8");
  if (utf8)
    vc_sendmessage(".e utf8");
}

/* parse and handle an idle message
 *  format: 305
 *    vars: %s message */
static void
idleprompt (char *message)
{
  char *msg = NULL;
  msg = strchr (message, ' ');
  msg[0] = '\0';
  msg++;

  snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_IDLE),msg);
  writechan (tmpstr);
}

/* parse and handle a topic information string
 *  format: 115 %d %s
 *    vars: %d chan  - channel number
 *          %s topic - topic  */
static void
topicinfo (char *message)
{
  char *channel = NULL, *topic = NULL;
  int tmpchan = 0, ownchan = own_channel_get();

  /* search start of channel number */
  channel = strchr (message, ' ');
  channel++;

  /* search start of topic and terminate channel number */
  topic = strchr (channel, ' ');
  topic[0] = '\0';
  topic++;

  /* convert channel number to integer */
  tmpchan = atoi (channel);

  if (tmpchan == ownchan ) {
     /* show change in topic window */
     if (strlen(topic))
        snprintf (topicstr, TOPICSTRSIZE, getformatstr(FS_TOPICW), ownchan, topic);
      else
        snprintf (topicstr, TOPICSTRSIZE, getformatstr(FS_NOTOPICW), ownchan );
     topicline(NULL);
  }

  /* announce change in channel window */
  if (strlen(topic))
     snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_TOPIC), tmpchan, topic);
   else
     snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_NOTOPIC), tmpchan);
  writechan (tmpstr);
}

/* parse and handle a topic change string
 *  format: 114 %s changes topic to '%s'
 *    vars: %s nick
 *          %s topic */
static void
topicchange (char *message)
{
  char *nick = NULL, *topic = NULL;
  int len, ownchan = own_channel_get();

  /* search start of nickname */
  nick = strchr (message, ' ');
  nick++;

  /* search start of message before topic, terminate nick */
  topic = strchr (nick, ' ');
  topic[0] = '\0';
  topic++;

  /* search start of real topic and terminate channel number */
  topic = strchr (topic, '\'');
  topic[0] = '\0';
  topic++;

  /* remove ending '\'', if there */
  len = strlen (topic);
  if (topic[len-1] == '\'')
    topic[len-1] = '\0';

  ul_public_action(nick);
  /* show change in topic window */
  snprintf (topicstr, TOPICSTRSIZE, getformatstr(FS_TOPICW), ownchan, topic);
  topicline(NULL);

  /* announce change in channel window */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_CHGTOPIC), nick, topic);
  writechan (tmpstr);
}

/* parse and handle a login message
 *  format: 212 %s %s
 *    vars: %s str1 - nick used to login
 *          %s str2 - servers message */
static void
justloggedin (char *message)
{
  char *str1 = NULL, *str2 = NULL;
  /* search start of nickname */
  str1 = strchr (message, ' ');
  str1++;

  /* search start of message, terminate nick */
  str2 = strchr (str1, ' ');
  str2[0] = '\0';
  str2++;

  /* if we have a new nick, store it */
  own_nick_set( str1 );

  /* show change in console window */
  snprintf (consolestr, CONSOLESTRSIZE, getformatstr(FS_CONSOLE), str1, getstroption (CF_SERVERHOST), getstroption (CF_SERVERPORT));
  consoleline (NULL);

  /* announce login as servermessage */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_SIGNON), str1, str2);
  writechan (tmpstr);

  /* we're not logged in, change status and request nicks */
  if (!loggedin)
    {
      loadcfg(getstroption(CF_LOGINSCRIPT),0,handleline);
      handleline(".S");
      loggedin = 1;
    }
}

/* this user joins a different channel */
void
ownjoin (int channel)
{
  vc_sendmessage(".t");
  snprintf(tmpstr, TMPSTRSIZE, ".S %d",channel);
  vc_sendmessage(tmpstr);
}

/* this user changes his nick */
void
ownnickchange (const char *newnick)
{
  (void)newnick;
}

/* parse and handle a nick error message
 *  format: 401 %s
 *          403 %s
 *          415 %s
 *    vars: %s - server message */
static void
nickerr (char *message)
{
  char *helpkiller = NULL;
  /* mutate message for output */
  message[2] = '!';
  /* help information found? remove it. */
  if ((helpkiller = strstr (message, " Type .h for help")))
    helpkiller[0] = '\0';
  /* nickchange not done? eliminate message */
  if (loggedin && (helpkiller = strstr (message, " - Nick not changed")))
    helpkiller[0] = '\0';
  /* show errormessage */
  writecf (FS_ERR,&message[2]);

  /* not logged in? insist on getting a new nick */
  if (!loggedin)
    {
      /* free bogus nick */
      own_nick_set(NULL);

      /* get new nick via vchat-ui.c */
      nickprompt ();

      /* form login message and send it to server */
      snprintf (tmpstr, TMPSTRSIZE, ".l %s %s %d", own_nick_get(), getstroption (CF_FROM), getintoption (CF_CHANNEL));
      vc_sendmessage (tmpstr);
    }
}

/* parse and handle a registered nick information
 *  format: 120 %s %s
 *    vars: %s      - this users registered nick
 *          %s msg  - server message */
static void
login (char *message) {
  char *msg = NULL;

  /* mutate message for output */
  message[2] = '*';
  /* show message to user */
  writecf (FS_SERV,&message[2]);

  /* we don't know our nick? */
  if (!own_nick_get() ) {
    /* find message after nick */
    msg = strchr (&message[4], ' ');
    if (msg) {
      /* terminate string before message and copy nick */
      msg[0] = '\0';
      own_nick_set(&message[4]);
    } else {
      /* no string in servers message (huh?), ask user for nick */
      nickprompt ();
    }
  }

  /* form login message and send it to server */
  snprintf (tmpstr, TMPSTRSIZE, ".l %s %s %d", own_nick_get(), getstroption (CF_FROM), getintoption (CF_CHANNEL));
  vc_sendmessage (tmpstr);
}

/* parse and handle anon login request
 *  format: 121 %s
 *    vars: %s - server message */
static void
anonlogin (char *message)
{
  /* mutate message for output */
  message[2] = '*';
  /* show message to user */
  writecf (FS_SERV,&message[2]);

  /* we don't know our nick? ask for it! */
  if (!own_nick_get())
    nickprompt ();

  /* form login message and send it to server */
  snprintf (tmpstr, TMPSTRSIZE, ".l %s %s %d", own_nick_get(), getstroption (CF_FROM), getintoption (CF_CHANNEL));
  vc_sendmessage (tmpstr);
}

/* parse and handle list of nicks (from '.S')
 *  format: 119 %s ..
 *    vars: %s nick - a users nick */
static void
receivenicks (char *message) {
  char *str1 = NULL, *str2 = NULL;
  int chanflag = -1;

  /* show message to user */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_USONLINE), &message[4]);
  writechan (tmpstr);

  /* search for channelnumber */
  if( !(str1 = strchr (message, ' ') ) ) return;
  str1++;

  if (str1[0] == '*') {
      ul_rebuild_list();
      str1++;
  } else {
      int mychan;
      str2 = str1;
      str1 = strchr(str2,' ');
      str1[0] = '\0';
      mychan = atoi(str2);
      if( mychan != own_channel_get() )
        return;

      /* Kick all users from the IN_MY_CHANNEL list */
      own_channel_set( own_channel_get() );
      chanflag = 1;
  }
  str1++;

  /* while user .. */
  while (str1) {
    /* search next user */
    str2 = strchr (str1, ' ');
    /* there is another user? terminate this one */
    if (str2) {
      str2[0] = '\0';
      str2++;
    }

    /* add this user via vchat-user.c */
    ul_add(str1, chanflag);

    /* next user .. */
    str1 = str2;
  }
  ul_clean();
}

/* parse and handle a login message
 *  format: 211 %s %s
 *    vars: %s nick - who logged on
 *          %s msg  - servers message */
static void
usersignon (char *message)
{
  char *nick = NULL, *msg = NULL;
  /* search start of nickname */
  nick = strchr (message, ' ');
  nick++;

  /* search start of message, terminate nick */
  msg = strchr (nick, ' ');
  msg[0] = '\0';
  msg++;

  /* add this user via vchat-user.c */
  ul_add (nick, 0);

  /* show message to user */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_SIGNON), nick, msg);
  writechan (tmpstr);
}

/* parse and handle a logoff message
 *  format: 221 %s %s
 *    vars: %s nick - who logged off
 *          %s msg  - servers message */
static void
usersignoff (char *message)
{
  char *nick = NULL, *msg = NULL;
  /* search start of nickname */
  nick = strchr (message, ' ');
  nick++;

  /* search start of message, terminate nick */
  msg = strchr (nick, ' ');
  if( msg ) {
    msg[0] = '\0';
    msg++;
  }

  /* delete this user via vchat-user.c */
  ul_del (nick);

  /* show message to user */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_SIGNOFF), nick, msg);
  writechan (tmpstr);
}

/* parse and handle a join message
 *  format: 232 %s %s %d
 *    vars: %s nick - who joined
 *          %s msg  - servers message
 *          %d chan - channel joined */
static void
userjoin (char *message)
{
  char *nick = NULL, *msg = NULL, *channel = NULL;
  int chan = 0;

  /* search start of nickname */
  nick = strchr (message, ' ');
  nick++;

  /* search start of message, terminate nick */
  msg = strchr (nick, ' ');
  msg[0] = '\0';
  msg++;

  /* search start of channel, terminate message */
  channel = strrchr (msg, ' ');
  channel[0] = '\0';
  channel++;

  /* convert channel to integer */
  chan = atoi (channel);

  /* is it myself joining */
  if( own_nick_check(nick) )
    own_channel_set(chan);

  /* notice channel join via vchat-user.c */
  if( own_channel_get() == chan )
    ul_enter_chan(nick);

  /* show message to user */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_JOIN), nick, msg, chan);
  writechan (tmpstr);
}

/* parse and handle a leave message
 *  format: 231 %s %s %d
 *    vars: %s nick - who left
 *          %s msg  - servers message
 *          %d chan - channel joined */
static void
userleave (char *message)
{
  char *nick = NULL, *msg = NULL, *channel = NULL;
  int chan = 0;

  /* search start of nickname */
  nick = strchr (message, ' ');
  nick++;

  /* search start of message, terminate nick */
  msg = strchr (nick, ' ');
  msg[0] = '\0';
  msg++;

  /* convert channel to integer */
  channel = strrchr (msg, ' ');
  channel[0] = '\0';
  channel++;

  /* convert channel to integer */
  chan = atoi (channel);

  /* notice channel leave via vchat-user.c */
  ul_leave_chan(nick);

  /* show message to user */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_LEAVE), nick, msg, chan);
  writechan (tmpstr);
}

/* parse and handle a nickchange message
 *  format: 241 %s %s %s
 *    vars: %s oldnick - users old nick
 *          %s newnick - users new nick
 *          %s msg     - server message */
static void
usernickchange (char *message)
{
  char *oldnick = NULL, *newnick = NULL, *msg = NULL;

  /* search start of old nickname */
  oldnick = strchr (message, ' ');
  oldnick++;

  /* search start of new nickname, terminate old nick */
  newnick = strchr (oldnick, ' ');
  newnick[0] = '\0';
  newnick++;

  /* search start of message, terminate new nick */
  msg = strchr (newnick, ' ');
  msg[0] = '\0';
  msg++;

  /* notice nickchange via vchat-user.c */
  ul_rename (oldnick, newnick);

  if( own_nick_check(newnick) ) {
    /* show change in console window */
    snprintf (consolestr, CONSOLESTRSIZE, getformatstr(FS_CONSOLE), newnick, getstroption (CF_SERVERHOST), getstroption (CF_SERVERPORT));
    consoleline (NULL);
  }

  /* show message to user */
  snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_NICKCHANGE), oldnick, newnick, msg);
  writechan (tmpstr);
}

/* handle received message from server */
void
protocol_parsemsg (char *message)
{
  char *str1, *str2;
  int i;
  /* message to short or starts with '<'? must be channel */
  if (message[0] == '<')
  {
    str1 = &message[1];
    str2 = strchr(str1,'>');
    str2[0] = '\0';
    str2++;
    if (str2[0] == ' ') str2++;
    if (own_nick_check(str1))
       snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_MYPUBMSG),str1,str2);
    else
       snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_RXPUBMSG),str1,str2);
    writechan (tmpstr);

    ul_public_action(str1);
  }
  else if (message[0] == '[')
  {
    str1 = &message[1];
    str2 = strchr(str1,']');
    str2[0] = '\0';
    str2++;
    if (str2[0] == ' ') str2++;
    if (own_nick_check( str1 ))
       snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_MYPUBURL),str1,str2);
    else
       snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_RXPUBURL),str1,str2);
    ul_public_action(str1);
    writechan (tmpstr);
  }
  /* message starts with '*'? must be private */
  else if (message[0] == '*')
  {
    str1 = &message[1];
    str2 = strchr(str1,'*');
    str2[0] = '\0';
    str2++;
    if (str2[0] == ' ') str2++;
    snprintf(tmpstr,TMPSTRSIZE,getformatstr(FS_RXPRIVMSG),str1,str2);
    writepriv (tmpstr, 1);
    ul_private_action(str1);
  }
  /* message starts with a number? must be a servermessage */
  else if ((message[0] >= '0') && (message[0] <= '9'))
    {
      /* walk server message array */
      for (i = 0; servermessages[i].id[0]; i++)
	{
          /* is this the message? */
	  if (!(strncmp (servermessages[i].id, message, 3)))
	    {
              /* scripting hook available - call it */
	      if (servermessages[i].hook)
		servermessages[i].hook (message);
              /* function available - call it */
	      else if (servermessages[i].funct)
		servermessages[i].funct (message);
              /* no function available, but known - give output */
	      else
		{
                  /* remove continutation mark */
		  if (message[3] == '-')
		    message[3] = ' ';

                  /* mutate message for display */
		  message[2] = '*';
                  /* giveout message by type */
		  switch (servermessages[i].type)
		    {
		    case SM_IGNORE:
		      break;
		    case SM_INFO:
                      /* change marker and send as servermessage */
		      message[2] = '#';
		      writecf (FS_SERV,&message[2]);
		      break;
		    case SM_USERINFO:
                      /* keep marker and send as servermessage */
		      writecf (FS_SERV,&message[2]);
		      break;
		    case SM_CHANNEL:
                      /* keep marker and send as channelmessage */
		      writechan (&message[2]);
		      break;
		    case SM_ERROR:
                      /* change marker and send as errormessage */
		      message[2] = '!';
		      writecf (FS_ERR,&message[2]);
		      break;
		    default:
                      /* fallback: keep marker and send as servermessage */
		      writecf (FS_SERV,&message[2]);
		    }
		}
	      return;
	    }
	}
      /* message not in list, raise errormessage */
      snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_UNKNOWNMSG), message);
      writecf (FS_ERR,tmpstr);
    }
  else
    {
      /* message neither public, private or server, raise errormessage */
      snprintf (tmpstr, TMPSTRSIZE, getformatstr(FS_BOGUSMSG), message);
      writecf (FS_ERR,tmpstr);
    }
}