/* unix/ui.c 
	vi:ts=3 sw=3:
 */

/* Set terminal discipline to non blocking io and such.
 */
/* $Id: ui.c,v 4.26 1995/09/17 23:26:46 espie Exp espie $
 * $Log: ui.c,v $
 * Revision 4.26  1995/09/17 23:26:46  espie
 * *** empty log message ***
 *
 * Revision 4.25  1995/09/16 19:04:13  espie
 * *** empty log message ***
 *
 * Revision 4.24  1995/09/03 21:54:41  espie
 * Added pseudo-monaural mode.
 *
 * Revision 4.23  1995/09/03 15:50:33  espie
 * *** empty log message ***
 *
 * Revision 4.22  1995/09/03 13:40:44  espie
 * *** empty log message ***
 *
 * Revision 4.21  1995/09/02 22:21:42  espie
 * sync_audio fixed.
 * display_pattern updated.
 * memory allocation fixed.
 *
 * Revision 4.20  1995/08/31 13:32:18  espie
 * *** empty log message ***
 *
 * Revision 4.19  1995/08/27 18:44:20  espie
 * *** empty log message ***
 *
 * Revision 4.18  1995/07/04 08:37:29  espie
 * *** empty log message ***
 *
 * Revision 4.17  1995/06/01  16:36:37  espie
 * Paranoid about song title.
 *
 * Revision 4.16  1995/05/15  12:20:16  espie
 * *** empty log message ***
 *
 * Revision 4.15  1995/05/12  20:41:31  espie
 * Added audio_ui call.
 *
 * Revision 4.14  1995/05/12  13:53:47  espie
 * New synchronization.
 *
 * Revision 4.13  1995/05/11  12:05:05  espie
 * Old code trimmed away.
 *
 * Revision 4.12  1995/03/17  00:32:53  espie
 * PREF XTERM code.
 *
 * Revision 4.11  1995/03/11  23:06:51  espie
 * Display everything in titlebars for xterms.
 *
 * Revision 4.10  1995/03/06  23:36:30  espie
 * Proper color patch.
 *
 * Revision 4.9  1995/03/03  14:24:40  espie
 * Color fixed.
 *
 * Revision 4.8  1995/02/27  14:26:34  espie
 * Cleaned up by Rolf Grossmann.
 *
 * Revision 4.7  1995/02/26  23:08:55  espie
 * POSIX_CONFORMANT.
 *
 * Revision 4.6  1995/02/24  15:40:38  espie
 * Removed all dependencies from show (cached PREF_SHOW) and
 * fixed a small display bug therefore.
 *
 * Revision 4.5  1995/02/21  17:59:26  espie
 * Internal problem: bad log with rcs.
 *
 * Revision 4.4  1995/02/08  13:47:35  espie
 * *** empty log message ***
 *
 * Revision 4.3  1995/01/28  09:56:53  espie
 * Port to FreeBSD/NeXTstep.
 *
 * Revision 4.2  1995/01/28  09:23:38  espie
 * Support for old bsd io discipline (NeXT)
 *
 *
 * Revision 4.0  1994/01/11  18:02:31  espie
 * Major change: lots of new calls.
 * Stupid termio bug: shouldn't restore term to sanity if we don't
 * know what sanity is. For instance, if we haven't modified anything.
 * cflags interpreted correctly.
 * Hsavolai fix.
 * Added bg/fg test.
 */


#include <signal.h>
#include <ctype.h>

#include "defs.h"
#include "extern.h"
#include "tags.h"
#include "prefs.h"

extern char *VERSION;

#ifdef OSK
#include <sgstat.h>
typedef struct sgbug TERM_SETUP;
#else
#ifndef IOCTL_IS_REDUNDANT
#include <sys/ioctl.h> 
#endif
#endif

#ifdef USE_TERMIOS
typedef struct termios TERM_SETUP;
#endif

#ifdef USE_SGTTY
typedef struct sgttyb TERM_SETUP;
#endif

#ifdef USE_TERMIOS
#include <sys/termios.h>	/* this should work on all posix hosts */
#endif
#ifdef USE_SGTTY				/* only NeXt does, currently */
#include <sgtty.h>
#include <fcntl.h>
#endif


#ifdef __hpux
#include <sys/bsdtty.h>
#endif

LOCAL void nonblocking_io P((void));
LOCAL void sane_tty P((void));

LOCAL void (*INIT)P((void)) = nonblocking_io;


/* poor man's timer */
LOCAL int current_pattern;
LOCAL int count_pattern, count_song;
#define SMALL_DELAY 75

LOCAL TERM_SETUP sanity;
LOCAL TERM_SETUP *psanity = 0;

LOCAL int is_fg;

/* signal handler */

LOCAL void goodbye(sig)
int sig;
   {
   static char buffer[25];
	char *pter = buffer;

	if (get_pref_scalar(PREF_COLOR))
		pter = write_color(pter, 0);
	sprintf(pter, "Signal %d", sig);
   end_all(pter);
   }

LOCAL void abort_this(sig)
int sig;
   {
   end_all("Abort");
   }

#ifdef SIGTSTP
LOCAL void suspend(sig)
int sig;
   {
	static char buffer[25];
	char *buf = buffer;
	
	if (get_pref_scalar(PREF_COLOR))
		buf = write_color(buf, 0);
	*buf = 0;
	puts(buf);
   fflush(stdout);
   sane_tty();
   signal(SIGTSTP, SIG_DFL);
   kill(0, SIGTSTP);
   }
#endif

int run_in_fg()
   {
   static int val;

		/* this should work on every unix */
	if (!isatty(fileno(stdin)) || !isatty(fileno(stdout)))
		return FALSE;

#ifdef OSK
	return TRUE;
#else
   /* real check for running in foreground */
   if (ioctl(fileno(stdin), TIOCGPGRP, &val))
      return FALSE;
#ifdef IS_POSIX
   if (val == getpgrp())
#else
   if (val == getpgrp(0))
#endif
      return TRUE;
   else
      return FALSE;
#endif
   }

/* if_fg_sane_tty():
 * restore tty modes, _only_ if running in foreground
 */
LOCAL void if_fg_sane_tty()
	{
	if (run_in_fg())
		sane_tty();
	}


LOCAL void switch_mode()
   {
	TERM_SETUP zap;

#ifdef SIGTSTP
   signal(SIGTSTP, suspend);
#endif
#ifndef OSK
   signal(SIGCONT, switch_mode);
#endif
   signal(SIGINT, goodbye);
   signal(SIGQUIT, goodbye);
   signal(SIGUSR1, abort_this);

   if (run_in_fg())
      {
#ifdef USE_SGTTY
		int tty;

      tty = fileno(stdin);
      fcntl(tty, F_SETFL, fcntl(tty, F_GETFL, 0) | FNDELAY);
      ioctl(tty, TIOCGETP, &zap);
      zap.sg_flags |= CBREAK;
      zap.sg_flags &= ~ECHO;
      ioctl(tty, TIOCSETP, &zap);
#endif
#ifdef USE_TERMIOS
		tcgetattr(fileno(stdin), &zap);
      zap.c_cc[VMIN] = 0;     /* can't work with old */
      zap.c_cc[VTIME] = 0; /* FreeBSD versions    */
      zap.c_lflag &= ~(ICANON|ECHO|ECHONL);
      tcsetattr(fileno(stdin), TCSADRAIN, &zap);
#endif
#ifdef OSK
      _gs_opt(fileno(stdin), &zap);

      zap.sg_pause = 0;    /* no pause at end of page */
      zap.sg_backsp = 0;     /* nondestructive backspace */
      zap.sg_delete = 0; /* no delete sequence */
      zap.sg_echo = 0; /* don't echo chars */
      zap.sg_dlnch = 0xff; /* ^X delete line character */
      zap.sg_rlnch = 0xff; /* ^D reprint line character */
      zap.sg_dulnch = 0xff; /* ^A duplicate line character */
      zap.sg_psch = 0xff; /* ^W pause character */
      zap.sg_eofch = 0xff; /* escape key in OS-9 */
      zap.sg_kbich = 0xff; /* ^C interrupt character */
      zap.sg_kbach = 0xff; /* ^E abort character */
      /* Note ^S and ^Q are disabled! */
      zap.sg_xon = 0xff;   /* ^Q XON */
      zap.sg_xoff = 0xff;  /* ^S XOFF */

      _ss_opt(fileno(stdin), &zap);
#endif

      is_fg = TRUE;
      }
   else
      is_fg = FALSE;
   }

/* nonblocking_io():
 * try to setup the keyboard to non blocking io
 */
LOCAL void nonblocking_io()
   {


#if 0 /* BROKEN */
   /* try to renice our own process to get more cpu time */
   if (nice(-15) == -1)
      nice(0);
#endif


   if (!psanity)
      {
      psanity = &sanity;
#ifdef USE_SGTTY
      ioctl(fileno(stdin), TIOCGETP, psanity);
#endif
#ifdef USE_TERMIOS
      tcgetattr(fileno(stdin), psanity);
#endif
#ifdef OSK
		_gs_opt(fileno(stdin), psanity);
#endif
      }
   switch_mode();
   at_end(if_fg_sane_tty);
   }


/* sane_tty():
 * restores everything to a sane state before returning to shell */
LOCAL void sane_tty()
   {
#ifdef USE_SGTTY
   ioctl(fileno(stdin), TIOCSETP, psanity);
#endif
#ifdef USE_TERMIOS
   tcsetattr(fileno(stdin), TCSADRAIN, psanity);
#endif
#ifdef OSK
	_ss_opt(fileno(stdin), psanity);
#endif
   }

LOCAL int may_getchar()
   {
   char buffer;

   INIT_ONCE;

   if (run_in_fg() && !is_fg)
      switch_mode();
#ifdef OSK
	if (run_in_fg() && (_gs_rdy(fileno(stdin)) > 0))
		{
		read(fileno(stdin), &buffer, 1);
		return buffer;
		}
#else
   if (run_in_fg() && read(fileno(stdin), &buffer, 1))
      return buffer;
#endif
   return EOF;
   }

LOCAL struct tag result[2];

struct tag *get_ui()
   {
	int c;

   result[0].type = TAG_END;
   result[1].type = TAG_END;
   count_pattern++;
   count_song++;
   switch(c = may_getchar())
      {
   case 'n':
      result[0].type = UI_NEXT_SONG;
      break;
   case 'p':
      if (count_song > SMALL_DELAY)
         result[0].type = UI_RESTART;
      else
         result[0].type = UI_PREVIOUS_SONG;
      count_song = 0;
      break;
   case 'x':
   case 'e':
   case 'q':
      result[0].type = UI_QUIT;
      break;
	case 'm':
		set_mix(100);
		break;
	case 'M':
		set_mix(30);
		break;
   case 's':
      result[0].type = UI_SET_BPM;
      result[0].data.scalar = 50;
      break;
   case 'S':
      result[0].type = UI_SET_BPM;
      result[0].data.scalar = 60;
      break;
   case '>':
      result[0].type = UI_JUMP_TO_PATTERN;
      result[0].data.scalar = current_pattern + 1;
      break;
   case '<':
      result[0].type = UI_JUMP_TO_PATTERN;
      result[0].data.scalar = current_pattern;
      if (count_pattern < SMALL_DELAY)
         result[0].data.scalar--;
      break;
   case '?':
		set_pref_scalar(PREF_SHOW, !get_pref_scalar(PREF_SHOW));
		if (get_pref_scalar(PREF_SHOW))
			putchar('\n');
      break;
   default:
		audio_ui(c);
      break;
      }
   return result;
   }
      
         


void notice(s)
char *s;
   {
   fprintf(stderr, "%s\n", s);
   }

void status(s)
char *s;
   {
   if (run_in_fg())
      {
		if (s)
         {
         puts(s);
         }
      else
         putchar('\n');
      }
   }

LOCAL char title[25];
void song_title(s)
char *s;
   {
	int i;

	for (i = 0; *s && i < 24; s++)
		if (isprint(*s))
		    title[i++] = *s;
	title[i] = 0;
	count_song = 0;
   }



LOCAL char scroll_buffer[200];

GENERIC begin_info(title)
char *title;
   {
   if (run_in_fg())
      return scroll_buffer;
   else
      return 0;
   }

void infos(handle, s)
GENERIC handle;
char *s;
   {
   if (handle)
      printf(s);
   }

void info(handle, line)
GENERIC handle;
char *line;
   {
   if (handle)
      puts(line);
   }

void end_info(handle)
GENERIC handle;
   {
   if (handle)
      fflush(stdout);
   }

LOCAL int ntracks;

void set_number_tracks(n)
int n;
	{
	ntracks = n;
	}

LOCAL char scroll_line[2000];

char *new_scroll()
   {
	scroll_line[0] = 0;
	return scroll_line;
   }
   
LOCAL void do_scroll(line)
char *line;
	{
   if (run_in_fg())
		{
      puts((char *)line);
		fflush(stdout);
		}
	free(line);
   }

LOCAL void free_p(line)
GENERIC line;
	{
	free(line);
	}

void scroll(end)
char *end;
   {
	if (run_in_fg())
		{
		int t;
		char *p;

		t = end - scroll_line;
		if (t > 0)
			{
			p = malloc(t+1);
			strncpy(p, scroll_line, t);
			p[t] = 0;
			sync_audio(do_scroll, free_p, p);
			}
		}
	}

struct thingy
	{
	int t0, t1, t2;
	unsigned long u0, u1;
	};

LOCAL void do_display_pattern(param)
GENERIC param;
	{
	struct thingy *thingy = (struct thingy *)param;
	int current, total, real;
	unsigned long uptilnow, totaltime;
	char buf0[50], buf1[50];
	current = thingy->t0;
	total = thingy->t1;
	real = thingy->t2;
	uptilnow = thingy->u0;
	totaltime =thingy->u1;
	free(thingy);

   if (run_in_fg())
		{
	  	if (get_pref_scalar(PREF_XTERM))
	  		{
			if (get_pref_scalar(PREF_SHOW))
				{
				int i;
				for (i = 0; i < ntracks; i++)
					printf("--------------");
				printf("\033]2;V%s %3d/%3d[%3d] %s/%s %s \007\n", VERSION,
					current, total, real, time2string(buf0, uptilnow),
					time2string(buf1, totaltime), title);
				}
			else
				{
				printf("\033]2;V%s %3d/%3d %s/%s %s\007", VERSION, 
					current, total, time2string(buf0, uptilnow),
					time2string(buf1, totaltime), title);
				fflush(stdout);
				}
		  	}
		else
		  	{
			if (get_pref_scalar(PREF_SHOW))
				printf("\n%3d/%3d[%3d] %s\n", current, total, real, title);
			else
				printf("%3d/%3d\b\b\b\b\b\b\b", current, total);
			fflush(stdout); 
		   }
      }
   current_pattern = current;
   count_pattern = 0;
   }

void display_pattern(current, total, real, uptilnow, totaltime)
int current, total, real;
unsigned long uptilnow, totaltime;
   {
	struct thingy *thingy;

	thingy = malloc(sizeof(struct thingy));
	thingy->t0 = current;
	thingy->t1 = total;
	thingy->t2 = real;
	thingy->u0 = uptilnow;
	thingy->u1 = totaltime;
	sync_audio(do_display_pattern, free_p, thingy);
	}

LOCAL void do_display_time(param)
GENERIC param;
	{
	char buffer[50];
	printf("%s\n", time2string(buffer, (unsigned long)param));
	}

LOCAL void do_nuts(p)
GENERIC p;
	{
	}

void display_time(time, check)
unsigned long time;
unsigned long check;
	{
	if (time/1000 != check/1000)
		{
		sync_audio(do_display_time, do_nuts, (GENERIC)check);
		if (time > check)
			sync_audio(do_display_time, do_nuts, (GENERIC)(time - check));
		else
			sync_audio(do_display_time, do_nuts, (GENERIC)(check - time));
		}
	}

int checkbrk()
   {
   return FALSE;
   }
