/* Copyright (c) 2010
 *      Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
 *      Sadrul Habib Chowdhury (sadrul@users.sourceforge.net)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file COPYING); if not, see
 * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 *
 ****************************************************************
 */

/* Deals with the list of windows */

/* NOTE: A 'struct win *' is used as the 'data' for each row. It might make more sense
 * to use 'struct win* ->w_number' as the 'data', instead, because that way, we can
 * verify that the window does exist (by looking at wtab[]).
 */

#include "config.h"
#include "screen.h"
#include "layer.h"
#include "extern.h"
#include "list_generic.h"

extern struct layer *flayer;
extern struct display *display, *displays;

extern char *wlisttit;
extern char *wliststr;

extern struct mchar mchar_blank, mchar_so;
extern int renditions[];

extern struct win **wtab, *windows, *fore;
extern int maxwin;

extern char *noargs[];

static char ListID[] = "window";

struct gl_Window_Data
{
  struct win *group;	/* Set only for a W_TYPE_GROUP window */
  int order;		/* MRU? NUM? */
  int onblank;
  int nested;
  struct win *fore;	/* The foreground window we had. */
};

/* Is this wdata for a group window? */
#define WLIST_FOR_GROUP(wdate)	((wdata)->group && !(wdata)->onblank && Layer2Window(flayer) && Layer2Window(flayer)->w_type == W_TYPE_GROUP)

/* This macro should not be used if 'fn' is expected to update the window list */
#define FOR_EACH_WINDOW(_wdata, _w, fn) do {	\
    if ((_wdata)->order == WLIST_MRU)	\
      {	\
	struct win *_ww;	\
	for (_ww = windows; _ww; _ww = _ww->w_next)	\
	  {	\
	    _w = _ww;	\
	    fn	\
	  }	\
      }	\
    else	\
      {	\
	struct win **_ww, *_witer;	\
	for (_ww = wtab, _witer = windows; _witer && _ww - wtab < maxwin; _ww++)	\
	  {	\
	    if (!(_w = *_ww))	continue;	\
	    fn	\
	    _witer = _witer->w_next;	\
	  }	\
      }	\
  } while (0)

/* Is 'a' an ancestor of 'd'? */
static int
window_ancestor(struct win *a, struct win *d)
{
  if (!a)
    return 1;	/* Every window is a descendant of the 'null' group */
  for (; d; d = d->w_group)
    if (d->w_group == a)
      return 1;
  return 0;
}

static void
window_kill_confirm(char *buf, int len, char *data)
{
  struct win *w = windows;
  struct action act;

  if (len || (*buf != 'y' && *buf != 'Y'))
    {
      memset(buf, 0, len);
      return;
    }

  /* Loop over the windows to make sure that the window actually still exists. */
  for (; w; w = w->w_next)
    if (w == (struct win *)data)
      break;

  if (!w)
    return;

  /* Pretend the selected window is the foreground window. Then trigger a non-interactive 'kill' */
  fore = w;
  act.nr = RC_KILL;
  act.args = noargs;
  act.argl = 0;
  act.quiet = 0;
  DoAction(&act, -1);
}

static struct ListRow *
gl_Window_add_group(struct ListData *ldata, struct ListRow *row)
{
  /* Right now, 'row' doesn't have any child. */
  struct gl_Window_Data *wdata = ldata->data;
  struct win *group = row->data, *w;
  struct ListRow *cur = row;

  ASSERT(wdata->nested);

  FOR_EACH_WINDOW(wdata, w,
    if (w->w_group != group)
      continue;

    cur = glist_add_row(ldata, w, cur);
    if (w == wdata->fore)
      ldata->selected = cur;

    if (w->w_type == W_TYPE_GROUP)
      cur = gl_Window_add_group(ldata, cur);
  );

  return cur;
}

static void
gl_Window_rebuild(struct ListData *ldata)
{
  struct ListRow *row = NULL;
  struct gl_Window_Data *wdata = ldata->data;
  struct win *w;

  FOR_EACH_WINDOW(wdata, w,
    if (w->w_group != wdata->group)
      continue;
    row = glist_add_row(ldata, w, row);
    if (w == wdata->fore)
      ldata->selected = row;
    if (w->w_type == W_TYPE_GROUP && wdata->nested)
      row = gl_Window_add_group(ldata, row);
  );
  glist_display_all(ldata);
}

static struct ListRow *
gl_Window_findrow(struct ListData *ldata, struct win *p)
{
  struct ListRow *row = ldata->root;
  for (; row; row = row->next)
    {
      if (row->data == p)
	break;
    }
  return row;
}

static int
gl_Window_remove(struct ListData *ldata, struct win *p)
{
  struct ListRow *row = gl_Window_findrow(ldata, p);
  if (!row)
    return 0;

  /* Remove 'row'. Update 'selected', 'top', 'root' if necessary. */
  if (row->next)
    row->next->prev = row->prev;
  if (row->prev)
    row->prev->next = row->next;

  if (ldata->selected == row)
    ldata->selected = row->prev ? row->prev : row->next;
  if (ldata->top == row)
    ldata->top = row->prev ? row->prev : row->next;
  if (ldata->root == row)
    ldata->root = row->next;

  ldata->list_fn->gl_freerow(ldata, row);
  free(row);

  return 1;
}

static int
gl_Window_header(struct ListData *ldata)
{
  char *str;
  struct gl_Window_Data *wdata = ldata->data;
  int g;

  if ((g = (wdata->group != NULL)))
    {
      LPutWinMsg(flayer, "Group: ", 7, &mchar_blank, 0, 0);
      LPutWinMsg(flayer, wdata->group->w_title, strlen(wdata->group->w_title), &mchar_blank, 7, 0);
    }

  str = MakeWinMsgEv(wlisttit, (struct win *)0, '%', flayer->l_width, (struct event *)0, 0);

  LPutWinMsg(flayer, str, strlen(str), &mchar_blank, 0, g);
  return 2 + g;
}

static int
gl_Window_footer(struct ListData *ldata)
{
  return 0;
}

static int
gl_Window_row(struct ListData *ldata, struct ListRow *lrow)
{
  char *str;
  struct win *w, *g;
  int xoff;
  struct mchar *mchar;
  struct mchar mchar_rend = mchar_blank;
  struct gl_Window_Data *wdata = ldata->data;

  w = lrow->data;

  /* First, make sure we want to display this window in the list.
   * If we are showing a list for a group, and not on blank, then we must
   * only show the windows directly belonging to that group.
   * Otherwise, do some more checks. */

  for (xoff = 0, g = w->w_group; g != wdata->group; g = g->w_group)
    xoff += 2;
  str = MakeWinMsgEv(wliststr, w, '%', flayer->l_width - xoff, NULL, 0);
  if (ldata->selected == lrow)
    mchar = &mchar_so;
  else if (w->w_monitor == MON_DONE && renditions[REND_MONITOR] != -1)
    {
      mchar = &mchar_rend;
      ApplyAttrColor(renditions[REND_MONITOR], mchar);
    }
  else if ((w->w_bell == BELL_DONE || w->w_bell == BELL_FOUND) && renditions[REND_BELL] != -1)
    {
      mchar = &mchar_rend;
      ApplyAttrColor(renditions[REND_BELL], mchar);
    }
  else if ((w->w_silence == SILENCE_FOUND || w->w_silence == SILENCE_DONE) && renditions[REND_SILENCE] != -1)
    {
      mchar = &mchar_rend;
      ApplyAttrColor(renditions[REND_SILENCE], mchar);
    }
  else
    mchar = &mchar_blank;

  LPutWinMsg(flayer, str, flayer->l_width, mchar, xoff, lrow->y);
  if (xoff)
    LPutWinMsg(flayer, "", xoff, mchar, 0, lrow->y);

  return 1;
}

static int
gl_Window_input(struct ListData *ldata, char **inp, int *len)
{
  struct win *win;
  unsigned char ch;
  struct display *cd = display;
  struct gl_Window_Data *wdata = ldata->data;

  if (!ldata->selected)
    return 0;

  ch = (unsigned char) **inp;
  ++*inp;
  --*len;

  win = ldata->selected->data;
  switch (ch)
    {
    case ' ':
    case '\n':
    case '\r':
      if (!win)
	break;
#ifdef MULTIUSER
      if (display && AclCheckPermWin(D_user, ACL_READ, win))
	return 0;		/* Not allowed to switch to this window. */
#endif
      if (WLIST_FOR_GROUP(wdata))
	SwitchWindow(win->w_number);
      else
	{
	  /* Abort list only when not in a group window. */
	  glist_abort();
	  display = cd;
	  if (D_fore != win)
	    SwitchWindow(win->w_number);
	}
      *len = 0;
      break;

    case 'm':
      /* Toggle MRU-ness */
      wdata->order = wdata->order == WLIST_MRU ? WLIST_NUM : WLIST_MRU;
      glist_remove_rows(ldata);
      gl_Window_rebuild(ldata);
      break;

    case 'g':
      /* Toggle nestedness */
      wdata->nested = !wdata->nested;
      glist_remove_rows(ldata);
      gl_Window_rebuild(ldata);
      break;

    case 'a':
      /* All-window view */
      if (wdata->group)
	{
	  int order = wdata->order | (wdata->nested ? WLIST_NESTED : 0);
	  glist_abort();
	  display = cd;
	  display_windows(1, order, NULL);
	  *len = 0;
	}
      else if (!wdata->nested)
	{
	  wdata->nested = 1;
	  glist_remove_rows(ldata);
	  gl_Window_rebuild(ldata);
	}
      break;

    case 010:	/* ^H */
    case 0177:	/* Backspace */
      if (!wdata->group)
	break;
      if (wdata->group->w_group)
	{
	  /* The parent is another group window. So switch to that window. */
	  struct win *g = wdata->group->w_group;
	  glist_abort();
	  display = cd;
	  SetForeWindow(g);
	  *len = 0;
	}
      else
	{
	  /* We were in a group view. Now we are moving to an all-window view.
	   * So treat it as 'windowlist on blank'. */
	  int order = wdata->order | (wdata->nested ? WLIST_NESTED : 0);
	  glist_abort();
	  display = cd;
	  display_windows(1, order, NULL);
	  *len = 0;
	}
      break;

    case ',':	/* Switch numbers with the previous window. */
      if (wdata->order == WLIST_NUM && ldata->selected->prev)
	{
	  struct win *pw = ldata->selected->prev->data;
	  if (win->w_group != pw->w_group)
	    break;	/* Do not allow switching with the parent group */

	  /* When a windows's number is successfully changed, it triggers a WListUpdatecv
	   * with NULL window. So that causes a redraw of the entire list. So reset the
	   * 'selected' after that. */
	  wdata->fore = win;
	  WindowChangeNumber(win->w_number, pw->w_number);
	}
      break;

    case '.':	/* Switch numbers with the next window. */
      if (wdata->order == WLIST_NUM && ldata->selected->next)
	{
	  struct win *nw = ldata->selected->next->data;
	  if (win->w_group != nw->w_group)
	    break;	/* Do not allow switching with the parent group */

	  wdata->fore = win;
	  WindowChangeNumber(win->w_number, nw->w_number);
	}
      break;

    case 'K':	/* Kill a window */
      {
	char str[MAXSTR];
	snprintf(str, sizeof(str) - 1, "Really kill window %d (%s) [y/n]",
	    win->w_number, win->w_title);
	Input(str, 1, INP_RAW, window_kill_confirm, (char *)win, 0);
      }
      break;

    case 033:	/* escape */
    case 007:	/* ^G */
      if (!WLIST_FOR_GROUP(wdata))
	{
	  int fnumber = wdata->onblank ? wdata->fore->w_number : -1;
	  glist_abort();
	  display = cd;
	  if (fnumber >= 0)
	    SwitchWindow(fnumber);
	  *len = 0;
	}
      break;
    default:
      if (ch >= '0' && ch <= '9')
	{
	  struct ListRow *row = ldata->root;
	  for (; row; row = row->next)
	    {
	      struct win *w = row->data;
	      if (w->w_number == ch - '0')
		{
		  struct ListRow *old = ldata->selected;
		  if (old == row)
		    break;
		  ldata->selected = row;
		  if (ldata->selected->y == -1)
		    {
		      /* We need to list all the rows, since we are scrolling down. But first,
		       * find the top of the visible list. */
		      ldata->top = row;
		      glist_display_all(ldata);
		    }
		  else
		    {
		      /* just redisplay the two lines. */
		      ldata->list_fn->gl_printrow(ldata, old);
		      ldata->list_fn->gl_printrow(ldata, ldata->selected);
		      flayer->l_y = ldata->selected->y;
		      LaySetCursor();
		    }
		  break;
		}
	    }
	  break;
	}
      --*inp;
      ++*len;
      return 0;
    }
  return 1;
}

static int
gl_Window_freerow(struct ListData *ldata, struct ListRow *row)
{
  return 0;
}

static int
gl_Window_free(struct ListData *ldata)
{
  Free(ldata->data);
  return 0;
}

static int
gl_Window_match(struct ListData *ldata, struct ListRow *row, const char *needle)
{
  struct win *w = row->data;
  if (InStr(w->w_title, needle))
    return 1;
  return 0;
}

static struct GenericList gl_Window =
{
  gl_Window_header,
  gl_Window_footer,
  gl_Window_row,
  gl_Window_input,
  gl_Window_freerow,
  gl_Window_free,
  gl_Window_match
};

void
display_windows(int onblank, int order, struct win *group)
{
  struct win *p;
  struct ListData *ldata;
  struct gl_Window_Data *wdata;

  if (flayer->l_width < 10 || flayer->l_height < 6)
    {
      LMsg(0, "Window size too small for window list page");
      return;
    }

  if (group)
    onblank = 0;	/* When drawing a group window, ignore 'onblank' */

  if (onblank)
    {
      debug3("flayer %x %d %x\n", flayer, flayer->l_width, flayer->l_height);
      if (!display)
	{
	  LMsg(0, "windowlist -b: display required");
	  return;
	}
      p = D_fore;
      if (p)
	{
	  SetForeWindow((struct win *)0);
          if (p->w_group)
	    {
	      D_fore = p->w_group;
	      flayer->l_data = (char *)p->w_group;
	    }
	  Activate(0);
	}
      if (flayer->l_width < 10 || flayer->l_height < 6)
	{
	  LMsg(0, "Window size too small for window list page");
	  return;
	}
    }
  else
    p = Layer2Window(flayer);
  if (!group && p)
    group = p->w_group;

  ldata = glist_display(&gl_Window, ListID);
  if (!ldata)
    {
      if (onblank && p)
	{
	  /* Could not display the list. So restore the window. */
	  SetForeWindow(p);
	  Activate(1);
	}
      return;
    }

  wdata = calloc(1, sizeof(struct gl_Window_Data));
  wdata->group = group;
  wdata->order = (order & ~WLIST_NESTED);
  wdata->nested = !!(order & WLIST_NESTED);
  wdata->onblank = onblank;

  /* Set the most recent window as selected. */
  wdata->fore = windows;
  while (wdata->fore && wdata->fore->w_group != group)
    wdata->fore = wdata->fore->w_next;

  ldata->data = wdata;

  gl_Window_rebuild(ldata);
}

static void
WListUpdate(struct win *p, struct ListData *ldata)
{
  struct gl_Window_Data *wdata = ldata->data;
  struct ListRow *row, *rbefore;
  struct win *before;
  int d = 0, sel = 0;

  if (!p)
    {
      if (ldata->selected)
	wdata->fore = ldata->selected->data;	/* Try to retain the current selection */
      glist_remove_rows(ldata);
      gl_Window_rebuild(ldata);
      return;
    }

  /* First decide if this window should be displayed at all. */
  d = 1;
  if (wdata->order == WLIST_NUM || wdata->order == WLIST_MRU)
    {
      if (p->w_group != wdata->group)
	{
	  if (!wdata->nested)
	    d = 0;
	  else
	    d = window_ancestor(wdata->group, p);
	}
    }

  if (!d)
    {
      if (gl_Window_remove(ldata, p))
	glist_display_all(ldata);
      return;
    }

  /* OK, so we keep the window in the list. Update the ordering.
   * First, find the row where this window should go to. Then, either create
   * a new row for that window, or move the exising row for the window to the
   * correct place. */
  before = NULL;
  if (wdata->order == WLIST_MRU)
    {
      if (windows != p)
	for (before = windows; before; before = before->w_next)
	  if (before->w_next == p)
	    break;
    }
  else if (wdata->order == WLIST_NUM)
    {
      if (p->w_number != 0)
	{
	  struct win **w = wtab + p->w_number - 1;
	  for (; w >= wtab; w--)
	    {
	      if (*w && (*w)->w_group == wdata->group)
		{
		  before = *w;
		  break;
		}
	    }
	}
    }

  /* Now, find the row belonging to 'before' */
  if (before)
    rbefore = gl_Window_findrow(ldata, before);
  else if (wdata->nested && p->w_group)	/* There's no 'before'. So find the group window */
    rbefore = gl_Window_findrow(ldata, p->w_group);
  else
    rbefore = NULL;

  /* For now, just remove the row containing 'p' if it is not already in the right place . */
  row = gl_Window_findrow(ldata, p);
  if (row)
    {
      if (row->prev != rbefore)
	{
	  sel = ldata->selected->data == p;
	  gl_Window_remove(ldata, p);
	}
      else
	p = NULL;		/* the window is in the correct place */
    }
  if (p)
    {
      row = glist_add_row(ldata, p, rbefore);
      if (sel)
	ldata->selected = row;
    }
  glist_display_all(ldata);
}

void
WListUpdatecv(cv, p)
struct canvas *cv;
struct win *p;
{
  struct ListData *ldata;
  struct gl_Window_Data *wdata;

  if (cv->c_layer->l_layfn != &ListLf)
    return;
  ldata = cv->c_layer->l_data;
  if (ldata->name != ListID)
    return;
  wdata = ldata->data;
  CV_CALL(cv, WListUpdate(p, ldata));
}

void
WListLinkChanged()
{
  struct display *olddisplay = display;
  struct canvas *cv;
  struct ListData *ldata;
  struct gl_Window_Data *wdata;

  for (display = displays; display; display = display->d_next)
    for (cv = D_cvlist; cv; cv = cv->c_next)
      {
        if (!cv->c_layer || cv->c_layer->l_layfn != &ListLf)
	  continue;
	ldata = cv->c_layer->l_data;
	if (ldata->name != ListID)
	  continue;
	wdata = ldata->data;
	if (!(wdata->order & WLIST_MRU))
	  continue;
        CV_CALL(cv, WListUpdate(0, ldata));
      }
  display = olddisplay;
}

