/*
 * commands.c: all the client commands.
 *
 * Copyright(c) 1997,1998 - All Rights Reserved
 *
 * See the COPYRIGHT file.
 */

#ifndef lint
static char rcsid[] = "@(#)$Id: commands.c,v 1.55 1998/09/03 20:38:07 kalt Exp $";
#endif

#include "os.h"

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

extern struct server_ *server;

extern char tab_add(char *, char *);
extern char space_add(char *, char *);
extern int cmd_ctcp(char *);
extern int cmd_ping(char *);
extern int cmd_seen(char *);
extern int cmd_lastlog(char *);
extern int sic_wreformat();

void parse_command(char *, int);

struct cmdlist_
{
  char	*name;
  int	(*func) (char *);
};

struct aliaslist_
{
  char	*name;
  char	*eval;
  struct aliaslist_ *nexta;
};

static struct aliaslist_ *alias = NULL;

/* return codes:
 *	0	"success"
 *	-1	"need more parameters"
 *	-2	"no recipient"
 *	-3	"no text to send"
 *	-4	"bad params"
 *	-5	"bind window to server first"
 *	-9	"not implemented"
 */

static char		outp[1024], outa[1024];

/* cmd_smart: if the first word in the buffer is not a channel, prepend
 *	      the window default channel name to the buffer.
 */
static char *
cmd_smart(ibuf)
  char *ibuf;
{
  static char newbuf[1024];
  struct channel_ *ctmp;
  char *sp;

  if (*ibuf == '&' || *ibuf == '#' || *ibuf == '+')
    {
      char known;
      if (sp = index(ibuf, ' '))
	  *sp = '\0';
      known = map_c2w(ibuf);
      if (sp)
	  *sp = ' ';
      if (known)
	  return ibuf;
    }
  ctmp = get_channel(NULL);
  if (ctmp == NULL)
      return "";
  sprintf(newbuf, "%s%s%s", ctmp->chname, (*ibuf) ? " " : "", ibuf);
  return newbuf;
}

/*
 * IRC commands
 */
static int
cmd_epart(p, forget)
  char *p;
  char forget;
{
  char *c;

  p = cmd_smart(p);
  c = index(p, ' ');
  if (!*p)
      return -1;
  if (c)
      *c++ = '\0';

  if (map_c2w(p) == 0)
    {
      vsic_slog(LOG_CLIENT, "--- Channel %s is unknown to me", p);
      return 0;
    }

  part_channel(p, (c) ? c : "", forget);

  return 0;
}

static int
cmd_cmode(p)
  char *p;
{
  p = cmd_smart(p);
  if (!*p)
      return -1;
  vsic_write("MODE %s", p);
  return 0;
}

static int
cmd_join(p)
  char *p;
{
  char *key = index(p, ' ');

  if (!*p)
      return -1;
  if (index(p, ','))
      return -9; /* better this than a crash */
  if (server == NULL)
      return -5;
  if (key)
      *key = '\0';
  switch (map_c2w(p))
    {
  case 0 :
      new_channel(p);
      break;
  case 1 :
      break;
  case 2 :
      vsic_slog(LOG_CLIENT,
		"--- Channel %s was mapped to a different window", p);
      sic_wchannel(p);
      break;
  default :
      abort(); /* never */
    }
  join_channel(p, 0);
  return 0;
}

static int
cmd_kick(p)
  char *p;
{
  char *comment;

  p = cmd_smart(p);
  if (!*p)
      return -1;
  if (comment = index(p, ' '))
      if (comment = index(comment+1, ' '))
	  *comment++ = '\0';
  vsic_write("KICK %s :%s", p, (comment) ? comment : "");
  return 0;
}

static int
cmd_leave(p)
{
  return cmd_epart(p, 0);
}

static int
cmd_list(p)
  char *p;
{
  vsic_write("LIST %s", (*p) ? p : cmd_smart(p));
  return 0;
}

int
cmd_msgnotice(cmd, p)
char *cmd, *p;
{
  char *txt = index(p, ' '), fmt;
  int rendering;
  unsigned int flags = 0;

  if (!*p)
      return -2;
  if (cmd)
    {
      if (!txt || !*(txt+1))
	  return -3;
      *txt++ = '\0';
      if (rmatch("*@[*]", p))
	{
	  char *s = index(p, ']');

	  *s = '\0';
	  s = index(p, '@');
	  *s++ = '\0';
	  s += 1;
	  if (select_server(s) == NULL)
	    {
	      vsic_slog(LOG_CLIENT, "--- No connected to %s.", s);
	      return 0;
	    }
	}
      if (*p != '&' && *p != '#' && *p != '+' && *txt != '\001')
	  tab_add(p, (*cmd == 'S') ? NULL : "");
      if (*p == '=')
	  if (*cmd == 'N')
	      return -4;
	  else
	    {
	      p++;
	      cmd = "DCC";
	    }
    }
  else
    {
      cmd = "PRIVMSG";
      txt = p;
      if (p = get_query())
	{
	  if (*p == '=')
	    {
	      cmd = "DCC";
	      p++;
	    }
	  else if (index(p, '@'))
	      cmd = "SQUERY";
	}
      else
	{
	  if (get_channel(NULL) == NULL)
	      return -3;
	  p = get_channel(NULL)->chname;
	}
    }

  if (*p != '=' && server == NULL)
      return -5;
  fmt = get_channel(p) ? (*cmd == 'P') ? F_MYPUBM : F_MYPUBN :
      (*cmd == 'D') ? F_MYDCC : (*cmd == 'P') ? F_MYPRIVM : F_MYPRIVN;
  rendering = out_sprintf(outp, outa, get_format(fmt, p), p, cmd, txt);
  select_active(get_channel(p) ? p : NULL, 0);
  if (*cmd == 'P' && get_channel(p) == NULL)
    {
      char tmpq[512];
      
      sprintf(tmpq, "%s%s", (*cmd == 'D') ? "=" : "", p);
      select_query(tmpq);
    }
  flags = LOG_CLIENT;
  flags |= (*cmd == 'D') ? LOG_DCC : (*cmd == 'P') ? LOG_MSG : LOG_NOTICE;
  flags |= get_channel(p) ? LOG_PUBLIC : LOG_PRIVATE;
  if (*cmd == 'N' && *txt == '\001'
      && !get_option(Z_VCTCP, (get_channel(p)) ? p : NULL))
      flags |= LOG_HIDE;
  sic_log(flags, 0, NULL, cmd, p, txt, fmt, outp, rendering ? outa : NULL);
  if (*cmd == 'D')
      vsic_dwrite(p, "%s", txt);
  else
      vsic_write("%s %s :%s", cmd, p, txt);
  if (get_channel(p) && (p = index(txt, ':')) < index(txt, ' ') && p)
    {
      *p = '\0';
      space_add(txt, "");
    }
  return 0;
}

static int
cmd_msg(p)
  char *p;
{
  return cmd_msgnotice("PRIVMSG", p);
}

static int
cmd_names(p)
  char *p;
{
  vsic_write("NAMES %s", (*p) ? p : cmd_smart(p));
  return 0;
}

static int
cmd_notice(p)
  char *p;
{
  return cmd_msgnotice("NOTICE", p);
}

static int
cmd_part(p)
  char *p;
{
  return cmd_epart(p, 1);
}

static int
cmd_quit(p)
  char *p;
{
  vsic_write("QUIT :%s", p);
  cmd_server(NULL);
  return 0;
}

static int
cmd_squery(p)
  char *p;
{
  char *txt = index(p, ' ');
  int rendering;

  if (!*p)
      return -2;
  if (server == NULL)
      return -5;
  if (!txt || !*(txt+1))
      return -3;
  *txt++ = '\0';

  vsic_write("SQUERY %s :%s", p, txt);
  rendering = out_sprintf(outp, outa, get_format(F_MYQUERY, NULL),
			  p, "SQUERY", txt);
  select_active(NULL, 0);
  sic_log(LOG_CLIENT|LOG_PRIVATE|LOG_MSG,
	  0, NULL, "SQUERY", p, txt, F_MYQUERY, outp, rendering ? outa : NULL);
  return 0;
}

static int
cmd_topic(p)
  char *p;
{
  char *comment;

  if (!*p)
      p = cmd_smart(p);
  if (!*p)
      return -1;
  if (comment = index(p, ' '))
      *comment++ = '\0';
  vsic_write("TOPIC %s%c%s", p, (comment) ? ':' : ' ',
	     (comment) ? comment : "");
  return 0;
}

static int
cmd_umode(p)
  char *p;
{
  if (server == NULL)
      return -5;
  vsic_write("MODE %s %s", server->nick, p);
  return 0;
}

static int
cmd_who(p)
  char *p;
{
  vsic_write("WHO %s", (*p) ? p : cmd_smart(p));
  return 0;
}

/*
 * internal commands
 */
int
cmd_alias(p)
  char *p;
{
  char tmp[1024], *wp = tmp, *txt;
  struct aliaslist_ *atmp = alias;

  if (txt = index(p, ' '))
    {
      *txt++ = '\0';
      
      while (atmp)
	{
	  if (!strcasecmp(p, atmp->name))
	      break;
	  atmp = atmp->nexta;
	}
      if (atmp)
	  free(atmp->eval);
      else
	{
	  atmp = (struct aliaslist_ *) malloc(sizeof(struct aliaslist_));
	  atmp->name = strdup(p);
	  atmp->nexta = alias;
	  alias = atmp;
	}
      while (*txt)
	{
	  if (*txt == '%' && isdigit(*(txt+1)))
	    {
	      *wp++ = *txt++; /* % */
	      *wp++ = '{';
	      while (isdigit(*txt))
		  *wp++ = *txt++;
	      *wp++ = '}';
	    }
	  else
	      *wp++ = *txt++;
	}
      *wp = '\0';
      atmp->eval = strdup(tmp);
    }
  else
    {
      int len = strlen(p);

      sic_slog(LOG_CLIENT, "--- Aliases:");
      while (atmp)
	{
	  if (!strncasecmp(p, atmp->name, len))
	      vsic_slog(LOG_CLIENT, "---     %-8s: %s", atmp->name,atmp->eval);
	  atmp = atmp->nexta;
	}
    }
  return 0;
}

static int
cmd_clear(p)
  char *p;
{
  if (*p && !strcasecmp(p, "-f"))
      sic_clog(1);
  else
      sic_clog(0);
  sic_scroll2(+1, NULL);
  sic_redowin(0);
  return 0;
}

static int
cmd_exit(p)
  char *p;
{
  sic_slog(LOG_CLIENT, "Exiting...");
  term_end();
  exit(0);
}

static int
cmd_dns(p)
  char *p;
{
  if (*p)
    {
      vsic_slog(LOG_CLIENT, "--- Looking up \"%s\" in DNS", p);
      dns_lookup(p);
      return 0;
    }
  else
      return -1;
}

int
cmd_option(p)
  char *p;
{
  static char *channel = NULL;
  char *c, *v;
  unsigned int flag, all = 0;
  int fnum = -1;

  if (!*p)
      return -1;

  if (channel)
    {
      free(channel);
      channel = NULL;
    }
  if (v = index(p, ' '))
      *v++ = '\0';

  switch (*p)
    {
  case 'c': case 'C':
      flag = O_CLEAR;
      break;
  case 'g': case 'G':
      flag = O_GET;
      break;
  case 's': case 'S':
      flag = O_SET;
      break;
  case 'r': case 'R':
      sic_wreformat();
      sic_redowin(1);
      return 0;
  default:
      return -4;
    }
  if ((c = index(p, '.')) == NULL)
      return -4;
  switch (*(++c))
    {
  case 'a': case 'A':
      all = 1;
      if (flag != O_GET)
	  return -4;
      break;
  case 'c': case 'C':
      if ((c = index(p, '=')) == NULL)
	  return -4;
      if ((p = index(c, '.')) == NULL)
	  return -4;
      *p = '\0';
      if (map_c2w(++c) == 0)
	{
	  vsic_slog(LOG_CLIENT, "%s: No such channel", c);
	  return 0;
	}
      channel = strdup(c);
      *(c = p) = '.';
      break;
  case 'd': case 'D':
      if (flag != O_GET)
	  return -4;
      flag |= O_DEFAULT;
      break;
  case 'p': case 'P':
      flag |= O_PROTO;
      break;
  case 's': case 'S':
      flag |= O_SERVER;
      break;
  case 't': case 'T':
      flag |= O_TOPLVL;
      break;
  case 'w': case 'W':
      flag |= O_WINDOW;
      break;
  default:
      return -4;
    }
  if ((p = index(c, '.')) == NULL)
      return -4;
  if (c = index(++p, '.'))
      *(c++) = '\0';
  if (!strcasecmp(p, "on"))
      flag |= O_ON;
  else if (!strcasecmp(p, "off"))
      flag |= O_OFF;
  else if (*p == '#' && isdigit(*(p+1)))
      fnum = atoi(p+1) + F_MAX+1;
  else if (isdigit(*p))
      fnum = atoi(p);
  else if (*p == 'k' || *p == 'K')
      flag |= O_KEYWORD;
  else if (*p == 'l' || *p == 'L')
      flag |= O_LOGFILE;
  else if (*p == 's' || *p == 'S')
      flag |= O_SWITCH;
  else if (*p == 'r' || *p == 'R')
      flag |= O_REWRITE;
  else
      return -4;
  if (flag & (O_ON|O_OFF))
    {
      if (c == NULL)
	  return -4;
      flag |= O_MASK;
    }
  if (flag & (O_GET|O_CLEAR))
    {
      unsigned char	i;
      char		*str = NULL;
      unsigned int	all_list[] = {O_DEFAULT, O_TOPLVL, O_SERVER, O_WINDOW},
      			ui = 0;

      for (i = 0; (i == 0 || all) && i < 4; i++)
	{
	  if (flag & (O_MASK|O_SWITCH))
	      str = c;
	  if (flag & O_KEYWORD)
	      str = v;
	  if (flag & O_REWRITE)
	    {
	      ui = (c) ? atoi(c) : F_MAX+1;
	      str = v;
	    }
	  customize((all) ? flag|all_list[i] : flag, channel, fnum, &str, &ui);
	  if (flag & O_GET)
	    {
	      if (flag & (O_KEYWORD|O_REWRITE))
		{
		  int count = fnum; /* -1 */

		  do
		    {
		      if (str)
			  if (flag & O_KEYWORD)
			      vsic_slog(LOG_CLIENT, "Keyword: %5.5X %s",
					ui, str);
			  else
			      vsic_slog(LOG_CLIENT, "Rewrite: %3u %s",
					ui, str);
		      str = v; fnum = --count;
		      if (flag & O_REWRITE)
			  ui = (c) ? atoi(c) : F_MAX+1;
		      customize((all) ? flag|all_list[i] : flag, channel,
				fnum, &str, &ui);
		    }
		  while (str != NULL && v == NULL);
		  fnum = -1;
		}
	      else if (flag & O_MASK)
		{
		  char *str2 = c;
		  unsigned int ui2 = 0;
		  
		  customize((all) ? (flag|all_list[i])^(O_ON|O_OFF) :
			    flag^(O_ON|O_OFF), channel, fnum, &str2, &ui2);
		  
		  vsic_slog(LOG_CLIENT, "Set to: \"%8.8X\" - \"%8.8X\"",
			    ui, ui2);
		}
	      else if (flag & O_LOGFILE)
		  vsic_slog(LOG_CLIENT, "Log: %s, Filter: %d", str, ui);
	      else if (str)
		  vsic_slog(LOG_CLIENT, "Set to: \"%c%s\"",
			    (*str) ? *str : ':', str+1);
	      else
		  vsic_slog(LOG_CLIENT, "Set to: %8.8X", ui);
	    }
	}
      return 0;
    }
  if (v)
    {
      unsigned int	ui = 0;
      char		*str = NULL;

      if (flag & O_SWITCH)
	{
	  if (!strcasecmp(v, "on"))
	      ui = 1;
	  else
	      ui = 0;
	  str = c;
	}
      else
	{
	  ui = (unsigned int) strtoul(v, &str, 0);
#if !defined(HAVE_REGEXP)
	  if (flag & O_REWRITE || ((flag & O_KEYWORD) && (ui & K_REGEXP)))
	    {
	      sic_slog(LOG_CLIENT,
		       "--- Option not available (requires regexp library).");
	      return 0;
	    }
#endif
	  if (flag & O_MASK)
	      str = c;
	  else if (flag & O_REWRITE)
	      ui = atoi(c);
	  else if (str)
	      while (*str && isspace(*str))
		  str++;
	}
      customize(flag, channel, fnum, &str, &ui);
      if (flag & (O_LOGFILE|O_REWRITE|O_KEYWORD) && str)
	vsic_slog(LOG_CLIENT, "--- Error: %s", str);
      return 0;
    }
  else
      return -4;
}

static int
cmd_query(p)
  char *p;
{
  char *txt = index(p, ' ');

  if (txt)
      *txt = '\0';
  if (map_c2w(p) == 0)
    {
      default_channel(p);
      if (txt && *(txt+1))
	{
	  *txt = ' ';
	  cmd_msgnotice("PRIVMSG", p);
	}
    }
  else
      sic_slog(LOG_CLIENT,"--- Cannot query a channel!");      
  return 0;
}

static int
cmd_window(p)
  char *p;
{
  if (!*p)
      return -1;
  if (!strcasecmp(p, "new"))
      sic_newwin();
  else if (!strcasecmp(p, "next"))
      sic_chgwin(1);
  else if (!strcasecmp(p, "kill"))
      sic_wkill();
  else if (!strcasecmp(p, "list"))
      sic_wlist();
  else if (!strcasecmp(p, "release"))
      sic_swin(2);
  else if (!strncasecmp(p, "number ", 7))
      if (p = index(p, ' '))
	  if (atoi(++p) >= 0 && atoi(p) < 11)
	      sic_wch(atoi(p));
	  else
	      return -4;
      else
	  return -1;
  else if (!strncasecmp(p, "channel ", 8))
      if (map_c2w(p+8) == 1)
	  default_channel(p+8);
      else
	  sic_slog(LOG_CLIENT,"--- No such channel or channel not in window.");
  else if (!strncasecmp(p, "dcc", 3))
      sic_wdcc();
  else
      return -4;
  return 0;
}

struct cmdlist_	cmdlist[] = {
	{ "ADMIN",	NULL },
	{ "ALIAS",	cmd_alias},
	{ "AWAY",	NULL },
	{ "CLOSE",	NULL },
	{ "CONNECT",	NULL },
	{ "CLEAR",	cmd_clear},
	{ "CMODE",	cmd_cmode},
	{ "CTCP",	cmd_ctcp},
	{ "DCC",	cmd_dcc},
	{ "DIE",	NULL },
	{ "DNS",	cmd_dns},
	{ "EXIT",	cmd_exit},
	{ "INFO",	NULL },
	{ "INVITE",	NULL },
	{ "ISON",	NULL },
	{ "JOIN",	cmd_join },
	{ "KICK",	cmd_kick},
	/*{ "KILL",	NULL },*/
	{ "LASTLOG",	cmd_lastlog},
	{ "LEAVE",	cmd_leave},
	{ "LINKS",	NULL },
	{ "LIST",	cmd_list},
	{ "LUSERS",	NULL },
	{ "MOTD",	NULL },
	{ "MODE",	NULL },
	{ "MSG",	cmd_msg},
	{ "NAMES",	cmd_names},
	{ "NICK",	NULL },
	{ "NOTICE",	cmd_notice},
	{ "OPER",	NULL },
	{ "OPTION",	cmd_option},
	{ "PART",	cmd_part},
	{ "PING",	cmd_ping},
	/* PING/PONG */
	{ "QUERY",	cmd_query},
	{ "QUIT",	cmd_quit},
	{ "REHASH",	NULL },
	{ "RESTART",	NULL },
	{ "SEEN",	cmd_seen},
	{ "SERVER",	cmd_server},
	{ "SERVLIST",	NULL },
	{ "SQUERY",	cmd_squery},
	/*{ "SQUIT",	NULL },*/
	{ "STATS",	NULL },
	{ "TIME",	NULL },
	{ "TOPIC",	cmd_topic },
	{ "TRACE",	NULL },
	{ "UMODE",	cmd_umode},
	{ "USERHOST",	NULL },
	{ "VERSION",	NULL },
	{ "WINDOW",	cmd_window },
	{ "WHO",	cmd_who },
	{ "WHOIS",	NULL },
	{ "WHOWAS",	NULL },
	{ NULL,		NULL }
	};

/* parse_alias: parse and execute an alias */
void
parse_alias(acmd, str)
  struct aliaslist_ *acmd;
  char *str;
{
  static unsigned char recursion = 0;
  char *slice = acmd->eval, *wp = acmd->eval, *rp;
  char command[1024];

  if (recursion++ >= 5)
    {
      vsic_slog(LOG_CLIENT, "--- Alias %s: maximum recursion exceeded.",
		acmd->name);
      return;
    }
  while (wp)
    {
      slice = index(slice+1, ';');
      command[0] = '\0'; rp = command;
      while(*wp && (!slice || wp < slice))
	{
	  while(*wp && (!slice || wp < slice) && *wp != '%')
	      *rp++ = *wp++;
	  if (*wp && (!slice || wp < slice))
	    {
	      wp++; /* skip % */
	      *rp = '\0'; /* for strcat() to work */
	      switch (*wp++)
		{
	      case '%':
		  *rp++ = '%'; *rp = '\0';
		  break;
	      case '*':
		  strcat(command, str);
		  break;
	      case '{':
		  strcat(command, sic_split(str, wp-1));
		  while (*wp != '}')
		      wp++;
		  wp++;
		  break;
		}
	      while (*rp != '\0')
		  rp++;
	    }
	}
      *rp = '\0';
      if (*command == '/')
	  parse_command(command+1, 1);
      else
	  cmd_msgnotice(NULL, command);
      wp = slice;
    }
  recursion--;
}

/* parse_command: parse an input string that is a command/alias. */
void
parse_command(str, aliases)
  char *str;
  int aliases;
{
  int i = -1, len;
  char *p;

  select_active(NULL, 2);
  p = index(str, ' ');
  if (p)
    {
      *p = '\0';
      while (*p++ == ' ');
    }
  else
      p = "";
  len = strlen(str);

  while (cmdlist[++i].name)
    {
      if (!strncasecmp(cmdlist[i].name, str, len))
	  break;
    }

  if (aliases)
    {
      struct aliaslist_ *atmp = alias;
      struct aliaslist_ *acmd = NULL;

      while (atmp)
	{
	  if (!strncasecmp(atmp->name, str, len))
	    {
	      if (len == strlen(atmp->name))
		  break;
	      if (!acmd)
		  acmd = atmp;
	    }
	  atmp = atmp->nexta;
	}
      if (atmp)
	{
	  if (len != strlen(atmp->name) && acmd)
	    {
	      vsic_slog(LOG_CLIENT, "--- %s: Ambiguous command.", str);
	      return;
	    }
	  acmd = atmp;
	  if (cmdlist[i].name && len != strlen(acmd->name))
	    {
	      vsic_slog(LOG_CLIENT, "--- %s: Ambiguous command.", str);
	      return;
	    }
	  parse_alias(acmd, p);
	  return;
	}
    }

  if (cmdlist[i].name)
    {
      if (len != strlen(cmdlist[i].name)
	  && cmdlist[i+1].name && !strncasecmp(cmdlist[i+1].name, str, len))
	{
	  vsic_slog(LOG_CLIENT, "--- %s: Ambiguous command.", str);
	  return;
	}

      if (cmdlist[i].func)
	  switch (cmdlist[i].func(p))
	    {
	  case 0:
	      break;
	  case -1:
	      vsic_slog(LOG_CLIENT, "%s: Not enough parameters.",
			cmdlist[i].name);
	      break;
	  case -2:
	      vsic_slog(LOG_CLIENT, "%s: No recipient specified.",
			cmdlist[i].name);
	      break;
	  case -3:
	      vsic_slog(LOG_CLIENT, "%s: No text to send.", cmdlist[i].name);
	      break;
	  case -4:
	      vsic_slog(LOG_CLIENT, "%s: Invalid parameter(s).",
			cmdlist[i].name);
	      break;
	  case -5:
	      vsic_slog(LOG_CLIENT, "%s: Bind the window to a server first!",
			cmdlist[i].name);
	      break;
	  case -9:
	      vsic_slog(LOG_CLIENT, "%s: functionality not implemented.",
			cmdlist[i].name);
	      break;
	  default:
	      abort(); /* never */
	    }
      else
	  vsic_write("%s %s", cmdlist[i].name, p);
    }
  else
      vsic_slog(LOG_CLIENT, "%s: Unknown command.", str);
}
