/* 

    TiMidity -- Experimental MIDI to WAVE converter
    Copyright (C) 1995 Tuukka Toivonen <titoivon@snakemail.hut.fi>

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    ncurses_ctl.c
   
    */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>

#include <ncurses.h>

#include "config.h"
#include "common.h"
#include "instruments.h"
#include "playmidi.h"
#include "output.h"
#include "controls.h"

static void ctl_refresh(void);
static void ctl_help_mode(void);
static void ctl_total_time(int tt);
static void ctl_master_volume(int mv);
static void ctl_file_name(char *name);
static void ctl_current_time(int ct);
static void ctl_note(int v);
static void ctl_program(int ch, int val);
static void ctl_volume(int channel, int val);
static void ctl_expression(int channel, int val);
static void ctl_panning(int channel, int val);
static void ctl_sustain(int channel, int val);
static void ctl_pitch_bend(int channel, int val);
static void ctl_reset(void);
static int ctl_open(int using_stdin, int using_stdout);
static void ctl_close(void);
static int ctl_read(int32 *valp);
static int cmsg(int type, int verbosity_level, char *fmt, ...);

/**********************************************/
/* export the interface functions */

#define ctl ncurses_control_mode

ControlMode ctl= 
{
  "ncurses interface", 'n',
  1,0,0,
  ctl_open, ctl_close, ctl_read, cmsg,
  ctl_refresh, ctl_reset, ctl_file_name, ctl_total_time, ctl_current_time, 
  ctl_note, 
  ctl_master_volume, ctl_program, ctl_volume, 
  ctl_expression, ctl_panning, ctl_sustain, ctl_pitch_bend
};


/***********************************************************************/
/* foreground/background checks disabled since switching to curses */
/* static int in_foreground=1; */
static int ctl_helpmode=0;

static WINDOW *dftwin=0, *msgwin=0;

static void _ctl_refresh(void)
{
  wmove(dftwin, 0,0);
  wrefresh(dftwin);
}

static void ctl_refresh(void)
{
  if (ctl.trace_playing)
    _ctl_refresh();
}

static void ctl_help_mode(void)
{
  static WINDOW *helpwin;
  if (ctl_helpmode)
    {
      ctl_helpmode=0;
      delwin(helpwin);
      touchwin(dftwin);
      _ctl_refresh();
    }
  else
    {
      ctl_helpmode=1;
      /* And here I thought the point of curses was that you could put
	 stuff on windows without having to worry about whether this
	 one is overlapping that or the other way round... */
      helpwin=newwin(2,COLS,0,0);
      wattron(helpwin, A_REVERSE);
      werase(helpwin); 
      waddstr(helpwin, 
	      "V/Up=Louder    b/Left=Skip back      "
	      "n/Next=Next file      r/Home=Restart file");
      wmove(helpwin, 1,0);
      waddstr(helpwin, 
	      "v/Down=Softer  f/Right=Skip forward  "
	      "p/Prev=Previous file  q/End=Quit program");
      wrefresh(helpwin);
    }
}

static void ctl_total_time(int tt)
{
  int mins, secs=tt/play_mode->rate;
  mins=secs/60;
  secs-=mins*60;

  wmove(dftwin, 4,6+6+3);
  wattron(dftwin, A_BOLD);
  wprintw(dftwin, "%3d:%02d", mins, secs);
  wattroff(dftwin, A_BOLD);
  _ctl_refresh();
}

static void ctl_master_volume(int mv)
{
  wmove(dftwin, 4,COLS-5);
  wattron(dftwin, A_BOLD);
  wprintw(dftwin, "%03d %%", mv);
  wattroff(dftwin, A_BOLD);
  _ctl_refresh();
}

static void ctl_file_name(char *name)
{
  wmove(dftwin, 3,6);
  wclrtoeol(dftwin );
  wattron(dftwin, A_BOLD);
  waddstr(dftwin, name);
  wattroff(dftwin, A_BOLD);
  _ctl_refresh();
}

static void ctl_current_time(int ct)
{
  int i,v;
  int mins, secs=ct/play_mode->rate;
  if (!ctl.trace_playing) 
    return;

  mins=secs/60;
  secs-=mins*60;
  wmove(dftwin, 4,6);
  wattron(dftwin, A_BOLD);
  wprintw(dftwin, "%3d:%02d", mins, secs);
  v=0;
  i=voices;
  while (i--)
    if (voice[i].status!=VOICE_FREE) v++;
  wmove(dftwin, 4,48);
  wprintw(dftwin, "%2d", v);
  wattroff(dftwin, A_BOLD);
  _ctl_refresh();
}

static void ctl_note(int v)
{
  int xl;
  if (!ctl.trace_playing) 
    return;
  xl=voice[v].note%(COLS-24);
  wmove(dftwin, 8+voice[v].channel,xl+3);
  switch(voice[v].status)
    {
    case VOICE_DIE:
      waddch(dftwin, ',');
      break;
    case VOICE_FREE: 
      waddch(dftwin, '.');
      break;
    case VOICE_ON:
      wattron(dftwin, A_BOLD);
      waddch(dftwin, '0'+(10*voice[v].velocity)/128); 
      wattroff(dftwin, A_BOLD);
      break;
    case VOICE_OFF:
    case VOICE_SUSTAINED:
      waddch(dftwin, '0'+(10*voice[v].velocity)/128);
      break;
    }
}

static void ctl_program(int ch, int val)
{
  if (!ctl.trace_playing) 
    return;
  wmove(dftwin, 8+ch, COLS-20);
  if (ISDRUMCHANNEL(ch))
    {
      wattron(dftwin, A_BOLD);
      wprintw(dftwin, "%03d", val);
      wattroff(dftwin, A_BOLD);
    }
  else
    wprintw(dftwin, "%03d", val);
}

static void ctl_volume(int channel, int val)
{
  if (!ctl.trace_playing) 
    return;
  wmove(dftwin, 8+channel, COLS-16);
  wprintw(dftwin, "%3d", (val*100)/127);
}

static void ctl_expression(int channel, int val)
{
  if (!ctl.trace_playing) 
    return;
  wmove(dftwin, 8+channel, COLS-12);
  wprintw(dftwin, "%3d", (val*100)/127);
}

static void ctl_panning(int channel, int val)
{
  if (!ctl.trace_playing) 
    return;
  wmove(dftwin, 8+channel, COLS-8);
  if (val==NO_PANNING)
    waddstr(dftwin, "   ");
  else if (val<5)
    waddstr(dftwin, " L ");
  else if (val>123)
    waddstr(dftwin, " R ");
  else if (val>60 && val<68)
    waddstr(dftwin, " C ");
  else
    {
      /* wprintw(dftwin, "%+02d", (100*(val-64))/64); */
      val = (100*(val-64))/64; /* piss on curses */
      if (val<0)
	{
	  waddch(dftwin, '-');
	  val=-val;
	}
      else waddch(dftwin, '+');
      wprintw(dftwin, "%02d", val);
    }
}

static void ctl_sustain(int channel, int val)
{
  if (!ctl.trace_playing) 
    return;
  wmove(dftwin, 8+channel, COLS-4);
  if (val) waddch(dftwin, 'S');
  else waddch(dftwin, ' ');
}

static void ctl_pitch_bend(int channel, int val)
{
  if (!ctl.trace_playing) 
    return;
  wmove(dftwin, 8+channel, COLS-2);
  if (val>0x2000) waddch(dftwin, '+');
  else if (val<0x2000) waddch(dftwin, '-');
  else waddch(dftwin, ' ');
}

static void ctl_reset(void)
{
  int i,j;
  if (!ctl.trace_playing) 
    return;
  for (i=0; i<16; i++)
    {
      wmove(dftwin, 8+i, 3);
      for (j=0; j<COLS-24; j++)
	waddch(dftwin, '.');
      ctl_program(i, channel[i].program);
      ctl_volume(i, channel[i].volume);
      ctl_expression(i, channel[i].expression);
      ctl_panning(i, channel[i].panning);
      ctl_sustain(i, channel[i].sustain);
      ctl_pitch_bend(i, channel[i].pitchbend);
    }
  _ctl_refresh();
}

/***********************************************************************/

/* #define CURSED_REDIR_HACK */

#ifdef CURSED_REDIR_HACK
static SCREEN *oldscr;
#endif

static int ctl_open(int using_stdin, int using_stdout)
{
  int i;
#ifdef CURSED_REDIR_HACK
  FILE *infp=stdin, *outfp=stdout;
  SCREEN *dftscr;

  /* This doesn't work right */
  if (using_stdin && using_stdout)
    {
      infp=outfp=stderr;
      fflush(stderr);
      setvbuf(stderr, 0, _IOFBF, BUFSIZ);
    }
  else if (using_stdout)
    {
      outfp=stderr;
      fflush(stderr);
      setvbuf(stderr, 0, _IOFBF, BUFSIZ);
    }
  else if (using_stdin)
    {
      infp=stdout;
      fflush(stdout);
      setvbuf(stdout, 0, _IOFBF, BUFSIZ);
    }

  dftscr=newterm(0, outfp, infp);
  if (!dftscr)
    return -1;
  oldscr=set_term(dftscr);
  /* dftwin=stdscr; */
#else
  initscr();
#endif

  cbreak();
  noecho();
  nonl();
  nodelay(stdscr, 1);
  scrollok(stdscr, 0);
  idlok(stdscr, 1);
  keypad(stdscr, TRUE);
  ctl.opened=1;

  if (ctl.trace_playing)
    dftwin=stdscr;
  else
    dftwin=newwin(6,COLS,0,0);

  werase(dftwin);
  wmove(dftwin, 0,0);
  waddstr(dftwin, "TiMidity v" TIMID_VERSION);
  wmove(dftwin, 0,COLS-52);
  waddstr(dftwin, "(C) 1995 Tuukka Toivonen <titoivon@snakemail.hut.fi>");
  wmove(dftwin, 1,0);
  waddstr(dftwin, "Press 'h' for help with keys, or 'q' to quit.");
  wmove(dftwin, 3,0);
  waddstr(dftwin, "File:");
  wmove(dftwin, 4,0);
  if (ctl.trace_playing)
    {
      waddstr(dftwin, "Time:");
      wmove(dftwin, 4,6+6+1);
      waddch(dftwin, '/');
      wmove(dftwin, 4,40);
      wprintw(dftwin, "Voices:    / %d", voices);
    }
  else
    {
      waddstr(dftwin, "Playing time:");
    }
  wmove(dftwin, 4,COLS-20);
  waddstr(dftwin, "Master volume:");
  wmove(dftwin, 5,0);
  for (i=0; i<COLS; i++)
    waddch(dftwin, '_');
  if (ctl.trace_playing)
    {
      wmove(dftwin, 6,0);
      waddstr(dftwin, "Ch");
      wmove(dftwin, 6,COLS-20);
      waddstr(dftwin, "Prg Vol Exp Pan S B");
      wmove(dftwin, 7,0);
      for (i=0; i<COLS; i++)
	waddch(dftwin, '-');
      for (i=0; i<16; i++)
	{
	  wmove(dftwin, 8+i, 0);
	  wprintw(dftwin, "%02d", i+1);
	}
    }
  else
    {
      msgwin=newwin(LINES-6,COLS,6,0);
      werase(msgwin);
      scrollok(msgwin, 1);
      wrefresh(msgwin);
    }
  _ctl_refresh();
  
  return 0;
}

static void ctl_close(void)
{
  if (ctl.opened)
    {
      endwin();
      ctl.opened=0;
    }
}

static int ctl_read(int32 *valp)
{
  int c;
  while ((c=getch())!=ERR)
    {
      switch(c)
	{
	case 'h':
	case '?':
	case KEY_F(1):
	  ctl_help_mode();
	  return RC_NONE;
	  
	case 'V':
	case KEY_UP:
	  *valp=10;
	  return RC_CHANGE_VOLUME;
	case 'v':
	case KEY_DOWN:
	  *valp=-10;
	  return RC_CHANGE_VOLUME;
	case 'q':
	case KEY_END:
	  return RC_QUIT;
	case 'n':
	case KEY_NPAGE:
	  return RC_NEXT;
	case 'p':
	case KEY_PPAGE:
	  return RC_REALLY_PREVIOUS;
	case 'r':
	case KEY_HOME:
	  return RC_RESTART;

	case 'f':
	case KEY_RIGHT:
	  *valp=play_mode->rate;
	  return RC_FORWARD;
	case 'b':
	case KEY_LEFT:
	  *valp=play_mode->rate;
	  return RC_BACK;
	  /* case ' ':
	     return RC_TOGGLE_PAUSE; */
	}
    }
  return RC_NONE;
}

static int cmsg(int type, int verbosity_level, char *fmt, ...)
{
  va_list ap;
  if ((type==CMSG_TEXT || type==CMSG_INFO || type==CMSG_WARNING) &&
      ctl.verbosity<verbosity_level)
    return 0;
  va_start(ap, fmt);
  if (!ctl.opened)
    {
      vfprintf(stderr, fmt, ap);
      fprintf(stderr, "\n");
    }
  else if (ctl.trace_playing)
    {
      switch(type)
	{
	  /* Pretty pointless to only have one line for messages, but... */
	case CMSG_WARNING:
	case CMSG_ERROR:
	case CMSG_FATAL:
	  wmove(dftwin, 2,0);
	  wclrtoeol(dftwin);
	  wattron(dftwin, A_REVERSE);
	  vwprintw(dftwin, fmt, ap);
	  wattroff(dftwin, A_REVERSE);
	  _ctl_refresh();
	  if (type==CMSG_WARNING)
	    sleep(1); /* Don't you just _HATE_ it when programs do this... */
	  else
	    sleep(2);
	  wmove(dftwin, 2,0);
	  wclrtoeol(dftwin);
	  _ctl_refresh();
	  break;
	}
    }
  else
    {
      switch(type)
	{
	default:
	  vwprintw(msgwin, fmt, ap);
	  wprintw(msgwin, "\n");
	  wrefresh(msgwin);
	  break;

	case CMSG_WARNING:
	  wattron(msgwin, A_BOLD);
	  vwprintw(msgwin, fmt, ap);
	  wprintw(msgwin, "\n");
	  wattroff(msgwin, A_BOLD);
	  wrefresh(msgwin);
	  break;
	  
	case CMSG_ERROR:
	case CMSG_FATAL:
	  wattron(msgwin, A_REVERSE);
	  vwprintw(msgwin, fmt, ap);
	  wprintw(msgwin, "\n");
	  wattroff(msgwin, A_REVERSE);
	  wrefresh(msgwin);
	  if (type==CMSG_FATAL)
	    sleep(2);
	  break;
	}
    }

  va_end(ap);
  return 0;
}
