/*
** pork_screen.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.
*/

#include <config.h>

#include <unistd.h>
#include <ncurses.h>
#include <stdlib.h>
#include <string.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_misc.h>
#include <pork_imwindow.h>
#include <pork_buddy_list.h>
#include <pork_proto.h>
#include <pork_acct.h>
#include <pork_screen.h>
#include <pork_screen_cmd.h>
#include <pork_status.h>
#include <pork_io.h>

/*
** Create the program's status window. This window will have the buddy
** list visible in it by default.
*/

int screen_init(u_int32_t rows, u_int32_t cols) {
	struct imwindow *imwindow;
	struct pork_acct *acct;

	memset(&screen, 0, sizeof(screen));

	screen.rows = rows;
	screen.cols = cols;

	bind_init(&screen.binds);
	input_init(&screen.input, cols);

	if (status_init() == -1)
		return (-1);

	acct = pork_acct_init(opt_get_str(OPT_TEXT_NO_NAME), PROTO_NULL);
	if (acct == NULL)
		return (-1);
	acct->refnum = -1UL;

	pork_acct_add(acct);
	screen.null_acct = acct;

	pork_io_add(STDIN_FILENO, IO_COND_READ, &screen, &screen,
		keyboard_input);

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

	imwindow = imwindow_new(rows, cols, 1, WIN_TYPE_STATUS, acct, "status");
	if (imwindow == NULL)
		return (-1);

	wopt_set(imwindow, WOPT_SHOW_BLIST, "1");
	screen_add_window(imwindow);
	screen.status_win = imwindow;
	return (0);
}

void screen_destroy(void) {
	dlist_t *cur = screen.window_list;

	do {
		dlist_t *next = cur->next;

		imwindow_destroy(cur->data);
		free(cur);

		cur = next;
	} while (cur != screen.window_list);

	opt_destroy();
	input_destroy(&screen.input);
	bind_destroy(&screen.binds);
	hash_destroy(&screen.alias_hash);
	event_destroy(&screen.events);
	delwin(screen.status_bar);
	wclear(curscr);
}

void screen_window_list_add(dlist_t *new_node) {
	struct imwindow *imwindow = new_node->data;

	/*
	** The window list is a sorted circular doubly linked list.
	** The window_list pointer points to the window having the lowest
	** refnum.
	*/

	if (screen.window_list == NULL) {
		new_node->prev = new_node;
		new_node->next = new_node;
		screen.window_list = new_node;
	} else {
		dlist_t *cur = screen.window_list;

		do {
			struct imwindow *imw = cur->data;

			if (imwindow->refnum < imw->refnum) {
				if (cur == screen.window_list)
					screen.window_list = new_node;
				break;
			}

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

		new_node->next = cur;
		new_node->prev = cur->prev;

		new_node->next->prev = new_node;
		new_node->prev->next = new_node;
	}
}

void screen_window_list_remove(dlist_t *node) {
	dlist_t *save = node;

	if (node == screen.window_list)
		screen.window_list = node->next;

	if (node == screen.window_list)
		screen.window_list = NULL;

	save->prev->next = node->next;
	save->next->prev = node->prev;
}

void screen_add_window(struct imwindow *imwindow) {
	dlist_t *new_node = xmalloc(sizeof(*new_node));

	new_node->data = imwindow;

	screen_window_list_add(new_node);

	/*
	** If this is the first window, make it current.
	*/

	if (screen.cur_window == NULL)
		screen_window_swap(new_node);
}

/*
** Change the refnum on the currently visible window.
*/

int screen_renumber(struct imwindow *imwindow, u_int32_t refnum) {
	dlist_t *node;
	u_int32_t old_refnum = imwindow->refnum;

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

	screen_window_list_remove(node);
	imwindow->refnum = refnum;

	/*
	** If there's more than one window, check to
	** make sure that no other window's refnum
	** is equal to the refnum we just set for 'imwindow'.
	** If it is, give it 'imwindow's' old refnum.
	*/

	if (node != node->next || node->next != node->prev) {
		dlist_t *temp = screen_find_refnum(refnum);
		if (temp != NULL) {
			struct imwindow *imw;

			screen_window_list_remove(temp);
			imw = temp->data;
			imw->refnum = old_refnum;
			screen_window_list_add(temp);
			screen_win_msg(imw, 0, 1, 1,
				"This is now window %%W%u", old_refnum);
		}
	}

	screen_window_list_add(node);
	screen_status_msg("This is now window %%W%u", imwindow->refnum);
	return (0);
}

void screen_resize(u_int32_t rows, u_int32_t cols) {
	dlist_t *cur;
	int ret;

	resize_terminal(rows, cols);

	screen.rows = rows;
	screen.cols = cols;

	for (cur = screen.acct_list ; cur != NULL ; cur = cur->next) {
		struct pork_acct *acct = cur->data;

		if (acct->blist != NULL) {
			blist_resize(acct->blist, max(1, (int) rows - STATUS_ROWS),
				acct->blist->slist.cols, cols);
		}
	}

	cur = screen.window_list;
	do {
		struct imwindow *imwindow = cur->data;
		u_int32_t im_cols = cols;

		if (imwindow->blist_visible) {
			u_int32_t blist_cols = imwindow->owner->blist->slist.cols;

			if (blist_cols >= cols) {
				imwindow->blist_visible = 0;
				imwindow->active_binds = &screen.binds.main;
			} else
				im_cols -= blist_cols;
		}

		imwindow_resize(imwindow,
			max(1, (int) rows - STATUS_ROWS), im_cols);
		input_resize(imwindow->input, cols);

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

	ret = mvwin(screen.status_bar, max(0, (int) rows - STATUS_ROWS), 0);
	if (ret == -1) {
		delwin(screen.status_bar);
		status_init();
	}
}

/*
** Find the window having the specified refnum, and return
** a pointer to the node that holds it.
**
** This depends on the list being sorted.
*/

dlist_t *screen_find_refnum(u_int32_t refnum) {
	dlist_t *cur = screen.window_list;

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

		if (imwindow->refnum == refnum)
			return (cur);

		if (imwindow->refnum > refnum)
			break;

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

	return (NULL);
}

/*
** Yes, this is pretty slow and stupid, but it'd be pretty
** unusual (i think) to have even 10 windows open at any one time.
**
** It's also only called when creating a new window.
*/

u_int32_t screen_get_new_refnum(void) {
	u_int32_t i;

	for (i = 1 ; i < 0xffffffff ; i++) {
		if (screen_find_refnum(i) == NULL)
			return (i);
	}

	return (0);
}

int screen_blist_width(struct blist *blist, u_int32_t new_width) {
	dlist_t *cur;

	if (new_width < 3 || new_width >= screen.cols)
		return (-1);

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

	cur = screen.window_list;
	do {
		struct imwindow *imwindow = cur->data;

		if (imwindow->owner != NULL && imwindow->owner->blist == blist &&
			imwindow->blist_visible)
		{
			imwindow_resize(imwindow, imwindow->swindow.rows,
				imwindow->swindow.cols + blist->slist.cols - new_width);
		}
		cur = cur->next;
	} while (cur != screen.window_list);

	blist_resize(blist, blist->slist.rows, new_width, screen.cols);
	return (0);
}
