/*
 * display.c: routines that proceeds the data from a server and format it.
 *
 * Copyright(c) 1997-2000 - All Rights Reserved
 *
 * See the COPYRIGHT file.
 */

#ifndef lint
static char rcsid[] = "@(#)$Id: display.c,v 1.57 2000/07/31 22:33:55 kalt Exp $";
#endif

#include "os.h"

#include "struct.h"
#include "term.h"
#include "window.h"
#include "format.h"
#include "option.h"
#include "server.h"
#include "utils.h"

extern struct server_	*server;
extern char		need_collect;

extern char		tab_add(char *, char *);
extern char		space_add(char *, char *);
extern char		sic_ctcp(char *, char, char *);
extern void		url_find(char *, char *, char *);

static char		outp[1024];
static u_char		outa[1024];
static char		*s, *c, *d, *p;
static int		n;
/* locals is currently unused */
static char		toself, locals, froms, fromv, toch, showid;
static char		nick[10], user[11], host[64];

/*
 * "RAW" options
 *	%d	recipient
 *	%m	command
 *	%o	origin (nick!user@host or server.name)
 *	%p	parameters
 *
 * blah
 *	%B	bold on/off
 *	%R	reverse video on/off
 *	%U	underline on/off
 *
 * "processed" options
 *	%I	tabkey identity (%u@%h)
 *	%n!%u@%h
 *	%N	numeric (alias for command??)
 *	%s	server name the message originated from
 *	%S	server name you are connected to
 */
static int
sic_sprintf(buffer, attr, format, channel, flags)
char	*buffer, *format, *channel;
u_char  *attr;
unsigned int	*flags;
{
  char		*wp = buffer;
  u_char	video = 0, vplus = 0, anyattr = 0, checknow = 0, dobeep = 0;
  int		align;

  *wp = '\0';
  while (*format)
    {
      while (*format && *format != '%')
	{
	  *wp++ = *format++;
	  *attr++ = video;
	  if (video)
	      anyattr = 1;
	}
      
      if(*format++)
	{
	  if (align = atoi(format))
	    {
	      if (align < 0)
		  format++;
	      while (isdigit((int) *format))
		  format++;
	    }

	  *wp = '\0';
	  switch (*format++)
	    {
	  case '%':
	      *wp++ = '%'; *attr++ = video;
	      *wp = '\0';
	      align = 0;
	      break;
	  case 'B':
	      video ^= TERM_BOLD;
	      align = 0;
	      break;
	  case 'c':
	      strcat(buffer, d);
	      break;
	  case 'd':
	      strcat(buffer, d);
	      break;
	  case 'h':
	      strcat(buffer, host);
	      break;
	  case 'I':
	      if (showid)
		{
		  strcat(buffer, user);
		  strcat(buffer, "@");
		  strcat(buffer, host);
		}
	      break;
	  case 'm':
	      strcat(buffer, c);
	      break;
	  case 'n':
	      strcat(buffer, nick);
	      get_ignore(s, channel, flags);
	      if (*flags & LOG_HIGHLIGHT && !(video & TERM_STANDOUT))
		video |= (vplus = TERM_STANDOUT);
	      break;
	  case 'N':
	      sprintf(wp, "%3.3d", n);
	      break;
	  case 'p':
	      if (*format != '{')
		  strcat(buffer, p);
	      else
		{
		  strcat(buffer, sic_split(p, format));
		  if (index(format, '}'))
		      format = index(format, '}') + 1;
		}
	      if (channel && (*flags & LOG_PUBLIC)
		  && (*flags & (LOG_MSG|LOG_NOTICE)))
		  checknow = 1;
	      break;
	  case 'o':
	      strcat(buffer, s);
	      get_ignore(s, channel, flags);
	      if (*flags & LOG_HIGHLIGHT && !(video & TERM_STANDOUT))
		video |= (vplus = TERM_STANDOUT);
	      break;
	  case 'R':
	      video ^= TERM_STANDOUT;
	      align = 0;
	      break;
	  case 's':
	      if (froms)
		  strcat(buffer, s);
	      else
		  strcat(buffer, "żżż");
	      break;
	  case 'S':
	      if (server)
		  strcat(buffer, server->sname);
	      else
		  strcat(buffer, "<how the hell did we get here?>");
	      break;
	  case 'u':
	      strcat(buffer, user);
	      break;
	  case 'U':
	      video ^= TERM_UNDERLINE;
	      align = 0;
	      break;
	  default:
	      *wp++ = (char) 191; *attr++ = TERM_STANDOUT; anyattr = 1;
	      *wp = '\0';
	      align = 0;
	      term_beep();
	      break;
	    }

	  if (align && *wp != '\0')
	    {
	      if (align > 0)
		  if (strlen(wp) < align)
		    {
		      char *where = wp;

		      align -= strlen(wp);
		      where += strlen(wp);
		      while (align--)
			  *where++ = ' ';
		      *where = '\0';
		    }
		  else
		      wp[align] = '\0';
	      else
		  if (strlen(wp) < -align)
		    {
		      char *where = wp - align;
		      char *end = wp + strlen(wp);

		      while (end >= wp)
			  *where-- = *end--;
		      while (where >= wp)
			  *where-- = ' ';
		    }
		  else
		      wp[-align] = '\0';
	    }

	  if (checknow)
	    {
	      char *where = wp, how = 0;
	      u_char extra;
	      int len;

	      checknow = 0;
	      len = get_keyword(channel, &where, &how, flags);
	      if (how & K_BEEP)
		  dobeep = 1;
	      if (how & K_SPACE && *flags & LOG_PUBLIC)
		  space_add(nick, "");
	      if (len || video)
		  anyattr = 1;
	      while (*wp != '\0')
		{
		  extra = 0;
		  /* check for ircii rendering junk */
		  if (*wp == ctrl('_'))
		      extra = TERM_EUNDERLINE;
		  else if (*wp == ctrl('V'))
		      extra = TERM_ESTANDOUT;
		  else if (*wp == ctrl('B'))
		      extra = TERM_EBOLD;
		  if (extra)
		    {
		      video ^= extra;
		      extra = TERM_EHIDE;
		      anyattr = 1;
		    }
		  /* keywords */
		  *attr = video | extra;
		  if (where)
		    {
		      if (wp >= where && (wp - where) < len)
			  *attr |= (how & 0x0F);
		      if ((wp - where) == len)
			{
			  where = wp;
			  if (len = get_keyword(channel, &where, &how, flags))
			      anyattr = 1;
			  if (how & K_BEEP)
			      dobeep = 1;
			  if (how & K_SPACE && *flags & LOG_PUBLIC)
			      space_add(nick, "");
			}
		    }
		  wp++;
		  attr++;
		}
	    }
	  else
	    {
	      while (*wp != '\0')
		{
		  wp++;
		  *attr++ = video;
		}
	      if (video)
		  anyattr = 1;
	    }
	  if (vplus)
	    {
	      video ^= vplus;
	      vplus = 0;
	    }
	}
      else
	  break;
    }
  *wp = '\0';
  if (dobeep && !(*flags & LOG_IGNORE))
      term_beep();
  return anyattr;
}

/* sic_splitorig: splits n!u@h */
static void
sic_splitorig(origin)
  char *origin;
{
  strcpy(nick, "<bogus>"); strcpy(user, "<bogus>"); strcpy(host, "<bogus>");
  if (s && index(s, '!'))
    {
      char *ch = s, *ptr;
      
      ptr = nick;
      while (*ch != '!')
	  *ptr++ = *ch++;
      *ptr = '\0'; ch++;
      ptr = user;
      while (*ch != '@')
	  *ptr++ = *ch++;
      *ptr = '\0'; ch++;
      ptr = host;
      while (*ch)
	  *ptr++ = *ch++;
      *ptr = '\0';
    }
}

void
sic_format(sender, cmd, dest, para)
     char *sender, *cmd, *dest, *para;
{
  int	fmt = -1;
  unsigned int flags = 0;
  int	tocurrent = 1, ctcp = 0;
  char	tochannel[512];

  assert(server);

  locals = toself = froms = fromv = toch = showid = 0;

  s = sender;
  if (option(server->sopt, S_DCC))
    {
      char name[11];

      strcpy(user, "<bogus>"); strcpy(host, "<bogus>");
      strcpy(nick, server->nick);
      flags = LOG_USER|LOG_PRIVATE|LOG_DCC;
      fmt = F_DCC;
      p = para;
      sprintf(name, "=%s", server->nick);
      tab_add(name, "");
    }
  else
    {
      sic_splitorig(s);
      c = cmd;
      d = (*dest == ':') ? dest+1 : dest; /* hmmpf */
      if (p = index(d, '\007')) /* special case, 2.9 join with ^G */
	{
	  assert (*para == '\0');
	  *p = '\0';
	  para = p+1;
	}
      p = para;
      n = atoi(cmd);
      
      if (n != 1)
	{
	  /*
	   * simple analysis of origin and destination
	   * It will be used to define the message appearance,
	   * as well as the window where it is to be displayed.
	   */
	  if (!strcasecmp(s, server->sname))
	      locals = 1; /* from local server */
	  if (!strcasecmp(d, server->nick))
	    {
	      toself = 1; /* to the user */
	      flags |= LOG_PRIVATE;
	    }
	  else
	      if (toch = map_c2w(d)) /* to a channel? */
		{
		  flags |= LOG_PUBLIC;
		  strcpy(tochannel, d);
		}
	      else
		  flags |= LOG_BROADCAST;
	  if (index(s, '!'))
	      flags |= LOG_USER;
	  else
	      if (!index(s, '@'))
		{
		  froms = 1; /* from a server */
		  flags |= LOG_SERVER;
		}
	      else
		{
		  fromv = 1; /* from a service */
		  flags |= LOG_SERVICE;
		}
	}
      
      /*
       * ugly huge statement, but everything that has to be parsed is here,
       * ...
       */
      if (!strcasecmp("NOTICE", cmd))
	{
	  time_t ts;
	  flags |= LOG_NOTICE;
	  fmt = fromv ? F_VNOTICE :
	      froms ? F_SNOTICE :
	      toch ? F_CNOTICE : F_UNOTICE;
	  if (fromv)
	      tab_add(sender, NULL);
	  tocurrent = 0;
	  if (!fromv && !froms && sscanf(p, "\001PING %lu\001", &ts) == 1)
	    {
	      flags |= LOG_HIDE;
	      vsic_slog(LOG_CLIENT, "--- CTCP ping reply from %s: %s", nick,
			sic_tdiff(time(NULL)-ts, 0));
	    }
	}
      else if (!strcasecmp("PRIVMSG", cmd))
	{
	  flags |= LOG_MSG;
	  fmt = froms ? F_SMSG :
	      toch ? F_CMSG : F_UMSG;
	  tocurrent = 0;
	  if (strncmp("\001DCC ", p, 5) && index(p, '\001'))
	    {
	      ctcp = 1;
	      if (strncmp("\001ACTION ", p, 8)
		  && !get_option(Z_VCTCP, (toch) ? tochannel : NULL))
		  flags |= LOG_HIDE;
	    }
	  if (get_option(Z_URL, (toch) ? tochannel : NULL))
	      url_find(sender, (toch) ? tochannel : NULL, p);

	  if (!froms && !toch)
	      if (strncmp("\001DCC ", p, 5))
		{
		  char uh[256];

		  sprintf(uh, "%s@%s", user, host);
		  if (strncmp("\001ACTION ", p, 8)
		      && get_option(Z_VCTCP, (toch) ? tochannel : NULL))
		      showid = tab_add(nick, uh);
		}
	      else
		{
		  char *file, *ip, *size;
		  signed char type = -1;
		  unsigned long ipaddr, sz;
		  unsigned int portnb;
		  
		  if (!strncmp("CHAT ", p+5, 5))
		      type = 0;
		  else if (!strncmp("SEND ", p+5, 5))
		      type = 1;
		  else
		      vsic_slog(LOG_CLIENT, "--- Unknown DCC request from %s:",
				nick);
		  if (type >= 0)
		      if (file = index(p+5, ' '))
			  if (ip = index(file+1, ' '))
			      if (size = index(ip+1, ' '))
				{
				  *ip = '\0';
				  flags |= LOG_HIDE;
				  sscanf(ip+1, "%lu %u", &ipaddr, &portnb);
				  if (size = index(size+1, ' '))
				      sscanf(size, "%lu", &sz);
				  sic_dcc(type, nick, ipaddr, portnb, file+1,
					  sz);
				  *ip = ' ';
				}
		}
	}
      else
	{
	  flags |= LOG_CRAP;
	  if (n)
	    {
	      char *ch1, *ch2;

	      fmt = F_NUM;
	      switch (n)
		{
	      case 1:
		  if (server->sname)
		      free(server->sname);
		  server->sname = strdup(s);
		  if (server->nick)
		      free(server->nick);
		  server->nick = strdup(d);
		  if (!strcasecmp(s, server->sname))
		      locals = 1;
		  if (!strcasecmp(d, server->nick))
		      toself = 1;
		  unset_option(server->sopt, S_MOTD);
		  break;
	      case 2:
		  ch1 = strstr(p, "version");
		  if (ch1 == NULL)
		    {
		      sic_slog(LOG_CLIENT,
			       "--- Unable to determine protocol type.");
		      server->protocol = P_IRC;
		    }
		  else
		    {
		      unsigned char proto;

		      ch1 += 8;
		      if (*ch1 == 'u')
			{
			  proto = P_U;
			  ch2 = "Undernet";
			}
		      else if (!strncmp(ch1, "dal", 3))
			{
			  proto = P_D;
			  ch2 = "Dalnet";
			}
		      else if (strstr(ch1, "+CSr") || strstr(ch1, "/hybrid"))
			{
			  proto = P_E;
			  ch2 = "EFnet";
			}
		      else
			{
			  proto = P_IRC;
			  ch2 = "IRC";
			}
		      if (server->protocol == 0)
			  vsic_slog(LOG_CLIENT, "--- Protocol: %s", ch2);
		      else if (proto != server->protocol)
			  vsic_slog(LOG_CLIENT,"--- Protocol changed: %s",ch2);
		      server->protocol = proto;
		    }
		  break;
	      case 221: /* umodes */
		  flags |= LOG_HIDE; /* see cheap trick comment below */
		  strcpy(server->umode, p);
		  break;
	      case 324: /* channel mode */
		  break;
	      case 353: /* names */
		  ch2 = p + 2; /* channel name */
		  *(ch1 = index(ch2, ' ')) = '\0';
		  strcpy(tochannel, ch2);
		  *ch1 = ' '; ch1 +=2;
		  if (map_c2w(tochannel))
		    {
		      toch = 1;
		      if (*ch1 == '@')
			  set_chop(tochannel, 2); /* inexact, caught by set_chop() */
		      while (ch1 && *ch1)
			{
			  while (*ch1 == '@' || *ch1 == '+')
			      ch1++;
			  if (ch2 = index(ch1, ' '))
			      *ch2 = '\0';
			  add_member(tochannel, ch1, 1);
			  if (ch2)
			    {
			      *ch2 = ' ';
			      ch1 = ch2 + 1;
			    }
			  else
			      ch1 = NULL;
			}
		    }
		  break;
	      case 366: /* end of names */
		  ch1 = index(p, ' '); *ch1 = '\0';
		  strcpy(tochannel, p); *ch1 = ' ';
		  if (map_c2w(tochannel))
		    {
		      toch = 1;
		      add_member(tochannel, NULL, 0);
		    }
		  break;
	      case 421: /* Unknown command */
		  if (!strncmp("LAME:", p, 5))
		    {
		      ch1 = index(p, ' ');
		      *ch1 = '\0';
		      join_channel(p+5, 1);
		      flags |= LOG_IGNORE;
		      *ch1 = ' ';
		    }
		  break;
	      case 422: /* MOTD file is missing */
	      case 376: /* end of MOTD */
		  if (!strcasecmp(s, server->sname)
		      && !option(server->sopt, S_MOTD))
		    {
		      if (need_collect)
			  collect_channel();
		      rejoin_channel(NULL);
		      set_option(server->sopt, S_MOTD);
		    }
		  break;
	      case 381: /* you're now such a lamer (ircop) */
		  vsic_write("MODE %s", server->nick);
		  break;
	      case 403: /* no such channel */
	      case 442: /* not on this channel */
		  ch1 = index(p, ' ');
		  *ch1 = '\0';
		  if (map_c2w(p))
		      del_channel(p);
		  *ch1 = ' ';
		  break;
	      case 437: /* temporarily unavailable */
	      case 471: /* cannot join +l */
	      case 473: /* cannot join +i */
	      case 474: /* cannot join +b */
	      case 475: /* cannot join +k */
		  ch1 = index(p, ' ');
		  *ch1 = '\0';
		  if (map_c2w(p))
		    {
		      if (get_option(Z_REJOIN, p))
			  rejoin_channel(p);
		      else
			{
			  del_channel(p);
			  collect_channel();
			}
		    }
		  *ch1 = ' ';
		  break;
		}
	    }
	  else if (!strcasecmp("KILL", cmd))
	      fmt = F_KILL;
	  else if (!strcasecmp("ERROR", cmd))
	    {
	      fmt = F_ERROR;
	      del_channel(NULL);
	    }
	  else if (!strcasecmp("INVITE", cmd))
	    {
	      struct channel_ *ctmp;

	      fmt = F_INVITE;
	      if ((ctmp = get_channel(p)) && option(ctmp->copt, C_REJOIN))
		{
		  vsic_slog(LOG_CLIENT, "--- Answering invitation to %s.", p);
		  join_channel(p, 0);
		}
	    }
	  else if (!strcasecmp("JOIN", cmd))
	    {
	      fmt = F_JOIN;
	      assert(toch || *d == '!');
	      if (!toch)
		  {
		    chg_channel(d);
		    strcpy(tochannel, d);
		    flags = (flags ^ LOG_BROADCAST) | LOG_PUBLIC;
		    toch = 1;
		  }
	      if (!strcasecmp(nick, server->nick))
		  del_member(d, NULL);
	      else
		  add_member(d, nick, 0);
	    }
	  else if (!strcasecmp("PART", cmd))
	    {
	      fmt = F_PART;
	      del_member(d, nick);
	      if (!strcasecmp(nick, server->nick))
		  del_channel(d);
	    }
	  else if (!strcasecmp("KICK", cmd))
	    {
	      char *comment = index(p, ' ');
	      
	      assert(comment);
	      fmt = F_KICK;
	      *comment = '\0';
		  del_member(d, p);
	      if (!strcasecmp(p, server->nick))
		{
		  del_channel(d);
		  if (get_option(Z_REJOIN, d))
		      rejoin_channel(d);
		}
	      *comment = ' ';
	    }
	  else if (!strcasecmp("TOPIC", cmd))
	      fmt = F_TOPIC;
	  else if (!strcasecmp("QUIT", cmd))
	    {
	      int split = 0;
	      char *wp;

	      fmt = F_QUIT;
	      flags |= LOG_MULTICAST;
	      del_member(NULL, nick);
	      toch = 0; d = "<bogus>"; p = dest;

	      wp = p;
	      while (*wp && *wp != ' ')
		  if (*wp++ == '.')
		      split = 1;
	      if (*wp++ && split)
		{
		  split = 0;
		  while (*wp && *wp != ' ')
		      if (*wp++ == '.')
			  split = 1;
		  if (!split || *wp)
		      flags |= LOG_UQUIT;
		  else
		      flags |= LOG_SQUIT;
		}
	      else
		  flags |= LOG_UQUIT;
	    }
	  else if (!strcasecmp("NICK", cmd))
	    {
	      chg_member(nick, d);
	      if (!strcmp(nick, server->nick))
		{
		  free(server->nick);
		  server->nick = strdup(d);
		}
	      else
		  flags |= LOG_MULTICAST;
	      fmt = F_NICK;
	    }
	  else if (!strcasecmp("MODE", cmd))
	      if (!strcmp(sender, server->nick))
		{
		  strcpy(nick, sender); /* eww */
		  fmt = F_UMODE;
		  if (!index(p, '-') && server->umode[0] == '\0')
		      strcpy(server->umode, p);
		  else
		      vsic_write("MODE %s", server->nick); /* cheap trick */
		}
	      else if (froms)
		  fmt = F_SMODE;
	      else
		  fmt = F_CMODE;
	  else
	      fmt = F_UNKNOWN;
	}
    }

  select_active((toch) ? tochannel : NULL, tocurrent);
  if (toch == 0 && (flags & (LOG_MSG|LOG_NOTICE|LOG_DCC)))
      if (fromv || froms)
	  select_query(sender);
      else
	{
	  char tmpq[11];

	  sprintf(tmpq, "%s%s", (flags & LOG_DCC) ? "=" : "", nick);
	  select_query(tmpq);
	}

  if (fmt != -1)
    {
      char *fmt_str;
      int rendering;

      if (fmt == F_NUM)
	  fmt_str = get_nformat(n, (toch) ? tochannel : NULL);
      else
	  fmt_str = get_format(fmt, (toch) ? tochannel : NULL);
      if (*fmt_str == '\0')
	{
	  flags |= LOG_HIDE;
	  fmt_str++;
	}
      rendering = sic_sprintf(outp, outa, fmt_str, (toch) ? tochannel : NULL,
			      &flags);

#if defined(HAVE_REGEXP)
      rendering += get_rewrite(fmt, outp, outa, &flags,
			       (toch) ? tochannel : NULL);
#endif

      sic_log(flags, 0, s, c, (toself) ? NULL : d, p, fmt, outp,
	      (rendering) ? outa : NULL);
    }
  else
      vsic_slog(LOG_CLIENT, "Wooops: %s", cmd);

  if (ctcp)
      sic_ctcp(nick, toself, p);
}

void
sic_reformat(log)
  struct log_ *log;
{
  char *fmt_str;

  /* locals is unused,
     toself and toch hace no use here,
     showid isn't reusable :/ */
  locals = toself = froms = fromv = toch = showid = 0;
  n = 0;

  if (log->fmt == -1 || log->fmt == F_SPLIT)
      return;
  s = log->origin; c = log->cmd; d = log->dest; p = log->para;
  sic_splitorig(log->origin);
  if (log->flags & LOG_DCC && log->origin)
    {
      char *at;

      strncpy(nick, log->origin, 10);
      at = index(nick, '@');
      if (at)
	  *at = '\0';
      else
	  nick[9] = '\0';
    }
  if (log->flags & LOG_SERVICE)
      fromv = 1;
  if (log->flags & LOG_SERVER)
      froms = 1;

  log->flags &= ~(LOG_HIGHLIGHT|LOG_KEYWORD | LOG_HIDE|LOG_IGNORE);
  if (log->fmt == F_NUM)
      fmt_str = get_nformat(n = atoi(log->cmd), log->dest);
  else
      fmt_str = get_format(log->fmt, log->dest);
  if (*fmt_str == '\0')
    {
      fmt_str++;
      log->flags |= LOG_HIDE;
    }
  free(log->prefix);
  if (log->prefix = get_format(F_PREFIX, log->dest))
    {
      char pbuf[80]; /* should be more than enough */
      
      my_cftime(pbuf, 80, log->prefix, log->ts);
      log->prefix = strdup(pbuf);
    }
  log->plen = strlen(log->prefix);

  free(log->formatted);
  free(log->attributes); log->attributes = NULL;
  if (sic_sprintf(outp, outa, fmt_str, log->dest, &(log->flags)))
    {
      log->attributes = (u_char *) malloc(log->len+1);
      bcopy(outa, log->attributes, log->len+1);
    }
  log->formatted = strdup(outp);
  log->len = strlen(log->formatted);
}
