/*  MikMod example player
	(c) 1999 Miodrag Vallat and others - see file AUTHORS for
	complete list.

	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 2 of the License, 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; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id: mwindow.c,v 1.3 1999/07/05 04:00:02 miod Exp $

  Some window functions

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#include "display.h"
#include <mikmod.h>
#include "player.h"
#include "mwindow.h"

#define PANEL_HELP    1
#define PANEL_SAMPLE  2
#define PANEL_INSTR   3
#define PANEL_COMMENT 4
#define PANEL_LIST    5
#define PANEL_CONFIG  6

#define PANEL_CNT     7

#ifndef ACS_ULCORNER
#if defined(__OS2__)||defined(__EMX__)
#define BOX_UL '\xDA'
#define BOX_UR '\xBF'
#define BOX_LL '\xC0'
#define BOX_LR '\xD9'
#define BOX_HLINE '\xC4'
#define BOX_VLINE '\xB3'
#else
#define BOX_UL '+'
#define BOX_UR '+'
#define BOX_LL '+'
#define BOX_LR '+'
#define BOX_HLINE '-'
#define BOX_VLINE '|'
#endif
#else
#define BOX_UL ACS_ULCORNER
#define BOX_UR ACS_URCORNER
#define BOX_LL ACS_LLCORNER
#define BOX_LR ACS_LRCORNER
#define BOX_HLINE ACS_HLINE
#define BOX_VLINE ACS_VLINE
#endif

/* text creation buffer */
char storage[STORAGELEN];

static int root_y1=7,root_y2=0; /* size of visible root window partions */
static BOOL curses_on=0,win_quiet=1;
static int cur_panel=0,old_panel=0;
static MWINDOW *panel[PANEL_CNT],*cur_window=NULL;

/* screen size */
static int winx=0,winy=0;
#if !defined(__OS2__)&&!defined(__EMX__)
static BOOL resize=0;
static int cursor_old=0;
#endif

#if defined(__OS2__)||defined(__EMX__)
static	HVIO hvio=0;
static	VIOCURSORINFO viocursorinfo;

static	BYTE clearscreen[2]={0xff,A_NORMAL};
static	BYTE mvattr=A_NORMAL;
#endif

/*========== Display routines */

/* old AIX curses are very limited */
#if defined(AIX) && !defined(mvaddnstr)
void mvaddnstr(int y,int x,char *str,int len)
{
	char buffer[STORAGELEN];
	int l=strlen(str);

	strncpy(buffer,str,len);
	if (l<len)
		while(l<len) buffer[l++]=' ';
	buffer[len]='\0';
	mvaddstr(y,x,buffer);
}
#endif

/* HP-UX curses macros don't work with every cpp */
#if defined __hpux
void getmaxyx_hpux(MWINDOW* win,int* y,int *x)
{
	*y=__getmaxy(win);
	*x=__getmaxx(win);
}
#define getmaxyx(win,y,x) getmaxyx_hpux((win),&(y),&(x))
#endif

#if defined(AIX) && !defined(NCURSES_VERSION) && !defined(getmaxyx)
#define getmaxyx(win,y,x)(y=LINES,x=COLS)
#endif

#if !defined(getmaxyx) && !defined(__OS2__) && !defined(__EMX__)
#define getmaxyx(w,y,x)((y)=getmaxy(w),(x)=getmaxx(w))
#endif

#if defined(__OS2__)||defined(__EMX__)

void clear(void)
{
	/* overwrite entire screen with 0s */
	clearscreen[1]=mvattr;
	VioWrtNCell(clearscreen,winy*winx,0,0,hvio);
}
#define attrset(a) mvattr=(a==A_REVERSE)?A_REVERSE:A_NORMAL
void mvaddnstr(int y,int x,char *str,int len)
{
	char buffer[STORAGELEN];
	int l=strlen(str);

	strncpy(buffer,str,len);
	if (l<len)
		while(l<len) buffer[l++]=' ';
	buffer[len]='\0';

	VioWrtCharStrAtt(buffer,len,y,x,&mvattr,hvio);
}

void mvaddch(int y,int x,char ch)
{
#ifdef __WATCOMC__
	char text[2]={0,0};
	
	text[0]=ch;
#else
	char text[2]={ch,0};
#endif
	VioWrtCharStrAtt(text,1,y,x,&mvattr,hvio);
}

void win_refresh(void){}

void win_cursor_set(BOOL visible)
{
	if (visible) {
		VioSetCurType(&viocursorinfo,hvio);
	} else {
		VIOCURSORINFO tmpviocursorinfo;
		tmpviocursorinfo.yStart=0;
		tmpviocursorinfo.cEnd=0;
		tmpviocursorinfo.cx=viocursorinfo.cx;
		tmpviocursorinfo.attr=-1; /* hide cursor */
		VioSetCurType(&tmpviocursorinfo,hvio);
	}
}

/* init window functions */
void win_init(BOOL quiet)
{
	VIOMODEINFO tmpviomodeinfo;

	win_quiet=quiet;
	if (!win_quiet) {
		/* get old cursor */
		VioGetCurType(&viocursorinfo,hvio);
		win_cursor_set(0);
		/* get video mode info */
		tmpviomodeinfo.cb=sizeof(tmpviomodeinfo);
		VioGetMode(&tmpviomodeinfo,hvio);
		winy=tmpviomodeinfo.row;winx=tmpviomodeinfo.col;

		clear();
		VioSetCurPos(winy-1,0,hvio);

		curses_on=1;
	}
	win_open(0,0,winx,winy,0,NULL);
	win_set_resize(1,NULL);
}

/* clean up */
void win_exit(void)
{
	if (win_quiet || !curses_on) return;

	clear();
	mvaddnstr(winy-2,0,mikversion,winx);
	win_cursor_set(1);
	VioSetCurPos(winy-1,0,hvio);

	curses_on=0;
}

/* clear to end of line on uppermost window */
void win_clrtoeol(int x,int y)
{
	if (cur_window->width-x>0) {
		clearscreen[1]=mvattr;
		VioWrtNCell(clearscreen,cur_window->width-x,
		            cur_window->y+y,cur_window->x+x,hvio);
	}
}

/* clear to end of line on root window */
void win_clrtoeol_root(int x,int y)
{
	clearscreen[1]=mvattr;
	VioWrtNCell(clearscreen,winx-x,y,x,hvio);
}

#else /* #if defined(__OS2__)||defined(__EMX__) */

/* handler for terminal resize events */
RETSIGTYPE sigwinch_handler(int signum)
{
	/* schedule a resizeterm() */
	resize=1;

	signal(SIGWINCH,sigwinch_handler);
}

/* update window */
void win_refresh(void)
{
	if (win_quiet) return;
	refresh();
}

void win_cursor_set(BOOL visible)
{
	if (cursor_old!=ERR) {
		if (visible) curs_set(cursor_old);
		else curs_set(0);
	}
}

static void init_curses(void)
{
	initscr();
	cbreak();
	noecho();
	nonl();
	nodelay(stdscr,TRUE);
#if !defined(AIX) || defined(NCURSES_VERSION)
	timeout(0);
#endif
	keypad(stdscr,TRUE);
	cursor_old=curs_set(0);
	curses_on=1;
}

/* init window functions(e.g. init curses) */
void win_init(BOOL quiet)
{
	win_quiet=quiet;

	if (!win_quiet) {
		init_curses();
		getmaxyx(stdscr,winy,winx);
		signal(SIGWINCH,sigwinch_handler);
	}
	win_open(0,0,winx,winy,0,NULL);
	win_set_resize(1,NULL);
}

/* clean up(e.g. exit curses) */
void win_exit(void)
{
	if (win_quiet || !curses_on) return;

	signal(SIGWINCH,SIG_DFL);
	clear();
	mvaddnstr(winy-2,0,mikversion,winx);
	win_refresh();
	win_cursor_set(1);
	endwin();
	curses_on=0;
}

/* clear to end of line on uppermost window */
void win_clrtoeol(int x,int y)
{
	int i,len=cur_window->width-x;

	if (len>0) {
		for (i=0;i<len;i++) storage[i]=' ';
		storage[i]='\0';
		mvaddnstr(cur_window->y+y,cur_window->x+x,storage,len);
	}
}

/* clear to end of line on root window */
void win_clrtoeol_root(int x,int y)
{
	move(y,x);clrtoeol();
}

#endif

BOOL win_clear_win(MWINDOW *win)
{
	int i;

	if ((win->width>0)&&(win->height>0)) {
		for (i=0;i<win->width;i++) storage[i]=' ';
		storage[i]='\0';
		for (i=0;i<win->height;i++)
			mvaddnstr(win->y+i,win->x,storage,win->width);
	}
	return 1;
}

/* clear uppermost window */
void win_clear(void)
{
	win_clear_win(cur_window);
}

/* clear root window */
void win_clear_root(void)
{
	MWINDOW *win=panel[0];
	int i;
	if ((win->width>0)&&(win->height>0)) {
		for (i=0;i<win->width;i++) storage[i]=' ';
		storage[i]='\0';
		for (i=0;i<win->height && i<root_y1;i++)
			mvaddnstr(win->y+i,win->x,storage,win->width);
		i=win->height-root_y2;
		if (i<0) i=0;
		for (;i<win->height;i++)
			mvaddnstr(win->y+i,win->x,storage,win->width);
	} 
}

void win_box(int x1,int y1,int x2,int y2,char *title)
{
	int i,sx1,sx2,sy1,sy2;

	if (win_quiet) return;

	sx1=x1>=0?x1+1:0;
	sx2=x2<winx?x2-1:winx-1;
	sy1=y1>=root_y1?y1+1:root_y1;
	sy2=y2<winy-root_y2?y2-1:winy-root_y2;

	win_attrset(A_REVERSE);

	if (y2<winy-root_y2) {
		if (x1>=0) mvaddch(y2,x1,BOX_LL);
		if (x2<winx) mvaddch(y2,x2,BOX_LR);

		for (i=sx1;i<=sx2;i++) mvaddch(y2,i,BOX_HLINE);
	}
	if (y1>=root_y1) {
		if (x1>=0) mvaddch(y1,x1,BOX_UL);
		if (x2<winx) mvaddch(y1,x2,BOX_UR);

		i=sx1;
		if (title)
			for (;i<=sx2 && *title;i++)
				mvaddch(y1,i,*title++);
		for (;i<=sx2;i++) mvaddch(y1,i,BOX_HLINE);
	}
	for (i=sy1;i<=sy2;i++) {
		if (x1>=0) mvaddch(i,x1,BOX_VLINE);
		if (x2<winx) mvaddch(i,x2,BOX_VLINE);
	}
	win_attrset(A_NORMAL);
}

/* open new window on panel 'panel' */
MWINDOW* win_panel_open(int dst_panel,int x,int y,int width,int height,BOOL border,char *title)
{
	MWINDOW *win=(MWINDOW*)malloc(sizeof(MWINDOW)),*help;

	int ofs=(border?1:0);

	if (x<ofs) x=ofs;
	if (!dst_panel) { /* root panel */
		if (y<ofs) y=ofs;
		if (y+height>winy-ofs) height=winy-y-ofs;
	} else {
		if (y<ofs+root_y1) y=ofs+root_y1;
		if (y+height>winy-ofs-root_y2) height=winy-y-ofs-root_y2;
	}
	if (x+width>winx-ofs) width=winx-x-ofs;
	if (width<0) width=0;

	if (height<0) height=0;

	if (border &&(dst_panel==cur_panel))
		win_box(x-1,y-1,x+width,y+height,title);

	win->x=x;
	win->y=y;
	win->width=width;
	win->height=height;
	win->border=border;
	win->resize=0;
	if (title) win->title=strdup(title);
	else win->title=NULL;
	win->next=NULL;
	win->repaint=win_clear_win;
	win->handle_key=NULL;
	for (help=panel[dst_panel];help && help->next;help=help->next);
	if (help) help->next=win;
	else panel[dst_panel]=win;
	if (dst_panel==cur_panel)
		cur_window=win;
	return win;
}

/* open new window on current panel */
MWINDOW* win_open(int x,int y,int width,int height,BOOL border,char *title)
{
	return win_panel_open(cur_panel,x,y,width,height,border,title);
}

MWINDOW *win_get_first(int dst_panel)
{
	MWINDOW *win;
	for (win=panel[dst_panel];win && win->next;win=win->next);
	return win;
}

/* set function which sould be called on a repaint request */
void win_set_repaint(WinRepaintFunc func)
{
	cur_window->repaint=func;
}
void win_panel_set_repaint(int panel,WinRepaintFunc func)
{
	win_get_first(panel)->repaint=func;
}

/* set function which sould be called on a key press */
void win_set_handle_key(WinKeyFunc func)
{
	cur_window->handle_key=func;
}
void win_panel_set_handle_key(int panel,WinKeyFunc func)
{
	win_get_first(panel)->handle_key=func;
}

/* should window be automatically resized?
   should a function be called on resize? */
void win_set_resize(BOOL auto_resize,WinResizeFunc func)
{
	cur_window->resize=auto_resize;
	cur_window->handle_resize=func;
}
void win_panel_set_resize(int panel,BOOL auto_resize,WinResizeFunc func)
{
	win_get_first(panel)->resize=auto_resize;
	win_get_first(panel)->handle_resize=func;
}

/* set private data */
void win_set_data(void *data)
{
	cur_window->data=data;
}
void win_panel_set_data(int panel,void *data)
{
	win_get_first(panel)->data=data;
}

void win_do_resize(int dx,int dy,BOOL root)
{
	MWINDOW *win;
	int i=root?0:1;

	if (win_quiet) return;

	for (;i<PANEL_CNT;i++)
		for (win=panel[i];win;win=win->next) {
			if (win->resize) {
				win->width+=dx;
				if (win->width<0) win->width=0;
				win->height+=dy;
				if (win->height<0) win->height=0;
			}
			if (win->handle_resize)
				win->handle_resize(win,dx,dy);
		}
	win_panel_repaint();
}

/* check if a resize was scheduled and do it */
BOOL win_check_resize(void)
{
	if (win_quiet) return 0;

#if !defined(__OS2__)&&!defined(__EMX__)
	/* if a resize was scheduled,do it now */
	if (resize) {
		int oldx,oldy;
#if (NCURSES_VERSION_MAJOR >= 4) && defined(TIOCGWINSZ) && defined(HAVE_NCURSES_RESIZETERM)
		struct winsize ws;

		ws.ws_col=ws.ws_row=0;
		ioctl(0,TIOCGWINSZ,&ws);
		if (ws.ws_col && ws.ws_row) 
			resizeterm(ws.ws_row,ws.ws_col);
#else
		endwin();
		init_curses();
		win_refresh();
#endif
		resize=0;
		oldx=winx;oldy=winy;
		getmaxyx(stdscr,winy,winx);
		win_do_resize(winx-oldx,winy-oldy,1);
		return 1;
	}
#endif
	return 0;
}

static char status_message[MAXWIDTH+2];

static void win_status_repaint(void)
{
	MWINDOW *win=panel[0];
	int i;
	if (win_quiet) return;

	if ((root_y2>1)&&(win->height>1))
		for (i=0;i<win->width;i++) mvaddch(win->height-2,i,BOX_HLINE);
}

/* init the status line(height=0,1,2  0: no status line) */
void win_init_status(int height)
{
	int old_y2=root_y2;

	if (height!=root_y2) {
		root_y2=height<0?0:(height>2?2:height);
#if defined(__OS2__)||defined(__EMX__)
		status_message[0]='\0';
#else
		status_message[0]='\n';
		status_message[1]='\0';
#endif
		win_do_resize(0,old_y2-root_y2,0);
	}
}

/* set the status line */
void win_status(char *msg)
{
	MWINDOW *win=panel[0];
	int len=0;

	if (msg) {
		len=strlen(msg);
		if (len>MAXWIDTH) len=MAXWIDTH;
		strncpy(status_message,msg,len);
	}
#if defined(__OS2__)||defined(__EMX__)
	status_message[len]='\0';
#else
	status_message[len]='\n';
	status_message[len+1]='\0';
#endif

	if (win_quiet) return;
	if ((root_y2>0)&&(win->height>0)) {
		mvaddnstr(win->y+win->height-1,win->x,status_message,win->width);
#if defined(__OS2__)||defined(__EMX__)
		win_clrtoeol_root(win->x+len,win->y+win->height-1);
#endif
	}
}

/* repaint the whole panel */
void win_panel_repaint(void)
{
	if (win_quiet) return;

	clear();
	if (panel[0]->repaint)
		if (!panel[0]->repaint(panel[0])) return;
	for (cur_window=panel[cur_panel];cur_window;cur_window=cur_window->next) {
		if (cur_window->border)
			win_box(cur_window->x-1,cur_window->y-1,
					 cur_window->x+cur_window->width,cur_window->y+cur_window->height,
					 cur_window->title);
		if (cur_window->repaint && cur_window->width>0 && cur_window->height>0)
			if (!cur_window->repaint(cur_window)) return;
	}
	win_status_repaint();
	for (cur_window=panel[cur_panel];cur_window && cur_window->next;cur_window=cur_window->next);
}

/* close uppermost window */
void win_close(void)
{
	if (cur_window) {
		MWINDOW *win;

		for (win=panel[cur_panel];win->next!=cur_window;win=win->next);
		if (cur_window->title) free(cur_window->title);
		free(cur_window);
		win->next=NULL;
		cur_window=win;
		win_panel_repaint();
	}
}

/* close window win */
void win_close_win(MWINDOW *win)
{
	int i;
	MWINDOW *pos;
	
	for (i=0;i<PANEL_CNT;i++)
		for (pos=panel[i];pos;pos=pos->next)
			if (pos==win) {
				if (win==cur_window)
					for (cur_window=panel[i]; cur_window->next!=win;
						 cur_window=cur_window->next);
				for (pos=panel[i]; pos->next!=win; pos=pos->next);
				pos->next=win->next;
				if (win->title) free(win->title);
				free(win);
				if (i==cur_panel) win_panel_repaint();
				return;
			}
}

/* get size of uppermost window */
void win_get_size(int *x,int *y)
{
	*x=cur_window->width;
	*y=cur_window->height;
}

/* get size of whole screen */
void win_get_size_root(int *x,int *y)
{
	*x=panel[0]->width;
	*y=panel[0]->height;
}

/* get maximal size of a new window without a border and therefore the needed
   minimal y position */
void win_get_size_max(int *y,int *width,int *height)
{
	*y=root_y1;
	*width=panel[0]->width;
	*height=panel[0]->height-root_y1-root_y2;
	if (*height<0) *height=0;
}

/* get uppermost window */
MWINDOW *win_get_window(void)
{
	return cur_window;
}

/* print string in current window(cur_window) */
void win_print(int x,int y,char *str)
{
	int len=strlen(str);
	
#if defined(__OS2__)||defined(__EMX__)
	if ((str[len-1]=='\n')&&(str[len-2]=='\r'))
		len--;
#endif

	if ((x>=cur_window->width)||(y>=cur_window->height)||
		((cur_window!=panel[0])&&(y+cur_window->y>=winy-root_y2))||
		(!len)||win_quiet) return;

	if (len+x>cur_window->width) len=cur_window->width-x;
	if ((str[len-1]=='\n')||(str[len-1]=='\r'))
#if !defined(__OS2__)&&!defined(__EMX__)
		if (cur_window->x+cur_window->width<winx)
#endif
		{
			len--;
			win_clrtoeol(x+len,y);
		}
	mvaddnstr(y+cur_window->y,x+cur_window->x,str,len);
}

/* print string on root window */
void win_print_root(int x,int y,char *str)
{
	MWINDOW *win=cur_window;
	
	cur_window=panel[0];
	win_print(x,y,str);
	cur_window=win;
}

/* draw horizontal line(rev: in reverse) */
void win_line(int x1,int x2,int y)
{
	int x;
	
	if ((y<cur_window->height) &&(!win_quiet))
		for (x=x1;x<=x2 && x<cur_window->width;x++)
			mvaddch(y+cur_window->y,x+cur_window->x,BOX_HLINE);
}

/* set attribute for the following output operations */
void win_attrset(int attrs)
{
	attrset(attrs);
}

/* change current panel */
void win_change_panel(int new_panel)
{
	if (new_panel==cur_panel) new_panel=old_panel;
	old_panel=cur_panel;
	if (new_panel!=cur_panel) {
		cur_panel=new_panel;
		for (cur_window=panel[cur_panel];cur_window && cur_window->next;cur_window=cur_window->next);
		win_panel_repaint();
	}
}

/* handle key press(panel change and call of key handler
   of uppermost window),return: was key handled */
BOOL win_handle_key(int ch)
{
	switch (ch) {
		case KEY_F(1):
			win_change_panel(PANEL_HELP);
			break;
		case KEY_F(2):
			win_change_panel(PANEL_SAMPLE);
			break;
		case KEY_F(3):
			win_change_panel(PANEL_INSTR);
			break;
		case KEY_F(4):
#if defined(__OS2__)||defined(__EMX__)
		case KEY_SF(9): /* shift-F9 */
#else
		case KEY_F(19): /* shift-F9 on some curses implementations */
#endif
			win_change_panel(PANEL_COMMENT);
			break;
		case KEY_F(5):
			win_change_panel(PANEL_LIST);
			break;
		case KEY_F(6):
			win_change_panel(PANEL_CONFIG);
			break;
		default:
			if (cur_window->handle_key)
				return cur_window->handle_key(cur_window,ch);
			else
				return 0;
		}
	return 1;
}

/* ex:set ts=4: */
