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

#include <stdio.h>
#include <string.h>
#include <ncurses.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>

#include "defines.h"
#include "colors.h"
#include "codes.h"
#include "scheck.h"
#include "nap.h"
#include "winio.h"
#include "sscr.h"
#include "lists.h"
#include "alias.h"
#include "status_line.h"

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

extern char *conns[];
extern ssearch_t *search; /* search result list */
extern int srch;          /* 1 while search in progress */
extern info_t info;

/* Command-line-related variables from winio.c */
extern cmds_t *ccmd;
extern char    cbuf[];
extern int     curr;
extern int     curx;

/* curses windows and global state for search screen */
WINDOW *swin;
int cy;             /* item number aligned with first line on screen */
int num;            /* item that the cursor is on */
int lk;             /* 0 if status line should be set to filename */
int max;            /* number of search results */
int showconn;       /* show connection speed? */
int showping;       /* show ping? (Note this will only be shown, by
                       default, if pings are actually available) */
int showuser;       /* show user nickname? */
int showbrate;      /* show bitrate? */
int showlength;     /* show length? */
int showfreq;       /* show frequency? */
int showsize;       /* show size? */
char lastsort;      /* indicates what the most recent sort key was */
int usercols;       /* width of longest user name */

/* initialize result screen state. Note: this only needs to be called
   when a new list of search results was generated, not each time we
   switch to the result screen. This way, the state will be preserved
   while switching back and forth between screens */
void resetsscr(void)
{
  ssearch_t *cur;
  char *defaults;

  cy = 1;  /* note search items are numbered starting from 1 */
  num = 1;
  lastsort = 0;
  
  /* determine number of items in search list, and maximum size of
     username */
  
  max = 0;
  usercols = 4;
  list_forall(cur, search) {
    max++;
    if (strlen(cur->nick) > usercols)
      usercols = strlen(cur->nick);
  }

  showconn = 0;
  showping = 0;
  showuser = 0;
  showbrate = 0;
  showlength = 0;
  showfreq = 0;
  showsize = 0;

  defaults = getval("sdefaults");
  if (!defaults)
    defaults = "blsp";

  while (*defaults) {
    shandlechar(*defaults);
    defaults++;
  }

  /* turn off display of connection speed and/or pings if the
     information is not available */

  showconn = (showconn && search && search->conn != -1) ? 1 : 0;
  showping = (showping && search && search->ping > -2) ? 1 : 0;

  /* reset screen and cursor to first item (after possible sorting!) */
  cy = 1; 
  num = 1;
}

/* turn on result screen */
void searchscr(void)
{
  sock_t *sk;

  sk = findsock("input");
  if (sk) {
    sk->func = sinput;
  }

  swin = newwin("sscr", LINES, 0, 0, 0);
  wbkgdset(swin, COLOR_PAIR(CPW));
  keypad(swin, TRUE);
  werase(swin);
  sl_set("Use F1 or 'q' to return to the main screen. 'h' for help.");
  lk = 1;

  plist();
}

/* turn off result screen */
void endsearchscr()
{
  delwin(swin);
}

/* print result screen */
void plist()
{
  int j, ln=0, min, sec, col, titlecols;
  ssearch_t *cur;
  float mb;
  char *t=NULL;
  
  if (screen != RESULT_SCREEN)
    return;
  
  if (srch)
  {
    werase(swin);
    mvwprintw(swin, LINES/2, (COLS-29)/2, "Waiting for search results...");
    sl_draw();
    wrefresh(swin);
    return;
  }
  
  if (!search)
  {
    werase(swin);
    mvwprintw(swin, LINES/2, (COLS-25)/2, "No search results to list");
    sl_draw();
    wrefresh(swin);
    return;
  }
  
  if (num > max)
    num = max;
  if (cy > max)
    cy = max-(LINES-3)+1;
  if (cy < 1)
    cy = 1;

  if (num >= (LINES-3)+cy)      /* screen follows cursor */
  {
    cy = num-(LINES-3)+1;
  }
  if (num < cy)
  {
    cy = num;
  }
  
  /* decide how many columns to use for filenames (=titlecols) */
  titlecols = COLS - 1;
  titlecols -= 8*showbrate;
  titlecols -= 9*showlength;
  titlecols -= (3+usercols)*showuser;
  titlecols -= 12*showconn;
  titlecols -= 7*showping;
  titlecols -= 8*showfreq;
  switch (showsize) {
  case 1:
    titlecols -= 9;
    break;
  case 2:
    titlecols -= 12;
    break;
  case 0: default:
    break;
  }
  if (titlecols < 14)
    titlecols = 14;
  
  werase(swin);
  
  msprintf(&t, "Filename (total = %d)", max);
  if (strlen(t) > titlecols)
    t[titlecols]=0;
  mvwprintw(swin, 0, 0, "%s", t);
  wmove(swin, 1, 0);
  whline(swin, ACS_HLINE, COLS);

  col = titlecols;
  if (showbrate) {
    mvwprintw(swin, 0, col+3, "BRate");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 8;
  }
  if (showlength) {
    mvwprintw(swin, 0, col+3, "Length");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 9;
  }
  switch (showsize) {
  case 1:
    mvwprintw(swin, 0, col+3, "MBytes");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 9;
    break;
  case 2:
    mvwprintw(swin, 0, col+3, "  Bytes");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 12;
    break;
  case 0: default:
    break;
  }
  if (showfreq) {
    mvwprintw(swin, 0, col+3, "Freq");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 8;
  }
  if (showuser) {
    mvwprintw(swin, 0, col+3, "User");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 3+usercols;
  }
  if (showconn) {
    mvwprintw(swin, 0, col+3, "Speed");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 12;
  }
  if (showping) {
    mvwprintw(swin, 0, col+3, "Ping");
    wmove(swin, 0, col+1);
    wvline(swin, ACS_VLINE, LINES-1);
    col += 7;
  }

  /* find cy'th element of the list (remember list macros count from
     0, but cy counts from 1 */
  list_nth(cur, search, cy-1);
  for (j=cy, ln=0; cur && ln < LINES-3; j++, ln++, cur=cur->next)
  {
    if (num == j)
    {
      if (!lk) {
	sl_set(quote(cur->rfn));
      }
      wattron(swin, COLOR_PAIR(1)|A_BOLD);
      wmove(swin, ln+2, 0);
      whline(swin, ' ', COLS);
    }
    
    msprintf(&t, "%i) %s", j, cur->song);
    if (strlen(t) > titlecols)
      t[titlecols]=0;
    mvwprintw(swin, ln+2, 0, "%s", t);
    col = titlecols;
    
    if (showbrate) {
      mvwprintw(swin, ln+2, col+3, "%4i", cur->brate);
      if (num==j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col+=8;
    }
    if (showlength) {
      min = cur->time/60;
      sec = cur->time%60;
      mvwprintw(swin, ln+2, col+3, "%3i:%02i", min, sec);
      if (num==j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col+=9;
    }
    switch (showsize) {
    case 1:
      mb = ((float)cur->sz)/1048576.0;
      mvwprintw(swin, ln+2, col+3, "%6.02f", mb);
      if (num == j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col += 9;
      break;
    case 2:
      mvwprintw(swin, ln+2, col+3, "%9d", cur->sz);
      if (num == j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col += 12;
      break;
    case 0: default:
      break;
    }
    if (showfreq) {
      mvwprintw(swin, ln+2, col+3, "%5i", cur->freq);
      if (num==j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col+=8;
    }
    if (showuser) {
      mvwprintw(swin, ln+2, col+3, "%s", cur->nick);
      if (num==j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col+=3+usercols;
    }
    if (showconn) {
      mvwprintw(swin, ln+2, col+3, cur->conn != -1 ? conns[cur->conn] : "N/A");
      if (num==j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col+=12;
    }
    if (showping) {
      mvwprintw(swin, ln+2, col+3, cur->ping>=0 ? "%4i" : " N/A", cur->ping);
      if (num==j) {
	wmove(swin, ln+2, col+1);
	wvline(swin, ACS_VLINE, 1);
      }
      col+=7;
    }
    
    if (num == j)
      wattroff(swin, COLOR_PAIR(1)|A_BOLD);
    
  } /* end for j */
  
  sl_draw();
  drw(swin);

  if (t)
    free(t);
}

/* handle input on result screen */
int sinput(WINDOW *win, sock_t *m)
{
  chtype cbuf = 0;
  WINDOW *w = swin;

  cbuf = wgetch(w);
  if (cbuf == '\e') {                   /* ESC */
    cbuf = wgetch(w);
    if (cbuf == ERR)
      return(1);
    else
      cbuf+=128;
  }

  if (!sl_handle_keystroke(cbuf))
    shandlechar(cbuf);

  plist();

  return(1);
}

/* handle key stroke on result screen */
int shandlechar(chtype ch)
{
  sock_t *sk;
  ssearch_t *cur, *a, *b;
  int r;
  const char *help = 
    "HELP - use <right> and <left> to scroll this information. "
    "<up> and <down> to move cursor. <return> to download item. "
    "q to return to main screen. "
    "F1-F3 or M-1 thru M-3 to switch between screens. "
    "<tab> to switch between results and dl/ul screens. "
    "<space> to enter a new search. "
    "'/' to goto main screen and start a command. "
    "l/b/m/f/u/s/p/a/n to toggle information displayed. "
    "N/D/L/B/M/F/U/S/P to sort. h for help.";
  
  switch (ch) {

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

  case KEY_F(3):                        /* F3 */
  case 128 + '3':                       /* M-3 */
  case '\t':                            /* Tab */
    switchtoscreen(DLUL_SCREEN);
    break;

  case '/':
      ccmd = NULL;
      strcpy(cbuf, "/");
      curr = 0;
      curx = strlen(cbuf);
      switchtoscreen(MAIN_SCREEN);
      break;

  case 'h': case 'H':
    sl_set(help);
    lk = 1;
    break;

  case KEY_UP:                          /* Up */
    num--;
    if (num <= 0)
      num = 1;
    lk = 0;
    break;

  case KEY_DOWN:                        /* Down */
    num++;
    lk = 0;
    break;
    
  case KEY_PPAGE:                       /* PgUp */
  case 5:                               /* Ctrl-E */
  case 16:                              /* Ctrl-P */
    if (nvar_default("cursorfollowsscreen", 0)) {
      if (cy<=1) {
	sl_set("Beginning of search results");
	lk = 1;
      } else {           /* one screen up, cursor follows screen */
	cy -= LINES-4;
	if (cy<1) {
	  cy = 1;
	}
	if (num >= (LINES-3)+cy) {
	  num = cy+(LINES-3)-1;
	}
	lk = 0;
      }
    } else {             /* screen follows cursor policy */
      if (num <= 1) {
	sl_set("Beginning of search results");
	lk = 1;
      } else {	
	num -= LINES-4;
	if (num <= 0)
	  num = 1;
	lk = 0;
      }
    }
    break;

  case KEY_NPAGE:                       /* PgDn */
  case 4:                               /* Ctrl-D */
  case 14:                              /* Ctrl-N */
  case 22:                              /* Crtl-V */
    if (nvar_default("cursorfollowsscreen", 0)) {
      if (cy>=max-(LINES-3)+1) {
	sl_set("End of search results");
	lk = 1;
      } else {           /* one screen down, cursor follows screen */
	cy += LINES-4;
	if (cy>max-(LINES-3)+1) {
	  cy = max-(LINES-3)+1;
	}
	if (num < cy) {
	  num = cy;
	}
	lk = 0;
      }
    } else {              /* screen follows cursor policy */
      if (num>=max) {
	sl_set("End of search results");
        lk = 1;
      } else {
	num += LINES-4;
	lk = 0;
      }
    }
    break;

  case 12:                              /* Ctrl-L */
    clearok(swin, 1);
    wrefresh(swin);
    break;

  case '\n':
    if (search)
    {
      lk = 1;
      list_nth(cur, search, num-1);
      if (cur)
      {
	sk = findsock("server");
	if (sk)
        {
	  r = requestdownload(sk->fd, cur);

	  switch(r) {
	  case 0:
	    sl_sprintf("Getting %s", cur->song);
	    wp(wchan, "* Getting \"%s\" from %s\n", cur->song, cur->nick);
	    break;
	  case 1: case 2:
	    sl_sprintf("Queued %s", cur->song);
	    wp(wchan, "* Queued download of \"%s\" from %s\n", cur->song, cur->nick);	    
	    break;
	  case 3:
	    sl_sprintf("Remotely queued %s", cur->song);
	    wp(wchan, "* Remotely queued download of \"%s\" from %s\n", cur->song, cur->nick);	    
	    break;
	  case -1:
	    sl_sprintf("Already getting %s", cur->song);
	    break;
	  case -2:
	    sl_sprintf("Already queued %s", cur->song);
	    break;
	  case -3:
	    sl_set("Can't get a file from yourself!");
	    break;
	  default:
	    break;
	  }
	}
	else /* !sk */
	{
	  sl_set("Not connected to server");
	}
      } 
      else /* !cur */
      {
	sl_set("Error: No matching item in search list??");
      }
    } /* end if(search) */
    break;

  case 'l':
    showlength = 1-showlength;
    break;

  case 'b':
    showbrate = 1-showbrate;
    break;

  case 'm':
    showsize = (showsize + 1) % 3;
    break;

  case 'f':
    showfreq = 1-showfreq;
    break;

  case 'u':
    showuser = 1-showuser;
    break;

  case 's':
    showconn = 1-showconn;
    break;

  case 'p':
    showping = 1-showping;
    break;

  case 'a':
    showlength = showbrate = showfreq = 1;
    showconn = showping = showuser = 1;
    showsize = showsize ? showsize : 1;
    break;

  case 'n':
    showlength = showbrate = showsize = showfreq = 0;
    showconn = showping = showuser = 0;
    break;

  case 'N':  /* various ways to sort */
  case 'D':
  case 'L':
  case 'B':
  case 'M':
  case 'F':
  case 'U':
  case 'S':
  case 'P':
    /* remember which item the cursor was on */
    list_nth(cur, search, num-1);
    
    if (ch == lastsort) {                 /* reverse? */

      list_reverse(ssearch_t, search);

    } else {                                /* sort */
      lastsort = ch;

      /* this looks awful, but list_mergesort is a fairly large macro,
	 and expanding it 7 times would probably be a worse option */
      list_mergesort(ssearch_t, search, a, b,
		     (ch=='N' && strcasecmp(a->song, b->song) <= 0)
		     || (ch=='D' && strcasecmp(a->rfn, b->rfn) <= 0)
		     || (ch=='L' && a->time <= b->time)
		     || (ch=='B' && a->brate <= b->brate)
		     || (ch=='M' && a->sz <= b->sz)
		     || (ch=='F' && a->freq <= b->freq)
		     || (ch=='U' && strcasecmp(a->nick, b->nick) <= 0)
		     || (ch=='S' && a->conn <= b->conn)
		     || (ch=='P' && (b->ping == -1 || 
				       (a->ping != -1 && a->ping <= b->ping)))
		     );
    }
    /* put the cursor back where it was */
    if (cur) {
      list_index(search, num, a, a==cur);
      num++;
      if (num >= (LINES-3)+cy || num < cy) {
	cy = num-(LINES-3)/2;
	if (cy<1)
	  cy = 1;
      }
    }
    break;
    
  default:
    break;
  }

  return(1);
}
