/*
** pork_screen_cmd.c - screen management.
** Copyright (C) 2002-2004 Ryan McCabe <ryan@numb.org>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License, version 2,
** as published by the Free Software Foundation.
**
** The functions in this file are mostly wrappers around
** functions that do the actual work. The difference is
** these functions do any necessary updating of the contents
** of the screen (i.e. refreshing, redrawing, etc).
*/

#include <config.h>

#include <unistd.h>
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>

#include <pork.h>
#include <pork_missing.h>
#include <pork_util.h>
#include <pork_list.h>
#include <pork_imwindow.h>
#include <pork_proto.h>
#include <pork_acct.h>
#include <pork_cstr.h>
#include <pork_misc.h>
#include <pork_chat.h>
#include <pork_buddy.h>
#include <pork_screen.h>
#include <pork_screen_cmd.h>
#include <pork_status.h>

inline void screen_draw_cursor(void) {
	wmove(screen.status_bar, STATUS_ROWS - 1,
		input_get_cursor_pos(cur_window()->input));
	wnoutrefresh(screen.status_bar);
}

inline void screen_doupdate(void) {
	int cur_old = curs_set(0);

	screen_draw_cursor();
	doupdate();
	curs_set(cur_old);
}

int screen_draw_input(void) {
	struct input *input = cur_window()->input;

	if (input->dirty) {
		char *input_line;
		int len;

		len = input->width;
		input_line = input_partial(input);

		if (input_line == NULL)
			return (0);

		wmove(screen.status_bar, STATUS_ROWS - 1, 0);
		wclrtoeol(screen.status_bar);

		if (input_line == input->input_buf && input->prompt != NULL) {
			wputnstr(screen.status_bar, input->prompt, min(input->width, input->prompt_len));
			len -= input->prompt_len;
		}

		if (len < 0)
			return (1);

		wputncstr(screen.status_bar, input_line, len);

		/*
		** We don't need a wnoutfresh here for this window. Since it's
		** dirty (implying we return 1), screen_doupdate() will be called,
		** which redraws the cursor and calls wnoutrefresh on the status
		** window, which happens to be the window the input is drawn in.
		*/

		input->dirty = 0;
		return (1);
	}

	return (0);
}

void screen_window_swap(dlist_t *new_cur) {
	struct imwindow *imwindow = NULL;
	u_int32_t last_own_input = 0;
	u_int32_t cur_own_input;
	struct pork_acct *acct;

	if (screen.cur_window != NULL) {
		imwindow = cur_window();

		last_own_input = wopt_get_bool(imwindow->opts, WOPT_PRIVATE_INPUT);
		imwindow->swindow.activity = 0;
		imwindow->swindow.visible = 0;
	}

	screen.cur_window = new_cur;

	imwindow = cur_window();
	imwindow->swindow.visible = 1;
	imwindow->swindow.activity = 0;
	cur_own_input = wopt_get_bool(imwindow->opts, WOPT_PRIVATE_INPUT);

	/*
	** If the current window and the one that we're going to switch
	** to don't share an input buffer, history, etc, the input
	** line must be refreshed. Force a redraw.
	*/

	if (cur_own_input || last_own_input) {
		imwindow->input->dirty = 1;
		screen_draw_input();
	}

	status_draw(imwindow->owner);

	acct = imwindow->owner;

	/*
	** To force ncurses to redraw it on the physical screen.
	*/
	redrawwin(imwindow->swindow.win);
	imwindow->swindow.dirty = 1;
	imwindow_blist_draw(imwindow);
}

inline int screen_goto_window(u_int32_t refnum) {
	dlist_t *cur = screen_find_refnum(refnum);

	if (cur == NULL)
		return (-1);

	screen_window_swap(cur);
	return (0);
}

void screen_refresh(void) {
	struct imwindow *imwindow = cur_window();

	wclear(curscr);

	wclear(imwindow->swindow.win);
	swindow_redraw(&imwindow->swindow);
	imwindow_blist_draw(imwindow);

	status_draw(imwindow->owner);

	imwindow_refresh(imwindow);

	imwindow->input->dirty = 1;
	screen_draw_input();

	screen_doupdate();
}

struct imwindow *screen_new_window(	struct pork_acct *acct,
									char *target,
									char *name)
{
	u_int32_t refnum = screen_get_new_refnum();
	struct imwindow *imwindow;
	u_int32_t rows;

	rows = max(1, (int) screen.rows - STATUS_ROWS);

	imwindow = imwindow_new(rows, screen.cols,
		refnum, WIN_TYPE_PRIVMSG, acct, target);
	if (imwindow == NULL)
		return (NULL);

	if (name != NULL)
		imwindow_rename(imwindow, name);

	screen_add_window(imwindow);
	status_draw(imwindow->owner);

	return (imwindow);
}

struct imwindow *screen_new_chat_window(struct pork_acct *acct, char *name) {
	u_int32_t refnum = screen_get_new_refnum();
	struct imwindow *imwindow;
	u_int32_t rows;

	rows = max(1, (int) screen.rows - STATUS_ROWS);
	imwindow = imwindow_new(rows, screen.cols,
		refnum, WIN_TYPE_CHAT, acct, name);
	if (imwindow == NULL)
		return (NULL);

	imwindow->data = NULL;

	screen_add_window(imwindow);
	status_draw(imwindow->owner);

	return (imwindow);
}

int screen_get_query_window(struct pork_acct *acct,
							char *name,
							struct imwindow **winr)
{
	struct imwindow *win;
	int new = 0;

	win = imwindow_find(acct, name);
	if (win == NULL || win->type != WIN_TYPE_PRIVMSG) {
		if (opt_get_bool(OPT_DUMP_MSGS_TO_STATUS))
			win = screen.status_win;
		else {
			win = screen_new_window(acct, name, buddy_name(acct, name));
			new++;
		}
	}

	*winr = win;
	return (new);
}

/*
** When we really want a query window.
*/

int screen_make_query_window(struct pork_acct *acct,
							char *name,
							struct imwindow **winr)
{
	struct imwindow *win;
	int new = 0;

	win = imwindow_find(acct, name);
	if (win == NULL || win->type != WIN_TYPE_PRIVMSG) {
		win = screen_new_window(acct, name, buddy_name(acct, name));
		new++;
	}

	*winr = win;
	return (new);
}

void screen_cycle_fwd(void) {
	dlist_t *cur = screen.cur_window;
	struct imwindow *win;

	do {
		cur = cur->next;
		win = cur->data;
	} while (win->skip && cur != screen.cur_window);

	screen_window_swap(cur);
}

void screen_bind_all_unbound(struct pork_acct *acct) {
	dlist_t *node;

	node = screen.window_list;

	do {
		struct imwindow *imwindow = node->data;

		if (imwindow->owner == screen.null_acct) {
			imwindow_bind_acct(imwindow, acct->refnum);

			if (!imwindow->blist_visible) {
				if (wopt_get_bool(imwindow->opts, WOPT_SHOW_BLIST))
					wopt_set(imwindow, WOPT_SHOW_BLIST, "1");
				}
			}

		node = node->next;
	} while (node != screen.window_list);
}

void screen_cycle_bak(void) {
	dlist_t *cur = screen.cur_window;
	struct imwindow *win;

	do {
		cur = cur->prev;
		win = cur->data;
	} while (win->skip && cur != screen.cur_window);

	screen_window_swap(cur);
}

int screen_close_window(struct imwindow *imwindow) {
	dlist_t *node = screen_find_refnum(imwindow->refnum);

	if (node == NULL)
		return (-1);

	/*
	** The status window can't be closed.
	*/

	if (imwindow->type == WIN_TYPE_STATUS)
		return (-1);

	if (imwindow->type == WIN_TYPE_CHAT && imwindow->data != NULL) {
		struct chatroom *chat = imwindow->data;

		chat_leave(imwindow->owner, chat->title, 0);
	}

	if (imwindow->swindow.visible)
		screen_cycle_fwd();

	screen_window_list_remove(node);
	imwindow_destroy(imwindow);
	free(node);

	return (0);
}

inline int screen_set_quiet(int status) {
	int ret = screen.quiet;

	screen.quiet = status;
	return (ret);
}

static void __screen_win_msg(	struct imwindow *win,
								int timestamp,
								int banner,
								int color,
								char *fmt,
								va_list ap)
{
	char buf[4096];
	chtype ch[4096];
	size_t chlen = array_elem(ch);
	int (*cstr_conv)(const char *, chtype *, size_t) = plaintext_to_cstr;
	size_t banner_len = 0;
	char *p;

	if (banner) {
		char *banner_txt;

		banner_txt = opt_get_str(OPT_BANNER);
		if (banner_txt != NULL && banner_txt[0] != '\0') {
			banner_len = plaintext_to_cstr(banner_txt, ch, chlen);
			chlen -= banner_len;
		}
	}

	if (!color)
		cstr_conv = plaintext_to_cstr_nocolor;

	vsnprintf(buf, sizeof(buf), fmt, ap);

	p = strchr(buf, '\n');
	if (p != NULL)
		*p++ = '\0';

	cstr_conv(buf, &ch[banner_len], chlen);
	imwindow_add(win, ch, timestamp);

	while (p != NULL) {
		char *next;

		next = strchr(p, '\n');
		if (next != NULL)
			*next++ = '\0';

		/* XXX - this should be configurable */
		ch[0] = ch[1] = ' ';
		chlen = array_elem(ch) - 2;

		plaintext_to_cstr(p, &ch[2], chlen);
		imwindow_add(win, ch, 0);
		p = next;
	}
}

inline void screen_win_msg(	struct imwindow *win,
							int ts,
							int banner,
							int color,
							char *fmt,
							...)
{
	va_list ap;

	va_start(ap, fmt);
	__screen_win_msg(win, ts, banner, color, fmt, ap);
	va_end(ap);
}

inline void screen_write(struct imwindow *win, char *buf) {
	chtype ch[4096];
	size_t chlen = array_elem(ch);

	plaintext_to_cstr(buf, ch, chlen);
	imwindow_add(win, ch, 0);
}

inline void screen_status_msg(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	if (!screen.quiet)
		__screen_win_msg(cur_window(), 0, 1, 1, fmt, ap);
	va_end(ap);
}

inline void screen_status_msg_ts(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	if (!screen.quiet)
		__screen_win_msg(cur_window(), 1, 1, 1, fmt, ap);
	va_end(ap);
}

inline void screen_err_msg(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	__screen_win_msg(cur_window(), 0, 1, 1, fmt, ap);
	va_end(ap);
}

inline void screen_output(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	__screen_win_msg(cur_window(), 0, 1, 1, fmt, ap);
	va_end(ap);
}

inline void screen_nocolor_msg(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	if (!screen.quiet)
		__screen_win_msg(cur_window(), 0, 1, 0, fmt, ap);
	va_end(ap);
}

void screen_win_target_msg(	struct pork_acct *owner,
							char *target,
							int timestamp,
							char *fmt, ...)
{
	va_list ap;
	struct imwindow *win = NULL;

	if (target != NULL)
		win = imwindow_find(owner, target);

	if (win == NULL)
		win = screen.status_win;

	va_start(ap, fmt);
	__screen_win_msg(win, timestamp, 0, 1, fmt, ap);
	va_end(ap);
}

int screen_prompt_user(char *prompt, char *buf, size_t len) {
	int ret;

	buf[0] = '\0';

	wmove(screen.status_bar, 1, 0);
	wclrtoeol(screen.status_bar);

	if (prompt != NULL)
		waddstr(screen.status_bar, prompt);

	wrefresh(screen.status_bar);

	ret = wgetnstr(screen.status_bar, buf, len);
	return (ret);
}
