/* Copyright (c) 2000  Kevin Sullivan <nite@gis.net>
 *
 * Please refer to the COPYRIGHT file for more information.
 */

/* this file contains procedures related to the main window: drawing,
   redrawing, scrolling, user input, etc. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#define _GNU_SOURCE /* needed for stdio.h:vasprintf */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <termios.h>
#include <ncurses.h>
#include <string.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <time.h>

#include "defines.h"
#include "codes.h"
#include "nap.h"
#include "colors.h"
#include "winio.h"
#include "alias.h"
#include "irc.h"
#include "lists.h"
#include "scheck.h"
#include "sscr.h"
#include "dlul_screen.h"
#include "missing.h"

#ifdef MEMWATCH
  #include "memwatch.h"
#endif

extern info_t info;
extern int ircmode, wmode;
extern chans_t *curchan;    /* current channel, if any */
extern upload_t *up;    /* download task list */
extern download_t *down;    /* download task list */
extern scount_t scount;     /* libraries, songs, gigs */
extern char *mnick;
extern int noprint;
extern int ircsock;
extern alias_t *alhead;     /* alias list */
extern out_nap_cmd_t out[]; /* list of builtin commands */
extern WINDOW *swin;

/* the new model for managing input screens is as follows: */

/* the number of the currently active screen. Currently three values
   are possible: MAIN_SCREEN, RESULT_SCREEN, and DLUL_SCREEN. However,
   further screens may be added in the future (such as: resume screen,
   configuration screen) */
int screen;

/* Each screen has its own curses windows, and its own global state
   (such as: scroll buffer, command history, and input state for main
   screen, search item under cursor, displayed information categories,
   etc, for search screen). When switching screens, it is equally
   important to switch the input focus. In the new model, there is
   only one "input" connection which remains active at all times, but
   its "func" component is set to a different handler for different
   screens. Its "d" component is not used. It is imperative to update
   the "screen" variable and this handler at the same time, or else
   the user input will be directed to an invisible screen. */

/* individual screens may choose to delete their curses windows when
   they are not switched on (the search result screen does this), or
   to retain them (the main screen does it this way). */

/* each screen should provide a procedure for switching it on
   (initializing its state, windows, and input handler) and for
   switching it off (deleting any part of the state that is not
   needed). The function switchscreen(n) switches to screen number n,
   setting "screen" as appropriate, and calling the individual
   screen's off and on routines. */

/* curses windows and global state for main screen */
WINDOW *wchan=NULL, *winput, *sep, *whead;
int tind = 0;                 /* indentation of the title (channel topic) */
chans_t *recent = NULL;       /* most recent channel; (always NULL) */

scroll_t *mscroll = NULL;      /* top of the main scroll */
scroll_t *mscrollend = NULL;   /* bottom of the main scroll */
scroll_t *scur = NULL;         /* last line now displayed, or NULL for end */
int mscrollsize = 0;           /* length of the main scroll */

int lastlogflag = 0;           /* set to 1 during output of "lastlog"
				  command - this causes these lines to
				  be marked and ignored in future
				  "lastlog" commands. */

/* the command history */
cmds_t *cmdl, *cmdlend = NULL; /* A doubly linked list of lines,
				  possibly empty. This is read-only,
				  except that new lines can be
				  appended at the end, and the total
				  length of the list is bounded by
				  BACKLOG. */
int cmdlsize = 0;              /* length of the command history */

cmds_t *ccmd = NULL;          /* a pointer to the line in cmdl which
				 the current edit is based on, or NULL if
				 the current edit is from scratch */
unsigned char cbuf[512];      /* the line currently being edited */
unsigned char cscratch[512];  /* a place where the current "from scratch"
				 line is saved if ccmd != NULL */

int curr = 0;                 /* first char of cbuf displayed on screen */
int curx = 0;                 /* cursor position in cbuf */

/* global state for search screen is defined in sscr.c */

colortab_t colortab[] = {
  { RED, CPR, 1 },
  { GREEN, CPG, 1 },
  { WHITE, CPW, 1 },
  { BLUE, CPB, 1 },
  { YELLOW, CPY, 1 },
  { MAGENTA, CPM, 1 },
  { CYAN, CPC, 1, },
  { BOLD, A_BOLD, 0 },
  { DARK, A_DIM, 0 },
  { BLINK, A_BLINK, 0 },
  { UNDERLINE, A_UNDERLINE, 0 },
  { NULL, -1, 0 }
};

/* switch to screen number n (which should be MAIN_SCREEN,
   RESULT_SCREEN, or DLUL_SCREEN). Returns 0 if we already were on
   that screen (without redrawing it), 1 if there was a real switch,
   and -1 on error. */
int switchtoscreen(int n) {

  if (n==screen) {
    return 0;
  }

  /* check that screen selected is valid */
  switch (n) {
  case MAIN_SCREEN:
  case RESULT_SCREEN:
  case DLUL_SCREEN:
    break;
  default:
    return(-1);
  }

  /* turn off old screen */
  switch (screen) {
  case MAIN_SCREEN:
    /* nothing to do */
    break;
  case RESULT_SCREEN:
    endsearchscr();
    break;
  case DLUL_SCREEN:
    enddlulscr();
    break;
  default:
    return(-1);
    break;
  }

  screen = n;

  /* turn on new screen */
  switch (n) {
  case MAIN_SCREEN:
    mainscr();
    break;
  case RESULT_SCREEN:
    searchscr();
    break;
  case DLUL_SCREEN:
    dlulscr();
    break;
  default: /* not reachable */
    return(-1);
    break;
  }
  return(1);
}

/* switch to the next available screen */
int nextscreen(void) {
  switch(screen) {
  case MAIN_SCREEN:
    switchtoscreen(RESULT_SCREEN);
    break;
  case RESULT_SCREEN:
    switchtoscreen(DLUL_SCREEN);
    break;
  case DLUL_SCREEN:
  default:
    switchtoscreen(MAIN_SCREEN);
    break;
  }
  return(1);
}

/* switch to main screen */
void mainscr(void) {
  sock_t *sk;

  sk = findsock("input");
  if (sk) {
    sk->func = input;
  }
  dscr(wchan);
  drw(wchan);
  dstatus();
  indraw();
}

void resize()
{
  sock_t *t;
  struct winsize ws;

  memset(&ws, 0, sizeof(ws));
  ioctl(fileno(stdin), TIOCGWINSZ, &ws);
  
  resizeterm(ws.ws_row, ws.ws_col);
  winput->_begy = LINES-1;
  winput->_begx = 0;
  sep->_begy = LINES-2;
  sep->_begx = 0;
  if (info.notop)
    wresize(wchan, LINES-2, COLS);
  else
    wresize(wchan, LINES-3, COLS);
  wresize(winput, 1, COLS);
  if (!info.notop)
    wresize(whead, 1, COLS);
  wresize(sep, 1, COLS);
  drw(wchan);
  drw(sep);
  
  dstatus();
  
  /* redraw input screen */
  werase(winput);
  t = findsock("input");
  if (t) {
    indraw();
  }
  dscr(wchan);
  drw(wchan);
  drw(winput);

  if (screen==RESULT_SCREEN) {
    plist();
  }
  else if (screen == DLUL_SCREEN) {
    dlul_refresh();
  }
    
  return;
}

void wstats(WINDOW *win)
{
  wp(win, "stdscr to %i - %i\n", stdscr->_maxy, stdscr->_maxx);
  wp(win, "winput to %i - %i\n", winput->_maxy, winput->_maxx);
  wp(win, "sep to %i - %i\n", sep->_maxy, sep->_maxx);
  wp(win, "wchan to %i - %i\n", wchan->_maxy, wchan->_maxx);
  wp(win, "LINES/COLS to %i - %i\n", LINES, COLS);
  drw(win);
}

void drw(WINDOW *win)
{
  if (info.daemon || !win)
    return;
  
  if (screen==RESULT_SCREEN && win == swin)
    wrefresh(win);
  else if (screen==DLUL_SCREEN && win == dlul_win)
    wrefresh(win);
  else if (screen==MAIN_SCREEN)
    wrefresh(win);
}

void dstatus()
{
  int j, lg;
  char *t = NULL;
  user_t *cur;
  int nu, nd, ndq;
  download_t *dtask;
  upload_t *utask;

  list_count(down, nd, dtask, ACTIVE(dtask->state) && dtask->state!=RRQUEUED);
  list_count(down, ndq, dtask, dtask->state == QUEUED || dtask->state == RQUEUED || dtask->state == RRQUEUED);
  list_count(up, nu, utask, !STOPPED(utask->state));

  if (info.daemon) {
    return;
  }

  werase(sep);
  if (!info.notop)
    werase(whead);
  for (j=0;j<COLS;j++)
  {
    waddch(sep, ' ');
    if (!info.notop)
      waddch(whead, ' ');
  }
  
  if ((!ircmode && !curchan) || (curchan && (!curchan->q || curchan->q == 1)))
  {
    msprintf(&t, "[U/%i D/%i Q/%i]", nu, nd, ndq);
    mvwprintw(sep, 0, COLS-(strlen(t)+1), t);
    free(t);
    mvwprintw(sep, 0, 0, cloaked ? " (%s)" : " [%s]", info.user);
    wprintw(sep, scount.songs == 1 ? " [%lu song" : " [%lu songs", scount.songs);
    wprintw(sep, scount.libraries == 1 ? " in %lu library" : " in %lu libraries", scount.libraries);
    wprintw(sep, scount.gigs == 1 ? " (%lu gig)]" : " (%lu gigs)]", scount.gigs);
  }
  else
  {
    if (!curchan || !curchan->users)
      mvwprintw(sep, 0, 0, " [%s]", mnick);
    else if (curchan->flag)
    {
      t = strdup(curchan->nm);
      if (strlen(curchan->nm) > COLS/4)
        t[COLS/4] = 0;
      cur = finduser(curchan, mnick);
      if (cur->flag & NAP_OP)
        mvwprintw(sep, 0, 0, " [@%s] [%s +", mnick, t);
      else if (cur->flag & NAP_VOICE)
        mvwprintw(sep, 0, 0, " [+%s] [%s +", mnick, t);
      else
        mvwprintw(sep, 0, 0, " [%s] [%s +", mnick, t);
      if (curchan->flag & NAP_I)
        waddch(sep, 'i');
      if (curchan->flag & NAP_S)
        waddch(sep, 's');
      if (curchan->flag & NAP_P)
        waddch(sep, 'p');
      if (curchan->flag & NAP_T)
        waddch(sep, 't');
      if (curchan->flag & NAP_M)
        waddch(sep, 'm');
      if (curchan->flag & NAP_N)
        waddch(sep, 'n');
      if (curchan->flag & NAP_K)
        waddch(sep, 'k');
      if (curchan->flag & NAP_L)
        waddch(sep, 'l');
      if (curchan->flag & NAP_K || curchan->flag & NAP_L)
        waddch(sep, ' ');
      if (curchan->flag & NAP_K)
        wprintw(sep, "%s", curchan->key);
      if (curchan->flag & NAP_K && curchan->flag & NAP_L)
        waddch(sep, ' ');
      if (curchan->flag & NAP_L)
        wprintw(sep, "%i", (unsigned int)curchan->l);
      waddch(sep, ']');
      free(t);
      t = NULL;
    }
    else
    {
      t = strdup(curchan->nm);
      if (strlen(curchan->nm) > COLS/4)
        t[COLS/4] = 0;
      cur = finduser(curchan, mnick);
      if (cur->flag & NAP_OP)
        mvwprintw(sep, 0, 0, " [@%s] [%s]", mnick, t);
      else if (cur->flag & NAP_VOICE)
        mvwprintw(sep, 0, 0, " [+%s] [%s]", mnick, t);
      else
        mvwprintw(sep, 0, 0, " [%s] [%s]", mnick, t);
      free(t);
      t = NULL;
    }
  }
  
  if (!info.notop) {
    lg = COLS-(strlen(VERSION)+8)-4;
    if (lg >= 0) {
      mvwprintw(whead, 0, COLS-(strlen(VERSION)+8), "[nap v%s]", VERSION);
      if (curchan && curchan->q != 1 && curchan->topic) {
	t = (char *)malloc(lg+1);
	memset(t, 0, lg+1);
	if (strlen(curchan->topic) < lg + tind) {
	  /* drw(wchan); */ /* why here? */
	  tind = strlen(curchan->topic)-lg;
	}
	if (tind < 0)
	  tind = 0;
	if (tind + lg < strlen(curchan->topic)) {
	  strncpy(t, curchan->topic+tind, lg-3);
	  mvwprintw(whead, 0, 0, " [%s...]", t);
	} else {
	  strncpy(t, curchan->topic+tind, lg);
	  mvwprintw(whead, 0, 0, " [%s]", t);
	}
	free(t);
      } else if (curchan && curchan->q == 1)
	mvwprintw(whead, 0, 0, " [Query (%s)]", curchan->nm);
      if (screen == MAIN_SCREEN)
	wnoutrefresh(whead);
    }
  }
  if (screen == MAIN_SCREEN)
  {
    wnoutrefresh(sep);
    doupdate();
  }
}

/* add the string STR to the given LOGFILE. If RP is set, replace
   escape sequences */
void addlog(char *logfile, char *str, int rp)
{
  FILE *f;
  int i, k;
  static int bol = 1;      /* are we at the beginning of a line? */
  time_t ct;
  struct tm *r;

  /* note: some extra care is taken to print the date and time just
     before a line is printed, and not just after one is printed,
     although that would have been easier. */

  ct = time(NULL);
  r = localtime(&ct);

  f = fopen(logfile, "a");
  if (!f) {
    return;
  }

  if (!rp) {
    fputs(str, f);
    return;
  }

  for (i=0;str[i];i++) {
    if (bol) {
      fprintf(f, "[%02i/%02i %i:%02i] ", r->tm_mon+1, r->tm_mday, r->tm_hour, r->tm_min);
      bol = 0;
    }
    if (str[i] == '\e') {
      for (k=0; str[i] && str[i]!='m' && k<6; k++, i++) ;
      continue;
    }
    fputc(str[i], f);
    if (str[i] == '\n') {
      bol = 1;
    }
  }
  
  fclose(f);
}

int wp(WINDOW *win, const char *fmt, ...)
{
  va_list args;
  char *str;
  int i;
  
  if (noprint == 2)
    return(0);
  
  str = NULL;

  va_start(args, fmt);
  vasprintf(&str, fmt, args);
  va_end(args);
  
  if (info.logallfile)
    addlog(info.logallfile, str, 1);
    
  if (win && !info.daemon)
  {
    addscroll(win, str);
    if (nvar_default("noscroll", 0)==0) {
      scur = NULL;           /* be sure to scroll to bottom on output */
    }
    dscr(win);
  }
  if (!win && info.daemon!=2) {
    printf("%s", str);
    fflush(stdout); /* flushing here helps OpenBSD, which will
                       otherwise not flush automatically before
                       reading password. -PS */
  }
  
  i = strlen(str);

  free(str);

  return(i);
}

/* examines string s for escape sequences and returns a (statically
   allocated) string which represents the escape sequences (color,
   boldface) in effect at the end of string s. If end!=NULL, s is
   considered to end there. */
char *esc_getcolor(char *s, char *end) {
  static char out[11] = BOLD;
  char *col = out+strlen(BOLD);
  int bf=0;

  while (*s!=0 && (end==NULL || s<end)) {
    if (*s == '\e') {
      if (!strncmp(s, BOLD, strlen(BOLD))) {
	bf = 1;
	s += strlen(BOLD);
      } else if (!strncmp(s, WHITE, strlen(WHITE))) {
	bf = 0;
	col[0] = 0;
	s += strlen(WHITE);
      } else if (s[3] == 'm') {
	strncpy(col, s, 4);
	col[4] = 0;
	s += 4;
      } else {
	strncpy(col, s, 5);
	col[5] = 0;
	s += 5;
      }
    } else {
      s++;
    }
  }
  if (bf) {
    return out;
  } else {
    return col;
  }
}

/* calculate the "printable" length of a string, not including any
   escape sequences */
int esc_getlength(char *s) {
  int len = 0;

  while (*s) {
    if (*s == '\e' && s[3] == 'm') {
      s+=4;
    } else if (*s == '\e') {
      s+=5;
    } else {
      s++;
      len++;
    }
  }
  return len;
}

/* NOTE: the scroll log is the biggest memory hog in nap, as it can
   grow to thousands of lines. Therefore some special care was taken
   to allocate no more room for each line than necessary. In addition,
   we limit the size of the scroll to scrollsize. */

/* add a line to the "scroll" list of the main screen */
void addscroll(WINDOW *win, char *buf)
{
  scroll_t *cur, *new;
  char *p, *q;
  char *linebreak, *linebreak1=NULL;
  int scrollsize;

  /* len: length of the current line to append to, or -1 if the last
     character was '\n' and a line has not yet been created. Note
     len>=0 implies mscrollend != NULL */
  static int len = -1;   
  static int size, asize;  /* real size and allocated size of current
                              line, if len != -1. */

  /* process each character or espace sequence from buf */
  
  for (p=buf; *p!=0; p++) {

    if (*p == '\r') {    /* ignore - concession to MSDOS? */
      continue;
    }
    
    /* create a new logical line if there is no current line (previous
       character was '\n'). */
    
    if (len==-1) {
      /* create a new empty line */
      cur = (scroll_t *)malloc(sizeof(scroll_t));
      cur->lastlog = lastlogflag;
      cur->chan = recent;
      cur->own = NULL;
      asize = 256;
      cur->line = (char *)malloc(asize);
      cur->line[0] = 0;

      /* attach to scroll */
      dlist_append(mscroll, mscrollend, cur);
      mscrollsize++;
      
      /* initialize local state for appending to this line */
      len = 0;
      size = 0;
    }

    cur = mscrollend;

    /* make sure at least 5 more characters fit in current line */
    if (size+5+1 > asize) {
      asize += 256;
      cur->line = (char *)realloc(cur->line, asize);
    }
    
    if (*p == '\n') {
      /* mark this line ended - next printable character will start a
         new (logical and physical) line */
      q = esc_getcolor(cur->line, NULL);
      if (q[0] != 0) {
 	strcat(cur->line, WHITE);
	size += strlen(WHITE);
      }
      len = -1;
      /* free some memory by allocating a fixed size for this line */
      q = strdup(cur->line);
      free(cur->line);
      cur->line = q;

      continue;

    } else if (*p == '\e') {
      /* escape sequence - copy to current line without counting its
         length towards the total */
      if (p[3] == 'm') {
	strncat(cur->line, p, 4);
	size += 4;
	p += 3;
      } else {
	strncat(cur->line, p, 5);
	size += 5;
	p += 4;
      }
      continue;
      
    } else {   /* printable character - copy it to current line */
      if (*p!=' ' && len >= COLS-1) {
	/* the character will not fit. Find a good place to break line */
	linebreak = strrchr(cur->line, ' ');
	if (linebreak - cur->line < COLS/2 || linebreak - cur->line < 40) {
	  linebreak = NULL;
	}
	if (linebreak) {
	  linebreak1 = linebreak + 1;
	  while (linebreak > cur->line && linebreak[-1]==' ') {
	    linebreak--;
	  }
	}
	
	/* create continuation line */
	new = (scroll_t *)malloc(sizeof(scroll_t));
	new->lastlog = lastlogflag;
	new->chan = recent;
	new->own = cur->own ? cur->own : cur;
	asize = 256;
	new->line = (char *)malloc(asize);
	new->line[0] = 0;

	/* copy over stuff from old line to new one */
	q = esc_getcolor(cur->line, linebreak);
	strcat(new->line, q);
	strcat(new->line, LINEINDENT);
	if (linebreak) {
	  strcat(new->line, linebreak1);
	}
	len = esc_getlength(new->line);
	size = strlen(new->line);
	
	/* truncate old line if necessary */
	if (linebreak) {
	  *linebreak = 0;
	}

	/* re-allocate old line using smallest amount of space */
	q = strdup(cur->line);
	free(cur->line);
	cur->line = q;

	/* attach new line to scroll */
	dlist_append(mscroll, mscrollend, new);
	mscrollsize++;

	cur = new;
      }
      
      /* character fits; append it */
      strncat(cur->line, p, 1);
      len++;
    }
  }
  
  scrollsize = nvar_default("scrollsize", SCROLLSIZE);

  while (scrollsize > 0 && mscrollsize > scrollsize) { 
    /* limit the size of mscroll */
    do {
      dlist_unlink_first(mscroll, mscrollend, cur);
      mscrollsize--;
      free(cur->line);
      free(cur);
    } while (mscroll && mscroll->own);
  }
}

/* clear the main scroll */
void clearscroll(void) {
  scroll_t *elt;
  
  dlist_forall_unlink(elt, mscroll, mscrollend) {
    free(elt->line);
    free(elt);
  }
  mscrollsize = 0;
  scur = NULL;
  dscr(wchan);
}  

/* scroll to bottom */
void scrollbottom(void) {
  scur = NULL;
  dscr(wchan);
}

void dscr(WINDOW *win)
{
  scroll_t *cur;
  int i, j, k, m, at=0, t, c;
  unsigned char bf = 0, uf = 0, gf = 0;
  char ebuf[6];
  int col; /* column */
  
  werase(win);
  wmove(win, 0, 0);
  
  if (scur == NULL)
    cur = mscrollend;
  else
    cur = scur;
  
  if (cur == NULL)
    return;
  
  i = win->_maxy-1;
  
  if (cur->chan && (wmode && cur->chan != curchan))
    i++;
  
  while (i >= 0)
  {
    if (cur->prev == NULL)
      break;
    cur = cur->prev;
    if (cur->chan && (wmode && cur->chan != curchan))
      continue;
    i--;
  }
  
  c = win->_maxy;
  
  for (j=0,i=0; i<=c; cur=cur->next)
  {
    if (cur == NULL)
      break;
    if (cur->chan && (wmode && cur->chan != curchan))
      continue;
    if (!cur->own)
    {
      bf = 0;
      uf = 0;
      gf = 0;
    }
    wmove(win, j, 0);
    col = 0;
    for (k=0; cur->line[k] && col < COLS; k++)
    {
      if (cur->line[k] == '\e' && cur->line[k+1] == '[' && isdigit(cur->line[k+2]))
      {
        for (m=0; cur->line[k] && cur->line[k]!='m'; m++, k++)
          ebuf[m] = cur->line[k];
        ebuf[m] = 'm';
        ebuf[m+1] = '\0';
        t = doesc(win, ebuf);
        if (t == COLOR_PAIR(CPW))
          at = 0;
        else
          at |= t;
      }
      else if (cur->line[k] == 2)
      {
        if (!bf)
        {
          at |= doesc(win, BOLD);
          bf = 1;
        }
        else
        {
          at &= ~doesc(win, BOLD);
          bf = 0;
        }
      }
      else if (cur->line[k] == 31)
      {
        if (!uf)
        {
          at |= doesc(win, UNDERLINE);
          uf = 1;
        }
        else
        {
          at &= ~doesc(win, UNDERLINE);
          uf = 0;
        }
      }
      else if (cur->line[k] == 7)
      {
        if (!gf && i == c)
          beep();
        gf = 1;
      }
      else if (cur->line[k] == 15)
        uf = bf = at = 0;
      else {
        waddch(win, cur->line[k]|at);
	col++;
      }
    }
    i++;
    j++;
  }
  scur = cur ? cur->prev : NULL;
}

void dscroll(WINDOW *win, int n)
{
  int i;
  scroll_t *cur;
  
  if (scur == NULL)
    cur = mscrollend;
  else
    cur = scur;
  
  if (!cur)
    return;
  
  if (n > 0)
  {
    i = n;
    if (cur->chan && (wmode && cur->chan != curchan))
      i++;
  
    while (i > 0)
    {
      if (cur->prev == NULL)
        break;
      cur = cur->prev;
      if (cur->chan && (wmode && cur->chan != curchan))
        continue;
      i--;
    }
  }
  else if (n < 0)
  {
    i = n;
    if (cur->chan && (wmode && cur->chan != curchan))
      i--;
  
    while (i < 0)
    {
      if (cur->next == NULL)
      {
        cur = NULL;
        break;
      }
      cur = cur->next;
      if (cur->chan && (wmode && cur->chan != curchan))
        continue;
      i++;
    }
  }
  else
    return;
  
  scur = cur;
  
  dscr(win);
  drw(win);
}

int doesc(WINDOW *win, const char *in)
{
  int i;
  
  for (i=0;;i++)
  {
    if (!colortab[i].in)
      return(0);
    if (!strcmp(colortab[i].in, in))
      break;
  }
  
  if (colortab[i].c)
    return(COLOR_PAIR(colortab[i].pair));
  else
    return(colortab[i].pair);
}

int input(WINDOW *win, sock_t *m)
{
  chtype ebuf = 0;
  sock_t *t;
  int g;
  int j, s, n; 
  char *msg;
  char *command;
  cmds_t *elt;

  t = findsock("server");

  s = t ? t->fd : -1;
  
  ebuf = wgetch(winput);
  if (ebuf == '\e') {                   /* ESC */
    ebuf = wgetch(winput);
    if (ebuf == ERR) { 
      return(1);
    } else {
      ebuf+=128;
    }
  }

  switch (ebuf)
  {

  case '\n':                            /* Newline */
  case '\r':                            /* Return */
    
    /* enter the current line (cbuf) in the command history, unless it
       is a repetition of the previous command, or unless it is empty. */

    if (*cbuf && (!cmdlend || strcmp(cbuf, cmdlend->cmd))) {
      elt = (cmds_t *)malloc(sizeof(cmds_t));
      elt->cmd = strdup(cbuf);
      elt->prev = cmdlend;
      elt->next = NULL;
      if (cmdlend) {
	cmdlend->next=elt; 
      } else {
	cmdl=elt;
      }
      cmdlend=elt;
      cmdlsize++;
      
      /* make sure command history does not get too long */
      if (cmdlsize > BACKLOG) {
	elt = cmdl->next;
	free(cmdl->cmd);
	free(cmdl);
	cmdl = elt;
	cmdl->prev = NULL;
	cmdlsize--;
      }
    }

    /* remember command from cbuf for execution below */
    command = strdup(cbuf);

    /* get ready for new input (we do this before the command is
       executed, because the command might cause a switch to another
       screen) */
    
    ccmd = NULL;
    cbuf[0]=0;
    curx = 0;
    curr = 0;

    indraw();
    
    ebuf = 0;
    if (*command == 0 || !strcmp(command, "/")) { 
      /* do nothing, just print an empty line to give visual feedback */
      wp(win, "\n");
      drw(win);
    } else if (*command == '/') {
      lpbrk = 0;   /* clear previous loop interrupt request */
      n = parseout(s, command+1, win);
      indraw();  /* necessary because command could have been "/query" etc */
      dstatus();
    } else if (!curchan) {
      wp(win, "%s* You are not on a channel. Commands start with \"/\".%s\n", RED, WHITE);
      drw(win);
    } else if (curchan->q == 1) {
      msg = fixquotes(strdup(command));
      sendpack(s, NAP_TELL, "%s %s", curchan->nm, msg);
      recent = curchan;
      wp(win, "%s* --> (%s%s%s)%s %s\n", GREEN, WHITE, curchan->nm, GREEN, WHITE, msg);
      drw(win);
      free(msg);
      recent = NULL;
    } else if (curchan->q == 2) {
      ssock(ircsock, "PRIVMSG %s :%s\n", curchan->nm, command);
      recent = curchan;
      wp(win, "%s<%s%s%s>%s %s\n", BRIGHT(MAGENTA), WHITE, mnick, BRIGHT(MAGENTA), WHITE, command);
      drw(win);
      recent = NULL;
    } else {
      msg = fixquotes(strdup(command));
      if (sendpack(s, NAP_SAY, "%s %s", curchan->nm, msg) == -1) {
	delsock(s);  /* s is the server, or -1 if no server */
      }
      free(msg);
    } /* if-else-else */
    free(command);
    break;

  case 128+KEY_BACKSPACE:               /* META-backspace */
  case 128+127:                         /* META-delete */
  case 128+8:                           /* META-backspace */
    curx--;
    while (curx >= 0 && cbuf[curx] == ' ') {
      memmove(cbuf+curx, cbuf+curx+1, strlen(cbuf+curx+1));
      cbuf[strlen(cbuf)-1] = 0;
      curx--;
    }
    while (curx >= 0 && cbuf[curx] != ' ') {
      memmove(cbuf+curx, cbuf+curx+1, strlen(cbuf+curx+1));
      cbuf[strlen(cbuf)-1] = 0;
      curx--;
    }
    if (curx < 0) {
      cbuf[0] = 0;
      curx = 0;
    } else {
      curx++;
    }
    if (curx == 0)
      curr = 0;
    indraw();
    break;

  case KEY_BACKSPACE:                   /* backspace */
  case 127:                             /* delete */
  case 8:                               /* backspace */
    if (curx) {
      for (j=curx-1; cbuf[j]; j++)
        cbuf[j] = cbuf[j+1];
      curx--;
      indraw();
    }
    ebuf = 0;
    break;

  case 23:                              /* Ctrl-W */
    cbuf[curx] = 0;
    curx--;

    while (curx >= 0 && cbuf[curx] == ' ') 
    { 
      cbuf[curx] = 0; 
      curx--; 
    } 

    while (curx >= 0 && cbuf[curx] != ' ')
    {
      cbuf[curx] = 0;
      curx--;
    }
    curx++;

    indraw();
    break;
    
  case 24:                              /* Ctrl-X */
    nextchan();
    indraw();
    /* If in window mode, redraw the screen */
    if (wmode)
    {
      dscr(wchan);
      drw(wchan);
    }
    dstatus();
    ebuf = 0;
    break;

  case 21:                              /* Ctrl-U */
    cbuf[0] = 0;
    curx = 0;
    curr = 0;
    indraw();
    ebuf = 0;
    break;

  case 11:                              /* Ctrl-K */
    cbuf[curx] = 0;
    indraw();
    ebuf = 0;
    break;
    
  case 1:                               /* Ctrl-A */
  case KEY_HOME:                        /* Home */
  case KEY_FIND:                        /* Find - a kind of home key */
    curx = 0;
    indraw();
    ebuf = 0;
    break;
    
  case 5:                               /* Ctrl-E */
  case KEY_END:                         /* End */
  case KEY_SELECT:                      /* Select - a kind of end key */
    curx = strlen(cbuf);
    indraw();
    ebuf = 0;
    break;

  case 4:                               /* Ctrl-D */
  case 330:                             /* Delete */
    if (cbuf[curx]) {
      memmove(cbuf+curx, cbuf+curx+1, strlen(cbuf+curx+1)+1);
    }
    indraw();
    ebuf = 0;
    break;

  case 154:                             /* Ctrl-Meta-Z ??? */
    ebuf = 0;
    break;

  case '\t':                            /* Tab */
    ebuf = 0;
    
    for (g=curx; cbuf[g]!=' ' && g>=0; g--);
    
    if (cbuf[g] != ' ')
      g = 0;
          
    if (*cbuf == '/' && !g)
    {
      alias_t *al;
      
      for (al=alhead;al;al=al->next)
        if (!strncasecmp(cbuf+1, al->nm, strlen(cbuf+1)))
        {
          memset(cbuf, 0, sizeof(cbuf));
          cbuf[0] = '/';
          strcpy(cbuf+1, al->nm);
          strcat(cbuf, " ");
          curx = strlen(cbuf);
          indraw();
          break;
        }

      for (j=0;out[j].func;j++)
        if (!strncasecmp(cbuf+1, out[j].nm, strlen(cbuf+1)) && out[j].help)
        {
          memset(cbuf, 0, sizeof(cbuf));
          cbuf[0] = '/';
          strcpy(cbuf+1, out[j].nm);
          strcat(cbuf, " ");
          curx = strlen(cbuf);
          indraw();
          break;
        }
    }
    else if (curchan && curchan->users)
    {
      user_t *usr;
      int k, z;
      
      for (k=curx;cbuf[k]!=' '&&k>=0;k--);
      
      if (cbuf[k] == ' ')
        z = k+1;
      else
        z = 0;
      
      for (usr=curchan->users;usr;usr=usr->next)
        if (!strncasecmp(cbuf+z, usr->nm, strlen(cbuf+z)))
        {
          for (k=z;k<=curx;k++)
            cbuf[k] = 0;
          strcpy(cbuf+z, usr->nm);
          if (!z)
            strcat(cbuf, ": ");
          curx = strlen(cbuf);
          indraw();
          break;
        }
    }
    break;
  
  case 20:                              /* Ctrl-T */
    if (!curchan || !curchan->topic)
      tind = 0;
    else {
      if (tind >= (strlen(curchan->topic)-(COLS-(strlen(VERSION)+8)-4)))
	tind = 0;
      else
	tind += 5;
    }
    ebuf = 0;
    dstatus();
    break;

  case KEY_PPAGE:                       /* PgUp */
  case 16:                              /* Ctrl-P */ 
    dscroll(win, (LINES-2)/2);
    indraw();
    ebuf = 0;
    break;

  case KEY_NPAGE:                       /* PgDn */
  case 14:                              /* Ctrl-N */
  case 22:                              /* Ctrl-V */
    dscroll(win, -(LINES-2)/2);
    indraw();
    ebuf = 0;
    break;
    
  case 12:                              /* Ctrl-L */
    clearok(wchan, 1);
    drw(wchan);
    drw(sep);
    drw(winput);
    if (!info.notop)
    {
      drw(whead);
    }
    dstatus();
    dscr(wchan);
    drw(wchan);
    indraw();
    ebuf = 0;
    break;

  case KEY_F(1):                        /* F1 */
  case 128 + '1':                       /* M-1 */
    return(1);
    break;

  case KEY_F(2):                        /* F2 */
  case 128 + '2':                       /* M-2 */
    switchtoscreen(RESULT_SCREEN);
    return(1);
    break;

  case KEY_F(3):                        /* F3 */
  case 128 + '3':                       /* M-3 */
    switchtoscreen(DLUL_SCREEN);
    return(1);
    break;

  case KEY_UP:                          /* UP */
    if (ccmd == NULL) {
      if (cmdlend != NULL) {
	strcpy(cscratch, cbuf);
	ccmd = cmdlend;
	strcpy(cbuf, ccmd->cmd);
	curx = strlen(cbuf);
      }
    } else {
      if (ccmd->prev != NULL) {
	ccmd = ccmd->prev;
	strcpy(cbuf, ccmd->cmd);
	curx = strlen(cbuf);
      }
    }
    indraw();

    break;

  case KEY_DOWN:                        /* DOWN */
    if (ccmd) {
      ccmd = ccmd->next;
      if (ccmd == NULL) {
	strcpy(cbuf, cscratch);
	curx = strlen(cbuf);
      } else {
	strcpy(cbuf, ccmd->cmd);
	curx = strlen(cbuf);
      }
    }
    indraw();

    break;

  case KEY_RIGHT:                       /* RIGHT */
    if (cbuf[curx])
    {
      curx++;
    }
    indraw();
    break;

  case KEY_LEFT:                        /* LEFT */
    if (curx)
    {
      curx--;
    }
    indraw();
    break;

  default:                              /* printable characters? */
    if ((32 <= ebuf && ebuf <= 126) || (160 <= ebuf && ebuf <=255))
    {
      j = strlen(cbuf);
      if (j<sizeof(cbuf)-1) {
	memmove(cbuf+curx+1, cbuf+curx, j-curx+1);
	cbuf[curx] = ebuf;
	ebuf = 0;
	curx++;
	indraw();
      }
    }
    break;

  } /* switch */
  
  return(1);
}

void initwin(unsigned char f)
{
  struct termios ts;
  WINDOW *w;
  int bgcolor;

  if (info.daemon) {
    info.daemon = 2;
    return;
  }

  tcgetattr(0, &ts);

#ifdef __CYGWIN32__
  setenv("TERMINFO", "./", 1);
  
  if (!newterm("cygwin", stdout, stdin))
  {
    fprintf(stderr, "Error opening terminal\n");
    exit(-1);
  }
  def_prog_mode();
  w = stdscr;
#else
  if (f)
  {
    if (!newterm("nxterm", stdout, stdin))
    {
      fprintf(stderr, "Error opening terminal\n");
      exit(-1);
    }
    def_prog_mode();
    w = stdscr;
  }
  else
    w = initscr();
#endif

/*  COLS = w->_maxx+1;
  LINES = w->_maxy+1; */
/*  dolc(); */
  start_color();
  cbreak();
  noecho();
  
  bgcolor = COLOR_BLACK;

#ifdef HAVE_ASSUME_DEFAULT_COLORS
  if (info.transparent) {
    assume_default_colors(COLOR_WHITE, -1);
    bgcolor = -1;
  }
#endif

  init_pair(1, COLOR_WHITE, COLOR_BLUE);
  init_pair(CPR, COLOR_RED, bgcolor);
  init_pair(CPG, COLOR_GREEN, bgcolor);
  init_pair(CPW, COLOR_WHITE, bgcolor);
  init_pair(CPB, COLOR_BLUE, bgcolor);
  init_pair(CPY, COLOR_YELLOW, bgcolor);
  init_pair(CPM, COLOR_MAGENTA, bgcolor);
  init_pair(CPC, COLOR_CYAN, bgcolor);
  init_pair(CPWR, COLOR_WHITE, COLOR_RED);
  init_pair(CPWB, COLOR_WHITE, COLOR_BLUE);
  winput = newwin("input", 1, 0, LINES-1, 0);
  sep = newwin("sep", 1, 0, LINES-2, 0);
  if (!info.notop)
  {
    wchan = newwin("chan", LINES-3, 0, 1, 0);
    whead = newwin("head", 1, 0, 0, 0);
  }
  else
    wchan = newwin("chan", LINES-2, 0, 0, 0);

  nodelay(winput, TRUE);
  wattrset(sep, COLOR_PAIR(1));
  if (!info.notop)
    wattrset(whead, COLOR_PAIR(1));
  idlok(wchan, FALSE);
  scrollok(wchan, TRUE);
  wbkgdset(winput, COLOR_PAIR(CPW));
  wbkgdset(wchan, COLOR_PAIR(CPW));
/*  bkgd(COLOR_PAIR(1)); */
  keypad(winput, TRUE);
  keypad(stdscr, TRUE);
  indraw();

/*  tcgetattr(0, &ts);
  printf("%i\n", ts.c_iflag&ISTRIP);
  exit(1); */
  
  dstatus();
}

/* draw the input area of the main window. This depends on the
 * following: 
 *
 * cbuf:  the text currently being edited by the user
 * curx:  the current cursor position within buf (0 <= strlen(buf) <= curx)
 * curr: the first character of buf that is actually visible (this is
 *       a global variable and we update it here - the only other two 
 *       places where it's updated are two places in input().)
 * curchan, curchan->nm, and curchan->q: this determines whether or not
 *       the current channel/query should be printed at the beginning of
 *       the line.
 * COL:  the current width of the screen.
 *
 **/

void indraw()
{
  int i, b;
  int cols = COLS;         /* trick compiler into allowing t[COLS+1] */
  int ucols;               /* columns not taken up by channel */
  unsigned char t[cols+1]; /* the entire line to be output, plus 0 */

  t[cols]=0;

  /* print the channel portion of the line */
  if (curchan && cols>=3) {
    char *p = strdup(curchan->nm);
    if (strlen(curchan->nm) > (cols/4))
      p[cols/4] = 0;
    sprintf(t, curchan->q ? "(%s) " : "[%s] ", p);
    free(p);
  } else {
    *t=0;
  }
  
  ucols = cols-strlen(t);

  /* first adjust curr with respect to curx */
  while (curx >= curr+ucols)
    curr += ucols/4 ? ucols/4 : 1;
  while (curx < curr)
    curr -= ucols/4 ? ucols/4 : 1;
  /* now curx is in the window defined by curr, unless ucols==0. */

  /* now adjust curr with respect to cbuf, to make sure there is no
     empty space on the left, and preferably none on the right. Notice
     this cannot move the cursor out of the window. */
  if (curr+ucols>strlen(cbuf)+1 && curr)
    curr = strlen(cbuf)+1-ucols;
  if (curr < 0)
    curr = 0;

  /* assemble the line */
  strncat(t, cbuf+curr, ucols);
  memset(t+strlen(t), ' ', cols-strlen(t));
  
  /* and print it. */
  werase(winput);
  for (i=0;i<cols;i++)
  {
    if ((t[i] & 0x7f) < 32 || (t[i] & 0x7f) == 127) {
      /* non-printable characters - should not normally occur */
      waddch(winput, ((t[i] & 0x1f)+'A'-1)|doesc(winput, BOLD));
    } else {
      waddch(winput, t[i]);
    }
  }
  /* calculate cursor position relative to window */
  b = curx-curr+cols-ucols;

  /* if the above calculations were right, we shouldn't have to do this */
  if (b < 0)
    b = 0;
  if (b >= cols)
    b = cols-1;

  /* move the cursor */
  wmove(winput, 0, b);
  drw(winput);
}

/* not used */
unsigned char gchr(WINDOW *win)
{
  fd_set fs;
  struct timeval tv;
  unsigned char r;
  
  FD_ZERO(&fs);
  FD_SET(0, &fs);
  
  tv.tv_sec = 0;
  tv.tv_usec = 50000;
  
  if (!select(1, &fs, NULL, NULL, &tv))
    return(0);
  
  r = wgetch(win);
  if (r == '\e')
  {
    ungetch('\e');
    return(0);
  }
  
  return(r);
}

