/*
 * Copyright (c) 2013, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Intel Corporation nor the names of its contributors
 *     may be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * numatop uses libcurses to display data on screen. For better control,
 * numatop brings a new definition 'reg' (regin) to control the data on
 * screen. It locates where the data is, what the attributes it has
 * (e.g. font, color, scrolling enable?, ...).
 * This file contains code to handle the 'reg'.
 */

#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <curses.h>
#include "include/reg.h"
#include "include/types.h"
#include "include/win.h"
#include "include/disp.h"

int g_scr_height;
int g_scr_width;

static boolean_t s_curses_init = B_FALSE;

/*
 * Highlight the selected line.
 */
static void
reg_idx_highlight(win_reg_t *r, int idx)
{
	scroll_line_t *scroll = &r->scroll;
	char line[WIN_LINECHAR_MAX];

	if (r->hdl != NULL) {
		r->line_get(r, idx, line, WIN_LINECHAR_MAX);
		reg_highlight_write(r, idx - scroll->page_start,
		    ALIGN_LEFT, line);
	}
}

/*
 * Display the hidden lines on screen.
 */
static void
reg_hidden_show(win_reg_t *r, int idx_start)
{
	int i, idx_end;
	char line[WIN_LINECHAR_MAX];

	if (r->hdl == NULL) {
		return;
	}

	if ((idx_end = idx_start + r->nlines_scr) > r->nlines_total) {
		idx_end = r->nlines_total;
	}

	reg_erase(r);
	for (i = idx_start; i < idx_end; i++) {
		r->line_get(r, i, line, WIN_LINECHAR_MAX);
		reg_line_write(r, i - idx_start, ALIGN_LEFT, line);
	}
}

/*
 * Lowlight the selected line.
 */
static void
reg_idx_lowlight(win_reg_t *r, int idx)
{
	scroll_line_t *scroll = &r->scroll;
	char line[WIN_LINECHAR_MAX];

	if (r->hdl != NULL) {
		r->line_get(r, idx, line, WIN_LINECHAR_MAX);
		reg_line_write(r, idx - scroll->page_start,
		    ALIGN_LEFT, line);
	}
}

/*
 * Create a 'window'.
 */
static WINDOW *
reg_win_create(win_reg_t *r)
{
	return (subwin(stdscr, r->nlines_scr, r->ncols_scr,
	    r->begin_y, r->begin_x));
}

/*
 * Initialization for one 'reg'.
 */
int
reg_init(win_reg_t *r, int begin_x, int begin_y, int ncols, int nlines,
	unsigned int mode)
{
	if ((ncols <= 0) || (nlines <= 0)) {
		return (-1);
	}

	(void) memset(r, 0, sizeof (win_reg_t));
	r->begin_x = begin_x;
	r->begin_y = begin_y;
	r->ncols_scr = ncols;
	r->nlines_scr = nlines;
	r->mode = mode;
	r->hdl = reg_win_create(r);
	return (r->begin_y + r->nlines_scr);
}

/*
 * Initialization for the data buffer in 'reg'.
 */
void
reg_buf_init(win_reg_t *r, void *buf,
	void (*line_get)(win_reg_t *, int, char *, int))
{
	r->buf = buf;
	r->line_get = line_get;
}

/*
 * Initialization for 'scrolling'.
 */
void
reg_scroll_init(win_reg_t *r, boolean_t enable)
{
	scroll_line_t *scroll = &r->scroll;

	scroll->enabled = enable;
	scroll->highlight = -1;
}

/*
 * Erase the data in 'reg' on screen.
 */
void
reg_erase(win_reg_t *r)
{
	if (r->hdl != NULL) {
		(void) werase(r->hdl);
	}
}

/*
 * Refresh the data in 'reg' and display the update data on screen.
 */
void
reg_refresh(win_reg_t *r)
{
	if (r->hdl != NULL) {
		(void) wrefresh(r->hdl);
	}
}

/*
 * Refresh the data in 'reg' but doesn't display the update data on
 * screen immediately.
 */
void
reg_refresh_nout(win_reg_t *r)
{
	if (r->hdl != NULL) {
		(void) wnoutrefresh(r->hdl);
	}
}

/*
 * Update the data on screen immediately.
 */
void
reg_update_all(void)
{
	(void) doupdate();
}

/*
 * Free the resource of libcurses 'window'.
 */
void
reg_win_destroy(win_reg_t *r)
{
	if (r->hdl != NULL) {
		(void) delwin(r->hdl);
		r->hdl = NULL;
	}
}

/*
 * Fill data in a line and display the line on screen.
 */
void
reg_line_write(win_reg_t *r, int line, reg_align_t align, char *content)
{
	int pos_x = 0, len;

	if (r->hdl == NULL) {
		return;
	}

	if (r->mode != 0) {
		(void) wattron(r->hdl, r->mode);
	}

	len = strlen(content);
	if (align == ALIGN_MIDDLE) {
		pos_x = (r->ncols_scr - len) / 2;
	}

	if (len > 0) {
		(void) mvwprintw(r->hdl, line, pos_x, "%s", content);
	}

	if (r->mode != 0) {
		(void) wattroff(r->hdl, r->mode);
	}
}

/*
 * Fill data in one line and display it on screen with highlight.
 */
void
reg_highlight_write(win_reg_t *r, int line, int align, char *content)
{
	int pos_x = 0, len;

	if (r->hdl == NULL) {
		return;
	}

	(void) wattron(r->hdl, A_REVERSE | A_BOLD);
	len = strlen(content);
	if (align == ALIGN_MIDDLE) {
		pos_x = (r->ncols_scr - len) / 2;
	}

	if (len > 0) {
		(void) mvwprintw(r->hdl, line, pos_x, "%s", content);
	}

	(void) wattroff(r->hdl, A_REVERSE | A_BOLD);
}

/*
 * Scroll one line UP/DOWN.
 */
void
reg_line_scroll(win_reg_t *r, int scroll_type)
{
	scroll_line_t *scroll = &r->scroll;
	int highlight, idx_next;
	boolean_t page_scroll = B_FALSE;

	if ((!scroll->enabled) || (r->hdl == NULL)) {
		return;
	}

	if ((highlight = scroll->highlight) == -1) {
		highlight = 0;
	}

	if (scroll_type == SCROLL_UP) {
		if ((idx_next = highlight - 1) < 0) {
			return;
		}

		if (idx_next < scroll->page_start) {
			scroll->page_start--;
			page_scroll = B_TRUE;
		}
	} else if (scroll_type == SCROLL_DOWN) {
		if ((idx_next = highlight + 1) >= r->nlines_total) {
			return;
		}

		if (((idx_next - scroll->page_start) % r->nlines_scr) == 0) {
			scroll->page_start++;
			page_scroll = B_TRUE;
		}
	} else {
		return;
	}

	if (page_scroll) {
		reg_hidden_show(r, scroll->page_start);
	}

	reg_idx_lowlight(r, highlight);
	reg_idx_highlight(r, idx_next);
	scroll->highlight = idx_next;
	reg_refresh(r);
}

/*
 * Show the 'scrolling reg'.
 */
void
reg_scroll_show(win_reg_t *r, void *lines, int nreqs,
    void (*str_build_func)(char *, int, int, void *))
{
	int highlight, i, start, end;
	char content[WIN_LINECHAR_MAX];

	highlight = r->scroll.highlight;
	if (highlight != -1) {
		if (highlight >= r->nlines_total) {
			highlight = r->nlines_total - 1;
		}

		if (highlight >= r->scroll.page_start) {
			if ((i = ((highlight - r->scroll.page_start) /
			    r->nlines_scr)) != 0) {
				r->scroll.page_start += r->nlines_scr * i;
			}
		} else {
			r->scroll.page_start =
			    (highlight / r->nlines_scr) *
			    r->nlines_scr;
		}

		start = r->scroll.page_start;
		i = MIN(nreqs, r->nlines_scr);
		if ((end = start + i) > r->nlines_total) {
			end = r->nlines_total;
		}
	} else {
		highlight = 0;
		start = 0;
		end = MIN(nreqs, r->nlines_scr);
	}

	for (i = start; i < end; i++) {
		str_build_func(content, sizeof (content), i, lines);
		dump_write("%s\n", content);
		if (i != highlight) {
			reg_line_write(r, i - r->scroll.page_start,
			    ALIGN_LEFT, content);
		}
	}

	if ((highlight >= start) && (highlight < end)) {
		str_build_func(content, sizeof (content), highlight, lines);
		reg_highlight_write(r, highlight - r->scroll.page_start,
		    ALIGN_LEFT, content);
		r->scroll.highlight = highlight;
	}
}

/*
 * Clean up the resources for libcurses.
 */
void
reg_curses_fini(void)
{
	if (s_curses_init) {
		(void) clear();
		(void) refresh();
		(void) endwin();
		(void) fflush(stdout);
		(void) putchar('\r');
		s_curses_init = B_FALSE;
	}
}

/*
 * Initialization for libcurses.
 */
boolean_t
reg_curses_init(boolean_t first_load)
{
	(void) initscr();
	(void) refresh();
	(void) use_default_colors();
	(void) start_color();
	(void) keypad(stdscr, TRUE);
	(void) nonl();
	(void) cbreak();
	(void) noecho();
	(void) curs_set(0);

	getmaxyx(stdscr, g_scr_height, g_scr_width);

	/*
	 * Set a window resize signal handler.
	 */
	(void) signal(SIGWINCH, disp_on_resize);
	s_curses_init = B_TRUE;

	if ((g_scr_height < 24 || g_scr_width < 80)) {
		if (!first_load) {
			(void) mvwprintw(stdscr, 0, 0,
			    "Terminal size is too small.");
			(void) mvwprintw(stdscr, 1, 0,
			    "Please resize it to 80x24 or larger.");
			(void) refresh();
		} else {
			reg_curses_fini();
			stderr_print("Terminal size is too small "
			    "(resize it to 80x24 or larger).\n");
		}

		dump_write("\n%s\n", "Terminal size is too small.");
		dump_write("%s\n", "Please resize it to 80x24 or larger.");
		return (B_FALSE);
	}

	return (B_TRUE);
}
