/*
 * window.c: routines for the windows manipulations
 *
 * Copyright(c) 1997,1998 - All Rights Reserved
 *
 * See the COPYRIGHT file.
 */

#ifndef lint
static char rcsid[] = "@(#)$Id: window.c,v 1.58 1998/08/17 20:29:00 kalt Exp $";
#endif

#include "os.h"

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

#define		sic_slog(x, y)	sic_log(x, 0, NULL, NULL, NULL, NULL, -1, y, NULL)
void		vsic_log(unsigned int, char *, char *, char *,
			 char *, char, char *, ...);
void		sic_redowin(int);
void		collect_channel();

extern sic_reformat(struct log_ *);
extern struct server_	*server;

char	need_collect = 0;

struct window_		*wlist = NULL, *current, *oldcurrent, *active;
static struct channel_	*clist = NULL;

static int	refresh_status;

static void	display_line(int, struct log_ *);

#define	LNCNTWP(x)	(((x->plen + x->len-1) / (CO-1)) + 1)
#define	LNCNTNP(x)	(((x->len-1) / (CO-1)) + 1)
#define	LNCNT(x)	((option(current->wopt, W_NOPREFIX)) ? LNCNTNP(x) : \
			 LNCNTWP(x))
#define	PLNLEN(x)	(option(current->wopt, W_NOPREFIX) ? CO-1 : \
			 CO-1-x->plen)

/*
 * GENERAL USEFUL FUNCTIONS
 */
struct channel_ *
get_channel(name)
  char *name;
{
  struct channel_ *ctmp = clist;

  if (name)
      while (ctmp && (strcasecmp(name, ctmp->chname) || server != ctmp->on->via))
	  ctmp = ctmp->nextc;
  else
      ctmp = current->defchan;

  return ctmp;
}

/*
 * LOGGING
 */

/* sic_log: ``display'' a string in the ``active'' window
 *	the string is added to the lastlog
 *	it will only really be displayed if the ``active == current''
 *
 *	(the active window must be set before calling this window)
 *	the second parameter is optional, it is used for video attributes
 */
void
sic_log(flags, ts, o, c, d, p, fmt, str, attr)
char            *str, *attr, *o, *c, *d, *p, fmt;
unsigned int	flags;
time_t		ts;
{
  struct log_ *new;

  assert (active && current);

  if (flags & LOG_MULTICAST)
    {
      struct window_	*wtmp = wlist, *oldactive = active;

      flags &= ~LOG_MULTICAST;
      while (wtmp)
	{
	  if (option(wtmp->wopt, W_MCAST))
	    {
	      active = wtmp;
	      sic_log(flags, ts, o, c, d, p, fmt, str, attr);
	    }
	  wtmp = wtmp->nextw;
	}
      active = oldactive; /* probably useless precaution */
      return;
    }
  /* smart split handling, kind of weird to put it here :/ */
  if (flags & LOG_SQUIT)
      if (!active->split || strcasecmp(active->split, p)
	  || active->split_t < time(NULL))
	{
	  char  *wp;
	  
	  if (active->split)
	      free(active->split);
	  active->split = strdup(p);
	  active->split_t = time(NULL) + 15;
	  wp = index(p, ' ');
	  assert(wp);
	  *wp = '\0';
	  vsic_log((flags & ~LOG_SQUIT) | LOG_SPLIT, "<internal>", "SPLIT",
		   d, active->split, F_SPLIT, get_format(F_SPLIT, d), p, wp+1);
	  *wp = ' ';
	}

#if 0
  if (o && ((flags & (LOG_PRIVATE|LOG_MSG)) == (LOG_PRIVATE|LOG_MSG)
	    || (flags & (LOG_PRIVATE|LOG_DCC)) == (LOG_PRIVATE|LOG_DCC)))
#else
  if (o && get_flag(flags, d, 10))
#endif
      set_option(active->wopt, W_ACTIVITY);
  new = (struct log_ *) malloc(sizeof(struct log_));
  bzero(new, sizeof(struct log_));
  
  if (active->llog)
      active->llog->nextl = new;
  else
      active->flog = new;
  new->prevl = active->llog;
  
  new->flags = flags;
  if (o)
      new->origin = strdup(o);
  if (c)
      new->cmd = strdup(c);
  if (d)
      new->dest = strdup(d);
  if (p)
      new->para = strdup(p);
  new->fmt = fmt;

  new->ts = (ts) ? ts : time(NULL);
  if (new->prefix = get_format(F_PREFIX, d))
    {
      char pbuf[80]; /* should be more than enough */

      my_cftime(pbuf, 80, new->prefix, new->ts);
      new->prefix = strdup(pbuf);
    }
  new->plen = strlen(new->prefix);

  new->formatted = strdup(str);
  new->len = strlen(str);
  if (attr)
    {
      new->attributes = (char *) malloc(strlen(str)+1);
      bcopy(attr, new->attributes, strlen(str)+1);
    }
  
  active->llog = new;

  /*
   * display the new line if it is the current window and we're at the bottom.
   * could be done using the scrolling routines?
   */
  if (active->ldisp == new->prevl && !get_option(Z_HOLD, d))
    {
      active->ldisp = new; /* not scrolling anything */
      active->ltrunc = 0; /* line is entirely displayed */
      if (active == current)
	  if (get_flag(flags, d, active->fnb) || get_option(Z_SHOWALL, d))
	    {
	      int line_count = LNCNT(new);
	      int i = 0;
	      
	      term_scroll(0, LI-3, line_count);
	      while (i < line_count)
		{
		  term_move_cursor(0, LI-2-line_count+i);
		  display_line(i++, new);
		}
	    }
    }
  sic_logfile(new);
}

void
vsic_log(unsigned int flags, char *o, char *c, char *d, char *p, char fmt,
	 char *format, ...)
{
  char  buffer[1024];
  va_list va;
 
  va_start(va, format);
  vsprintf(buffer, format, va);
  va_end(va);
  sic_log(flags, 0, o, c, d, p, fmt, buffer, NULL);
}

void
vsic_slog(unsigned int flags, char *format, ...)
{
  char  buffer[1024];
  va_list va;
 
  va_start(va, format);
  vsprintf(buffer, format, va);
  va_end(va);
  sic_slog(flags, buffer);
}

/* sic_dlog: delete log entries */
void
sic_dlog(force)
  int force;
{
  struct log_ *del, *tmpl = current->flog;
  time_t now = time(NULL);
  unsigned int cnt = 0, dcnt = 0;

  if (!(current->flog && current->flog->flags & LOG_DEL) && (force == 0))
      return;
  while (tmpl)
    {
      tmpl = (del = tmpl)->nextl;
      cnt++;
      if (force
	  || ((del->flags & LOG_DEL)
	      && ((del->flags & LOG_QUICKDEL) || now > del->delts)))
	{
	  if (del->prevl)
	      del->prevl->nextl = del->nextl;
	  if (del->nextl)
	      del->nextl->prevl = del->prevl;

	  if (current->flog == del)
	      current->flog = del->nextl;
	  if (current->llog == del)
	      current->llog = del->nextl;
	  if (current->ldisp == del)
	      current->ldisp = del->nextl;

	  if (del->origin)
	      free(del->origin);
	  if (del->cmd)
	      free(del->cmd);
	  if (del->dest)
	      free(del->dest);
	  if (del->para)
	      free(del->para);
	  if (del->prefix)
	      free(del->prefix);
	  if (del->formatted)
	      free(del->formatted);
	  if (del->attributes)
	      free(del->attributes);
	  free(del);
	  dcnt++;
	}
    }
#if defined(DEBUG)
  /* it's a memory leak when called from sic_wkill() */
  vsic_slog(LOG_DEBUG, "sic_dlog(%d): %u out of %u", force, dcnt, cnt);
#endif
}

/* sic_clog: clear the lastlog for the current window */
void
sic_clog(force)
  int force;
{
  struct log_ *tmpl = current->llog;
  time_t now = time(NULL) + 3600;
  unsigned int cnt = 0;

  unset_option(current->custw.zopt_on, Z_UNDEL);
  if (force)
      sic_dlog(1);
  else
    {
      while (tmpl && !(tmpl->flags & LOG_DEL))
	{
	  tmpl->flags |= LOG_DEL;
	  tmpl->delts = now;
	  tmpl = tmpl->prevl;
	  cnt++;
	}
      vsic_slog(LOG_DEBUG, "sic_clog(0): %u", cnt);
      sic_dlog(0); /* not very efficient */
    }
}

/* cmd_seen: internal whowas */
int
cmd_seen(p)
  char *p;
{
  char *c;
  int max = 3, skip = 0, hit = 0;
  struct log_ *ltmp = active->llog, *found[100];

  if (!*p)
      return -1;
  if (c = index(p, ' '))
    {
      *c++ = '\0';
      max = atoi(c);
      if (c = index(c, ' '))
	  skip = atoi(c+1);
    }
  if (max < 0 || skip < 0)
      return -4;
  if (max > 100)
      max = 100;
  while (ltmp && (max == 0 || hit < max))
    {
      if (ltmp->flags & LOG_USER && rmatch(p, ltmp->origin))
	{
	  int i = hit;

	  if (hit)
	    {
	      for (i = 0; i < hit; i++)
		  if (!strcasecmp(found[i]->origin, ltmp->origin))
		      break;
	    }
	  if (i == hit)
	    {
	      found[hit++] = ltmp;
	      if (skip == 0)
		  vsic_slog(LOG_CLIENT, "--- Seen %s%s%s.", ltmp->origin,
			    (ltmp->dest) ? " on " : "",
			    (ltmp->dest) ? ltmp->dest : "");
	      else
		  skip--;
	    }
	}
      ltmp = ltmp->prevl;
    }
  if (hit == 0)
      vsic_slog(LOG_CLIENT, "--- No match found.");
  return 0;
}

/* cmd_lastlog: redisplay from the /lastlog */
int
cmd_lastlog(p)
  char *p;
{
  unsigned int mask = LOG_ALL;
  char *pattern = (*p) ? p : NULL;
  struct log_ *tmpl = current->flog;

  if (!strncmp("0x", p, 2))
    {
      sscanf(p, "%x", &mask);
      pattern = index(p, ' ');
    }
  vsic_slog(LOG_CLIENT|LOG_LASTLOG, "--- Lastlog:");
  while (tmpl)
    {
      if ((tmpl->flags & LOG_LASTLOG) == 0 && (tmpl->flags & mask))
	  if (!pattern || rmatch(pattern, tmpl->prefix)
	      || strcasestr(tmpl->formatted, pattern))
	    {
	      sic_log(tmpl->flags | LOG_LASTLOG, tmpl->ts, tmpl->origin,
		      tmpl->cmd, tmpl->dest, tmpl->para, tmpl->fmt,
		      tmpl->formatted, tmpl->attributes);
	    }
      tmpl = tmpl->nextl;
    }
  vsic_slog(LOG_CLIENT|LOG_LASTLOG, "--- End of lastlog.");
  return 0;
}

/* sic_wreformat: reformat the log */
void
sic_wreformat(p)
  char *p;
{
  struct log_ *tmpl = current->llog;

  while (tmpl)
    {
      sic_reformat(tmpl);
      tmpl = tmpl->prevl;
    }
  sic_redowin(1);
  vsic_slog(LOG_CLIENT|LOG_LASTLOG, "--- Reformatted lastlog.");
}

/* display_status: formats the status line */
/* this should be customizable */
void
display_status()
{
  static char st[160];
  struct channel_ *ctmp = clist;
  struct window_ *wtmp = wlist;
  int chcnt = 0;
  char chbuf[6] = "", actbuf[40] = "", tbuf[6],
       buf[160]; /* hmmpf */

  my_strftime(tbuf, 6, "%H:%M");

  while (ctmp)
    {
      if (ctmp->on == current)
	chcnt++;
      ctmp = ctmp->nextc;
    }
  if (chcnt > 1)
    sprintf(chbuf, " %d (%d)", option(current->defchan->copt, C_JOINED) ?
	    current->defchan->member_cnt : -1, chcnt);
  else if (chcnt == 1)
      sprintf(chbuf, " %d",  option(current->defchan->copt, C_JOINED) ?
	    current->defchan->member_cnt : -1);

  if (current->ldisp == current->llog)
    {
      unset_option(current->wopt, W_KEYWORD);
      unset_option(current->wopt, W_ACTIVITY);
    }
  while (wtmp)
    {
      if (option(wtmp->wopt, W_KEYWORD) || option(wtmp->wopt, W_ACTIVITY))
	{
	  char blah[4];
	  
	  if (*actbuf)
	    strcat(actbuf, ",");
	  sprintf(blah, "%d%s", wtmp->ref, /* this stinks. hmmpf! */
		  option(wtmp->wopt, W_ACTIVITY) ? "+" : "");
	  strcat(actbuf, blah);
	}
      wtmp = wtmp->nextw;
    }
  
  sprintf(buf, "sic[%d:%d%s] %s%s%s%s%s %s%s%s%s (%s) [%s] %s%s%s",
	  current->ref, current->fnb,
	  (option(current->custw.zopt_on, Z_UNDEL)) ? "u" : "",
	  (current->llog == current->ldisp) ? "" : "*more* ",
	  (*actbuf) ? "(" : "", actbuf, (*actbuf) ? ") " : "",
	  tbuf,
	  (current->via && current->via->nick) ? current->via->nick : "",
	  (*current->query) ? " [>" : "", current->query,
	  (*current->query) ? "<]" : "",
	  (current->via) ? current->via->umode : "",
	  (current->via) ? current->via->sname : "no server",
	  (current->defchan && option(current->defchan->copt,C_CHOP)) ? "@":"",
	  (current->defchan) ? current->defchan->chname : "", chbuf);
  
  if (refresh_status || strcmp(buf,st))
    {
      refresh_status = 0;
      active = current;
      server = current->via;
      term_status(buf, !get_option(Z_RSTATUS, (current->defchan) ?
				   current->defchan->chname : NULL));
      term_clreol();
      strcpy(st, buf);
    }
}

/*
 * WINDOW MANAGEMENT
 */

static int		win_cnt = 0, win_max = -1;
static struct window_ *	win_map[20];

/* sic_newwin: creates a new window */
void
sic_newwin()
{
  struct window_ *new, *w = wlist;
  int i;

  oldcurrent = new = (struct window_ *) malloc(sizeof(struct window_));
  bzero(new, sizeof(struct window_));

  for (i = 0; i <= win_max; i++)
      if (win_map[i] == NULL)
	  break;
  if (i == win_max && win_map[i] != NULL)
      i++;
  win_map[i] = new;
  if (i > win_max)
      win_max = i;
  win_cnt += 1;
  new->ref = i;
  new->fnb = 1;

  /* make first window created current */
  if (wlist == NULL)
    {
      wlist = new;
      /*set_option(new->wopt, W_DEFAULT);*/
      set_option(new->wopt, W_DCC);
      active = current = new;
    }
  else
    {
      while (w->nextw)
	w = w->nextw;
      w->nextw = new;
    }
  new->via = current->via; /* new can be current */
  vsic_slog(LOG_DEBUG, "assigned window %d to %s.", new->ref, (server) ?
	    server->sname : "<unknown>");
  /*set_option(new->custw.zopt_on, Z_SHOWALL);*/
}

/* sic_swin: re/assigns server/window relationship
 *	1 -> assign new/reassign
 *	2 -> free
 */
char
sic_swin(what)
  char what;
{
  struct window_ *wtmp = wlist, *act = active;
  struct server_ *olds = current->via;
  char cnt = 0, merge = 0;

  switch (what)
    {
  case 1:
      /* where is this info going? */
      if (option(server->sopt, S_CONNECTED)
	  || option(server->sopt, S_CONNECTING))
	{
	  if (current->defchan)
	    {
	      sic_slog(LOG_CLIENT,
		       "--- Part channels in current window first!");
	      break;
	    }
	  current->via = server;
	  unset_option(current->wopt, W_DEFAULT);
	  cnt++;
	  vsic_slog(LOG_DEBUG, "(re)assigned window %d from %s to %s.",
		    current->ref, (olds) ? olds->sname : "<none>",
		    server->sname);
	}
      else
	{
	  /* are we merging two groups of windows? */
	  while (wtmp)
	    {
	      if (wtmp->via == server)
		  merge += 1;
	      wtmp = wtmp->nextw;
	    }
	  wtmp = wlist;
	  if (merge)
	    {
	      /*
	      ** merging can get really tricky because of channels,
	      ** so only simple merge cases are allowed.
	      */
	      while (wtmp)
		{
		  if (wtmp->via == olds && wtmp->defchan)
		      cnt += 1;
		  wtmp = wtmp->nextw;
		}
	      if (cnt)
		{
		  vsic_slog(LOG_CLIENT, "--- %d window(s) from this group have channels bound to them!", cnt);
		  break;
		}
	      wtmp = wlist;
	    }
	  while (wtmp)
	    {
	      if (wtmp->via == olds)
		{
		  if (server)
		    {
		      active = wtmp;
		      vsic_slog(LOG_CLIENT,
				"--- Window now bound to %s %d [%s]",
				server->sname, server->port,
				(server->ip) ? server->ip : "???");
		    }
		  active = current;
		  vsic_slog(LOG_DEBUG, "(re)assigned window %d from %s to %s.",
			    wtmp->ref, (olds) ? olds->sname : "<none>",
			    server->sname);
		  wtmp->lasts = wtmp->via;
		  wtmp->via = server;
		  cnt++;
		}
	      wtmp = wtmp->nextw;
	    }
	  set_option(current->wopt, W_DEFAULT);
	  active = current;
	  wtmp = wlist;
	  while (wtmp)
	    {
	      if (current != wtmp && wtmp->via == server 
		  && option(wtmp->wopt, W_DEFAULT))
		  unset_option(current->wopt, W_DEFAULT);
	      wtmp = wtmp->nextw;
	    }
	  if (option(current->wopt, W_DEFAULT))
	    {
	      vsic_slog(LOG_DEBUG, "window %d is default for %s.",
			current->ref, server->sname);
	    }
	}
      server = olds; cmd_server(NULL); server = current->via;
      vsic_slog(LOG_CLIENT, "--- %sassigned %d window(s) to %s.", 
		(olds) ? "re" : "", cnt, server->sname);
      break;
  case 2:
      if (olds == NULL)
	  sic_slog(LOG_CLIENT, "--- Window is already free.");
      else
	{
	  while (wtmp)
	    {
	      if (wtmp->via == olds)
		{
		  cnt++;
		  if (option(current->wopt, W_DEFAULT) && current != wtmp)
		    {
		      unset_option(current->wopt, W_DEFAULT);
		      set_option(wtmp->wopt, W_DEFAULT);
		      vsic_slog(LOG_CLIENT,
				"--- %d is now the default window for %s.",
				wtmp->ref, wtmp->via->sname);
		    }
		}
	      wtmp = wtmp->nextw;
	    }
	  if ((cnt == 1
	       && (option(current->via->sopt, S_CONNECTING)
		   || option(current->via->sopt, S_CONNECTED)))
	      || !wlist->nextw)
	      sic_slog(LOG_CLIENT, "--- Cannot free last window.");
	  else
	    {
	      struct channel_ *ctmp = clist;

	      if (cnt == 1)
		  cmd_server(NULL);
	      cnt = 0;
	      while (ctmp)
		{
		  if (ctmp->on == current)
		      cnt++;
		  ctmp = ctmp->nextc;
		}
	      if (cnt)
		  vsic_slog(LOG_CLIENT,
			    "--- %d channel%s bound to this window!", cnt,
			    (cnt == 1) ? " is" : "s are");
	      else
		{
		  current->lasts = current->via;
		  current->via = NULL;
		}
	    }
	}
      break;
  default:
      abort(); /* never */
    }
  active = act;
  return cnt;
}

void
sic_wkill()
{
  sic_swin(2);
  if (current->via == NULL)
    {
      struct window_ **wtmp = &wlist, *wdel;

      sic_clog(1);
      while (*wtmp != current)
	  wtmp = &((*wtmp)->nextw);
      wdel = *wtmp;
      *wtmp = wdel->nextw;

      if (wdel->split)
	  free(wdel->split);
      win_map[wdel->ref] = NULL;
      if (wdel->ref == win_max)
	  while (win_map[--win_max] == NULL);

      if (wdel->defchan)
	  free(wdel->defchan);
      if (wdel->split)
	  free(wdel->split);

      if (option(wdel->wopt, W_DCC))
	  set_option(wdel->nextw->wopt, W_DCC);
      opt_free(&(wdel->custw));
      free(wdel);

      win_cnt -= 1;

      if (oldcurrent == wdel)
	{
	  oldcurrent = NULL;
	  current = wlist;
	}
      else
	  current = oldcurrent;
      sic_redowin(1);
    }
}

void
sic_wlist()
{
  struct window_ *wtmp;
  struct channel_ *ctmp;
  char buffer[1024];
  int i;

  vsic_slog(LOG_CLIENT, "--- %d window%s:", win_cnt, (win_cnt > 1) ? "s" : "");
  for (i = 0; i <= win_max; i++)
    {
      if ((wtmp = win_map[i]) == NULL)
	  continue;

      vsic_slog(LOG_CLIENT, "---   %2d: %s%s%20s %20s %s", i,
		(option(wtmp->wopt, W_DCC)) ? "=" : " ",
		(option(wtmp->wopt, W_DEFAULT)) ? "*" : " ",
		(wtmp->via) ? wtmp->via->sname : "<unbound>",
		(wtmp->defchan) ? wtmp->defchan->chname : "", wtmp->query);

      ctmp = clist;
      buffer[0] = '\0';
      while (ctmp)
	{
	  if (ctmp->on == wtmp)
	    {
	      if (option(ctmp->copt, C_JOINED))
		  strcat(buffer, ">");
	      if (option(ctmp->copt, C_CHOP))
		  strcat(buffer, "@");
	      if (option(ctmp->copt, C_REJOIN))
		  strcat(buffer, "*");
	      strcat(buffer, ctmp->chname);
	      strcat(buffer, " ");
	    }
	  ctmp = ctmp->nextc;
	}
      if (buffer[0])
	  vsic_slog(LOG_CLIENT, "---       %s", buffer);
    }
}

void
sic_wch(new)
  signed char new;
{
  struct window_ *swap;
  int i = 0;

  assert( new >= 0 && new < 10 );
  if (swap = win_map[(int)new])
      swap->ref = current->ref;
  win_map[current->ref] = swap;
  current->ref = new;
  win_map[(int)new] = current;
  for (i=0; i < 20; i++)
      if (win_map[i])
	  win_max = i;
}

void
sic_wdcc()
{
  struct window_ *wtmp = wlist;

  while (wtmp)
    {
      unset_option(wtmp->wopt, W_DCC);
      wtmp = wtmp->nextw;
    }
  set_option(active->wopt, W_DCC);
}

void
sic_wchannel(chan)
  char *chan;
{
  struct channel_ *ctmp, *mtmp = get_channel(chan);
  struct window_ *curw, *act = active;

  assert(mtmp);
  
  curw = mtmp->on;
  mtmp->on = active;
  if (active->defchan == NULL)
      active->defchan = mtmp;
  if (curw->defchan == mtmp)
    {
      ctmp = clist;
      while (ctmp)
	{
	  if (ctmp->on->via == server && ctmp->on == curw && ctmp != mtmp)
	      break;
	  ctmp = ctmp->nextc;
	}
      if (ctmp)
	  curw->defchan = ctmp;
    }
  active = curw;
  vsic_slog(LOG_CLIENT, "--- Channel %s moved to window %d.", chan,
	    act->ref);
  active = act;
}

/*
 * DISPLAYING
 */

/* display_line: display one physical line from a log entry */
static void
display_line(lnb, line)
  int lnb;
  struct log_ *line;
{
  assert(lnb >= 0 && lnb <= LNCNT(line));

  if (!get_flag(line->flags, line->dest, active->fnb))
      term_underline_on(); /* Z_SHOWALL is on */
  if (lnb == 0)
    {
      if (line->prefix && !option(current->wopt, W_NOPREFIX))
	{
	  if (line->flags & LOG_IGNORE)
	      term_underline_on();
	  if (current->searchstr
	      && strcasestr(line->formatted, current->searchstr))
	      term_bold_on();
	  term_puts(line->prefix, line->plen);
	  if (current->searchstr
	      && strcasestr(line->formatted, current->searchstr))
	      term_bold_off();
	  if (line->flags & LOG_IGNORE)
	      term_underline_off();
	}
      term_putes(line->formatted, PLNLEN(line), line->attributes);
    }
  else
      term_putes(line->formatted + (lnb-1)*(CO-1) + PLNLEN(line), CO-1,
		 (line->attributes) ? 
		 line->attributes + (lnb-1)*(CO-1) + PLNLEN(line) : NULL);
  if (!get_flag(line->flags, line->dest, active->fnb))
      term_underline_off();
}

/* sic_redowin: repaint the window from the lastlog */
void
sic_redowin(clear)
int clear;
{
  struct log_ *tmpl;

  if (clear)
      {
	term_clear();
	refresh_status = 1;
      }
  else
    {
      int i;

      for (i = 0; i < LI-2; i++)
	{
	  term_move_cursor(0, i);
	  term_clreol();
	}
    }

  active = current; /* for get_flag() */
  if (tmpl = current->ldisp)
    {
      int i = LI-3;
      
      while (i >= 0 && tmpl)
	{
	  int j = LNCNT(tmpl);
	  
	  if (get_flag(tmpl->flags, tmpl->dest, current->fnb)
	      || get_option(Z_SHOWALL, tmpl->dest))
	    {
	      if (tmpl == current->ldisp && current->ltrunc)
		  j -= current->ltrunc;
	      while (i >= 0 && j--)
		{
		  term_move_cursor(0, i--);
		  display_line(j, tmpl);
		}
	    }
	  tmpl = tmpl->prevl;
	}
    }
}

/* sic_wptoggle: toggle W_NOPREFIX flag for current window */
void
sic_wptoggle()
{
  if (option(current->wopt, W_NOPREFIX))
      unset_option(current->wopt, W_NOPREFIX);
  else
      set_option(current->wopt, W_NOPREFIX);
}

/* sic_chgwin: switch current window
 *	-3 -> last, -2 -> previous, -1 -> next
 */
void
sic_chgwin(n)
int n;
{
  struct window_ *w = wlist;

  if (wlist->nextw == NULL)
    {
      sic_slog(LOG_CLIENT, "--- No other window!");
      return;
    }
  if (n == -3)
    {
      if (oldcurrent)
	{
	  w = current;
	  current = oldcurrent;
	  oldcurrent = w;
	}
    }
  else
    {
      int i = current->ref, dir = 0;

      if (n == -1)
	  dir = 1;
      else if (n == -2)
	  dir = -1;
	  
      if (dir != 0)
	{
	  i += dir;
	  while (i >= 0 && i <= win_max)
	      if (win_map[i])
		  break;
	      else
		  i += dir;
	  if (i < 0 || i > win_max)
	    {
	      i = (dir == -1) ? win_max : 0;
	      while (win_map[i] == NULL)
		  i += dir;
	    }
	  w = win_map[i];
	}
      else
	  if ((w = win_map[n]) == NULL)
	      return;
      oldcurrent = current;
      current = w;
    }      
  sic_redowin(1);
}

/* sic_scroll: scroll the lastlog */
void
sic_scroll(n)
int n;
{
  struct log_ *tmpl = current->ldisp;

  active = current; /* for get_*() calls */
  /* lame version which repaints all the window */
  if (n > 0)
    {
      /* scrolling down */
      if (current->ltrunc == 0 && current->ldisp == current->llog)
	  return;
      if (current->ltrunc)
	  /* some lines are not displayed */
	  if (n >= current->ltrunc)
	    {
	      /* we are scrolling more than there are undisplayed lines */
	      n -= current->ltrunc;
	      current->ltrunc = 0;
	    }
	  else
	      /* there are more undisplayed lines than we are scrolling */
	      current->ltrunc -= n;
      /* keep on scrolling */
      while (n > 0 && tmpl)
	  if (tmpl = tmpl->nextl)
	      if (get_flag(tmpl->flags, tmpl->dest, current->fnb)
		  || get_option(Z_SHOWALL, tmpl->dest))
		  n -= LNCNT(tmpl);
      if (!tmpl)
	  tmpl = current->llog; /* bottom of the log */
      if (n < 0)
	  /* went too much down: need to truncate the last log entry */
	  current->ltrunc = -n;
      current->ldisp = tmpl;
    }
  else if (n < 0)
    {
      /* scrolling up */
      int seen = LNCNT(current->ldisp) - current->ltrunc;

      n *= -1;
      if (current->ltrunc)
	  /* only some lines are displayed */
	  if (n >= seen)
	    {
	      /* we are scrolling more than there are displayed lines */
	      n -= seen;
	      current->ltrunc = 0;
	      tmpl = tmpl->prevl;
	    }
	  else
	    {
	      /* there are more displayed lines than we are scrolling */
	      current->ltrunc += n;
	      n = 0;
	    }
      /* keep on scrolling */
      while (n > 0 && tmpl)
	{
	  while (tmpl && !(get_flag(tmpl->flags, tmpl->dest, current->fnb)
			   || get_option(Z_SHOWALL, tmpl->dest)))
	      tmpl = tmpl->prevl;;
	  if (tmpl)
	    {
	      n -= LNCNT(tmpl);
	      tmpl = tmpl->prevl;
	    }
	}
      if (!tmpl)
	{
	  tmpl = current->flog; /* top of the log */
	  n = 0; /* hack, this isn't exact */
	}
      if (n < 0)
	{
	  /* went too much up */
	  tmpl = tmpl->nextl;
	  /* need to truncate the last log entry */
	  current->ltrunc = LNCNT(tmpl) + n;
	}
      current->ldisp = tmpl;
    }
  else
      return;

  sic_redowin(0); /* unefficient -hmmpf */
}

/* sic_scroll2:
 *	-1: top			+1: bottom
 *	-2: previous keyword	+2: next keyword
 *	if txt is non NULL, then search for txt instead of keywords.
 */
void
sic_scroll2(opt, txt)
  signed char opt;
  char *txt;
{
  struct log_ *tmpl = current->ldisp;
  int n = 0;

  assert( !txt || (txt && (opt == -2 || opt == 0 || opt == 2)));

  active = current;
  if (current->searchstr)
    {
      free(current->searchstr);
      current->searchstr = NULL;
    }
  if (txt)
      current->searchstr = strdup(txt);
  switch (opt)
    {
  case 0:
      /* do nothing */
      break;
  case -1:
      current->ldisp = current->flog;
      current->ltrunc = 0;
      sic_scroll(2*(LI-2)/3);
      break;
  case +1:
      current->ldisp = current->llog;
      current->ltrunc = 0;
      sic_redowin(0);
      break;
  case -2:
      while (tmpl)
	{
	  while (tmpl && !(get_flag(tmpl->flags, tmpl->dest, current->fnb)
			   || get_option(Z_SHOWALL, tmpl->dest)))
	      tmpl = tmpl->prevl;;
	  if (tmpl)
	    {
	      n -= LNCNT(tmpl);
	      if (n < -(LI-2)/3-1)
		  if (txt == NULL && tmpl->flags & LOG_KEYWORD)
		      break;
		  else if (txt && strcasestr(tmpl->formatted, txt))
		      break;
	      tmpl = tmpl->prevl;
	    }
	}
      if (tmpl)
	  sic_scroll(n + (LI-2)/3);
      else
	  term_beep();
      break;
  case +2:
      while (tmpl)
	  if (tmpl = tmpl->nextl)
	      if (get_flag(tmpl->flags, tmpl->dest, current->fnb)
		  || get_option(Z_SHOWALL, tmpl->dest))
		{
		  n += LNCNT(tmpl);
		  if (txt == NULL && tmpl->flags & LOG_KEYWORD)
		      break;
		  else if (txt && strcasestr(tmpl->formatted, txt))
		      break;
		}
      if (tmpl)
	  sic_scroll(n + (LI-2)/3);
      else
	  term_beep();
      break;
  default:
      abort(); /* never */
    }
}

/*
 * CHANNELS
 */

/* map_c2w: map a channel to a window
 *	returns 0 if name isn't a channel
 *	returns 1 it is the window's channel
 *	returns 2 if is not ...
 *
 * This doesn't indicate if the user speaking is actually on the channel or
 * not... to be added.
 */
int
map_c2w(name)
char *name;
{
  struct channel_	*ctmp;

  if ((ctmp = get_channel(name)) && !strcasecmp(name, ctmp->chname))
      if (ctmp->on == current)
	  return 1;
      else
	  return 2;
  else
      return 0;
}

void
chg_channel(name)
char *name;
{
  struct channel_	*ctmp = clist;
  char *sname;

  assert(name && *name);
  while (name && ctmp)
    {
      if (option(ctmp->copt, C_JOINING) && *ctmp->chname == '!')
	{
	  if (ctmp->chname[1] == '#')
	      sname = ctmp->chname+1;
	  else
	      sname = ctmp->chname;
	  if (!strcasecmp(name+6, sname+1))
	    {
	      vsic_slog(LOG_DEBUG, "channel %s renamed to %s", ctmp->chname,
			name);
	      *sname = '!';
	      cfg_read(sname);
	      free(ctmp->chname);
	      ctmp->chname = strdup(name);
	      name = NULL;
	    }
	}
      ctmp = ctmp->nextc;
    }
  assert(name == NULL);
}

/* new_channel: creates a new struct for channel data */
void
new_channel(name)
char *name;
{
  struct channel_ *new;

  assert (map_c2w(name) == 0);
  new = (struct channel_ *) malloc(sizeof(struct channel_));
  bzero(new, sizeof(struct channel_));
  new->nextc = clist;
  clist = new;

  new->on = current;
  new->on->via = server;
  /*bzero(new->copt, sizeof(new->copt));*/
  new->chname = strdup(name);
  if (!current->defchan)
      current->defchan = new;
  cfg_read(name);
}

/* join_channel: try to join a channel */
void
join_channel(name, try)
  char *name;
  int try;
{
  struct channel_ *ctmp;

  ctmp = get_channel(name);
  if (try == 0)
    {
      assert(ctmp);
      if (option(ctmp->copt, C_JOINED))
	{
	  vsic_slog(LOG_CLIENT, "--- Already on channel %s.", name);
	  return;
	}
      if (!option(server->sopt, S_CONNECTED))
	{
	  sic_slog(LOG_CLIENT, "--- Not connected.");
	  return;
	}
      vsic_write("JOIN %s", name);
      vsic_write("LAME:%s", name); /* trick to know when JOIN was completed */
      set_option(ctmp->copt, C_JOINING);
    }
  else if (ctmp)
      unset_option(ctmp->copt, C_JOINING);
}

/* del_channel: delete channel structures if appropriate */
void
del_channel(name)
  char *name;
{
  struct channel_ *del;

  if (name)
    {
      del = get_channel(name);
      assert(del);
      unset_option(del->copt, C_JOINED);
      unset_option(del->copt, C_CHOP);
      if (!option(del->copt, C_PART))
	  return;
      need_collect++;
      set_option(del->copt, C_DEL);
    }
  else
    {
      del = clist;
      while (del)
	{
	  if (del->on->via == server)
	      del_channel(del->chname);
	  del = del->nextc;
	}
    }
}

/* part_channel: mark channel for deletion */
void
part_channel(name, comment, forget)
  char *name, *comment;
  int forget;
{
  struct channel_ *ctmp;

  assert(comment);

  ctmp = get_channel(name);
  assert(ctmp);

  if (option(ctmp->copt, C_JOINING))
    {
      sic_slog(LOG_CLIENT, "--- Join in progress, try later.");
      return;
    }

  if (forget)
      set_option(ctmp->copt, C_PART);

  if (option(server->sopt, S_CONNECTED))
      vsic_write("PART %s :%s", name, (comment) ? comment : "");
  else if (forget)
    {
      del_channel(name);
      collect_channel();
    }
  return;
}

/* rejoin_channel: set auto rejoin for channel */
void
rejoin_channel(name)
{
  struct channel_ *ctmp = clist;

  if (name)
    {
      ctmp = get_channel(name);
      assert(ctmp);
      if (!option(ctmp->copt, C_REJOIN))
	{
	  need_collect++;
	  unset_option(ctmp->copt, C_REJOINED);
	}
      set_option(ctmp->copt, C_REJOIN);
    }
  else
      while (ctmp)
	{
	  if (ctmp->on->via == server)
	      rejoin_channel(ctmp->chname);
	  ctmp = ctmp->nextc;
	}
}

void
add_member(channel, nick, rpl)
  char *channel, *nick, rpl;
{
  struct channel_ *ctmp;
  struct member_ *mtmp;

  assert(channel && *channel);

  ctmp = get_channel(channel);
  assert(ctmp);

  if (nick)
    {
      if (!rpl || !option(ctmp->copt, C_NAMES))
	{
	  mtmp = (struct member_ *) malloc(sizeof(struct member_));
	  mtmp->next = ctmp->members;
	  ctmp->members = mtmp;
	  mtmp->nick = strdup(nick);
	  if (++ctmp->member_cnt == 1)
	    {
	      unset_option(ctmp->copt, C_JOINING);
	      set_option(ctmp->copt, C_JOINED);
	      unset_option(ctmp->copt, C_REJOIN);
	      unset_option(ctmp->copt, C_REJOINED);
	    }
	}
    }
  else
      set_option(ctmp->copt, C_NAMES);
}

/* del_member: remove member(s) from channel(s)
 *	channel, nick:	remove nickname from channel
 *	NULL, nick: remove nickname from all channels
 *	channel, NULL: remove everybody from channel, unset C_JOINING
 *	NULL, NULL: remove everybody from all channels, unset C_JOINING
 */
int
del_member(channel, nick)
  char *channel, *nick;
{
  struct channel_ *ctmp = clist;

  if (channel == NULL)
      if (nick == NULL)
	{
	  while (ctmp)
	    {
	      if (ctmp->on->via == server)
		  del_member(ctmp->chname, NULL);
	      ctmp = ctmp->nextc;
	    }
	  return 0;
	}
      else
	{
	  struct window_ *wtmp = wlist;
	  
	  assert(nick && *nick);
	  while (wtmp)
	    {
	      unset_option(wtmp->wopt, W_MCAST);
	      wtmp = wtmp->nextw;
	    }
	  while (ctmp)
	    {
	      if (ctmp->on->via == server && del_member(ctmp->chname, nick))
		  set_option(ctmp->on->wopt, W_MCAST);
	      ctmp = ctmp->nextc;
	    }
	  return 0;
	}
  else
    {
      ctmp = get_channel(channel);
      assert(ctmp);
      if (nick)
	{
	  struct member_ **mtmp = &(ctmp->members), *del;
	  
	  while (*mtmp && strcasecmp((*mtmp)->nick, nick))
	      mtmp = &((*mtmp)->next);
	  if (*mtmp == NULL)
	      return 0;
	  del = *mtmp;
	  *mtmp = (*mtmp)->next;
	  free(del->nick);
	  free(del);
	  ctmp->member_cnt--;
	  return 1;
	}
      else
	{
	  struct member_ *mtmp = ctmp->members, *del;
	  
	  while (del = mtmp)
	    {
	      mtmp = mtmp->next;
	      free(del->nick);
	      free(del);
	    }
	  ctmp->members = NULL;
	  ctmp->member_cnt = 0;
	  unset_option(ctmp->copt, C_CHOP);
	  unset_option(ctmp->copt, C_NAMES);
	  unset_option(ctmp->copt, C_JOINING);
	  unset_option(ctmp->copt, C_JOINED);
	  return 0;
	}
    }
}

void
chg_member(old, new)
  char *old, *new;
{
  struct channel_ *ctmp = clist;

  assert(old && *old && new && *new);
  while (ctmp)
    {
      struct member_ *mtmp = ctmp->members;

      unset_option(ctmp->on->wopt, W_MCAST);
      if (ctmp->on->via == server && option(ctmp->copt, C_JOINED))
	{
	  while (mtmp && strcasecmp(mtmp->nick, old))
	      mtmp = mtmp->next;
	  if (mtmp)
	    {
	      free(mtmp->nick);
	      mtmp->nick = strdup(new);
	      set_option(ctmp->on->wopt, W_MCAST);
	    }
	}
      ctmp = ctmp->nextc;
    }
}

int
is_member(channel, nick)
  char *channel, *nick;
{
  struct channel_ *ctmp;
  struct member_ *mtmp;

  assert(channel && *channel && nick && *nick);

  if (!(ctmp = get_channel(channel)))
      return 0;
  mtmp = ctmp->members;
  while (mtmp && strcasecmp(mtmp->nick, nick))
      mtmp = mtmp->next;
  if (mtmp)
      return 1;
  else
      return 0;
}

/* collect_channel: auto rejoins / deletions */
void
collect_channel()
{
  struct channel_ *ctmp = clist;

  while (ctmp)
    {
      if (ctmp->on->via != server)
	{
	  ctmp = ctmp->nextc;
	  continue;
	}
      if (option(ctmp->copt, C_REJOIN) && option(server->sopt, S_CONNECTED))
	  if (!option(ctmp->copt, C_REJOINED))
	    {
	      vsic_slog(LOG_CLIENT, "--- Rejoining %s.", ctmp->chname);
	      join_channel(ctmp->chname, 0);
	      set_option(ctmp->copt, C_REJOINED);
	      ctmp->rejoin = time(NULL);
	      ctmp = ctmp->nextc;
	      continue;
	    }
	  else if (time(NULL) - ctmp->rejoin > 600)
	    {
	      unset_option(ctmp->copt, C_REJOINED);
	      continue;
	    }
      if (option(ctmp->copt, C_DEL))
	{
	  struct channel_ *del = ctmp, **dtmp = &clist;
	  struct window_ *w;

	  ctmp = ctmp->nextc;
	  vsic_slog(LOG_CLIENT, "Forgetting about channel %s.", del->chname);

	  del_member(del->chname, NULL);
	  assert(del);
	  while (*dtmp != del)
	      dtmp = &((*dtmp)->nextc);
	  assert(*dtmp);
	  *dtmp = del->nextc;
	  
	  free(del->chname);
	  if ((w = del->on)->defchan == del)
	    {
	      struct channel_ *ctmp2 = clist;

	      while (ctmp2)
		{
		  if (ctmp2->on == w)
		      break;
		  ctmp2 = ctmp2->nextc;
		}
	      w->defchan = ctmp2;
	    }
	  opt_free(&(del->custc));
	  free(del);

	  need_collect--;
	  continue;
	}
      ctmp = ctmp->nextc;
    }
}

/* default_channel: changes default channel or query */
void
default_channel(name)
  char *name;
{
  struct channel_ *ctmp;

  if (ctmp = get_channel(name))
    {
      assert(ctmp->on == current);
      current->defchan = ctmp;
    }
  else
    {
      struct window_ *wtmp = wlist;
      
      if (*name)
	  while (wtmp)
	    {
	      if (wtmp->via == server && !strcasecmp(wtmp->query, name))
		  wtmp->query[0] = '\0';
	      wtmp = wtmp->nextw;
	    }
      strcpy(current->query, name);
    }
}

/* get_query: returns the nick being queried if any */
char *
get_query()
{
  if (*current->query)
      return current->query;
  else
      return NULL;
}

/* select_query: selects a new active window in case of /query */
void
select_query(dest)
  char *dest;
{
  struct window_	*w = wlist;

  while (w)
    {
      if (((server == w->via) || (option(server->sopt, S_DCC)))
	  && !strcasecmp(dest, w->query))
	{
	  active = w;
	  break;
	}
      w = w->nextw;
    }
}

/* select_active: select active window (where things will be displayed)
 *	if dest isn't null, the channel is looked up to decide.
 *	if dest is NULL,
 *		action == 2 -> current
 *		action == 1 -> current if same server, default otherwise
 *		otherwise, default window is chosen (on a server base)
 */
void
select_active(dest, action)
  char *dest;
  int action;
{
  if (dest)
    {
      struct channel_	*ctmp = clist;

      ctmp = get_channel(dest);
      assert(ctmp);
      active = ctmp->on;
    }
  else
    {
      struct window_	*w = wlist;
      
      switch (action)
	{
      case 2:
	  active = current;
	  break;
      case 1:
	  active = current;
	  if (current->via == server)
	      break;
	  /* fall through */
      default:
	  while (w && (!option(w->wopt, W_DEFAULT) || server != w->via)
		 && (!option(w->wopt, W_DCC) || !option(server->sopt, S_DCC)))
	      w = w->nextw;
	  assert(w);
	  active = w;
	  break;
	}
    }
}

/* function written before the generic ones, should be removed later */
void
set_chop(channel, op)
  char *channel;
  int op;
{
  struct channel_ *ctmp;

  ctmp = get_channel(channel);
  if (ctmp)
      if (op)
	{
	  if (op == 1 || (op == 2 && ctmp->members == NULL))
	      set_option(ctmp->copt, C_CHOP);
	}
      else
	  unset_option(ctmp->copt, C_CHOP);
}
