/*  VT1500 Device Driver for Linux, Version 1.0.5
 *  Copyright (C) 1995  M. Gutschke
 *
 *  At the time of writing my e-mail address is:
 *	Internet: gutschk@uni-muenster.de
 *  My snail mail address is:
 *	Markus Gutschke
 *	Schlage 5a
 *	48268 Greven-Gimbte
 *	Germany
 *  If you like this software, I would appreciate if you sent me a postcard
 *  from your hometown. Under the terms of the GNU general public license
 *  you are free to include this program into (commercial) software
 *  distributions (e.g. putting it onto CD-ROM); nonetheless, I would really
 *  appreciate if you dropped me a short note (sending me a sample copy of
 *  your distribution would be even more appreciated!)
 *
 *  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.
 */

#include <ctype.h>
#include <string.h>

#include "gui.h"
#include "osd.h"
#include "rc.h"
#include "terminal.h"
#include "tv.h"
#include "tvcontrol.h"

enum _colMenuEntry {mBrightness,mSaturation,mContrast,
		    mHue,mRed,mGreen,mBlue,mCount} colMenuEntry = mBrightness;
static int curChannel = 0;
static void (*drwWin)(void) = NULL;
static WINDOW *win = NULL;

void termRefresh(void)
{
  static const char prgTitle[] = "VT1500 Device Driver for Linux, Version "
                                 VERSION;
  static const char menuMsg[] = "S(rc, M(ute, B(ass, T(reb, V(ol, C(ol, "
                                "E(dit, P(AL, N(TSC, Sca(n, SPACE, Q(uit";
  static const char copyMsg1[] = "Copyright "YEAR" by "AUTHOR;
  static const char copyMsg2[] = "Refer to COPYING for copying conditions";
  int selChannel = getChannel();
  int offset,height,margin,cur = 0,i,idx,frq,adj,prgNum,PALNTSC;
  const char *name,*longName;

  werase(stdscr);
  mvprintw(0,(COLS-strlen(prgTitle))/2,(char *)prgTitle);
  height = LINES - 10;
  margin = (COLS-54)/2;
  if (height >= 1) {
    offset = curChannel - height/2;
  loop:
    if (offset < 0) offset = 0;
    idx = offset-1;
    for (i = 0; i < height &&
	 channelInfo(&idx,1,1,&name,&longName,&frq,&adj,
		     &prgNum,&PALNTSC) == 0; i++) {
      if (i + offset == selChannel)
	standout();
      mvprintw(3+i,margin+2,"%50s","");
      mvprintw(3+i,margin+2,"    %3d%c%d.%dMhz %3s %-5s  --  \"%s%s\"",
	       frq/10,adj >= 0 ? '+' : '-',abs(adj)/10,abs(adj)%10,name,
	       PALNTSC?PALNTSC<0?"Radio":"PAL":"NTSC",
	       *longName ? "" : "Channel ",*longName ? longName : name);
      if (prgNum >= 0)
	mvprintw(3+i,margin+2,"%3d",prgNum);
      standend();
      if (idx == curChannel) cur = i; }
    if (i < height && (height-i) < offset) {
      offset -= height-i;
      goto loop; }
    mvprintw(cur+3,margin,">"); mvprintw(cur+3,margin+53,"<"); }
  mvprintw(LINES-5,(COLS-strlen(menuMsg))/2,(char *)menuMsg);
  mvprintw(LINES-2,(COLS-strlen(copyMsg1))/2,(char *)copyMsg1);
  mvprintw(LINES-1,(COLS-strlen(copyMsg2))/2,(char *)copyMsg2);
  if (win != NULL && drwWin != NULL)
    drwWin();
  refresh();
  return;
}

static void doMute()
{
  int mute = 1-getMute();

  setMute(mute);
  mute = getMute();
  mvprintw(LINES-3,0,"Muting has been %sactivated",mute?"":"de");
  showMute(mute);
  refresh();
  return;
}

static int doTVVCR()
{
  int myTVVCR = getTVVCR();
  int ch;

  for (;;) {
    mvprintw(LINES-3,0,"Select source: %cTV%c <-> %cVCR%c then press <Enter>",
	     myTVVCR?'>':' ',myTVVCR?'<':' ',myTVVCR?' ':'>',myTVVCR?' ':'<');
    showTVVCR(myTVVCR);
    refresh();
    switch (ch = getKey()) {
    case KEY_LEFT:
    case 'B'&0x1F:
    case KEY_RIGHT:
    case 'F'&0x1F:
      myTVVCR = 1-myTVVCR;
      break;
    case 0x0A:
    case 0x0D:
    case KEY_ENTER:
      if (!myTVVCR)
	setVCR();
      else {
	int channel = getChannel(),PALNTSC;
	setTV();
	if (channel < 0) {
	  if (!getMute())
	    doMute();
	  setVGA(1); }
	else {
	  curChannel = channel;
	  channelInfo(&curChannel,1,0,NULL,NULL,NULL,NULL,NULL,&PALNTSC);
	  if (PALNTSC)
	    setPAL();
	  else
	    setNTSC(); } }
      move(LINES-3,0); clrtoeol();
      return(0);
    default:
      move(LINES-3,0); clrtoeol();
      clearScreen();
      return(ch); } }
}

static int doBass()
{
  int bass = getBass();
  int i,ch;

  for (;;) {
    setBass(bass);
    mvprintw(LINES-3,0,"<Bass>   ");
    for (i = 0; i < bass; i++) addch('*');
    for (     ; i <   15; i++) addch('.');
    refresh();
    showBass(bass);
    switch (ch = getKey()) {
    case KEY_LEFT:
    case 'B'&0x1F:
      bass -= 1;
      if (bass < 0) bass = 0;
      break;
    case KEY_RIGHT:
    case 'F'&0x1F:
      bass += 1;
      if (bass > 15) bass = 15;
      break;
    default:
      clearScreen();
      return(ch); } }
}

static int doTreble()
{
  int treble = getTreble();
  int i,ch;

  for (;;) {
    setTreble(treble);
    mvprintw(LINES-3,0,"<Treble> ");
    for (i = 0; i < treble; i++) addch('*');
    for (     ; i <     15; i++) addch('.');
    refresh();
    showTreble(treble);
    switch (ch = getKey()) {
    case KEY_LEFT:
    case 'B'&0x1F:
      treble -= 1;
      if (treble < 0) treble = 0;
      break;
    case KEY_RIGHT:
    case 'F'&0x1F:
     treble += 1;
      if (treble > 15) treble = 15;
      break;
    default:
      clearScreen();
      return(ch); } }
}

static int doVolume()
{
  int volume = getVolume();
  int i,ch;

  for (;;) {
    setVolume(volume);
    mvprintw(LINES-3,0,"<Volume> ");
    for (i = 0; i < volume/2; i++) addch('*');
    for (     ; i <       15; i++) addch('.');
    refresh();
    showVolume(volume/2);
    switch (ch = getKey()) {
    case KEY_LEFT:
    case 'B'&0x1F:
      if (getMute() && (!RCSettings.externalMute || isVisible()))
	doMute();
      volume -= 1;
      if (volume < 0) volume = 0;
      break;
    case KEY_RIGHT:
    case 'F'&0x1F:
      if (getMute() && (!RCSettings.externalMute || isVisible()))
	doMute();
      volume += 1;
      if (volume > 31) volume = 31;
      break;
    default:
      clearScreen();
      return(ch); } }
}

static int settings[mCount];
static void drwColor(void)
{
  werase(win);
  box(win,0,0);
  mvwprintw(win,2,3,"Brightness%4d",settings[mBrightness]);
  mvwprintw(win,3,3,"Saturation%4d",settings[mSaturation]);
  mvwprintw(win,4,3,"Contrast%6d",settings[mContrast]);
  mvwprintw(win,5,3,"Hue%11d",settings[mHue]);
  mvwprintw(win,6,3,"Red%11d",settings[mRed]);
  mvwprintw(win,7,3,"Green%9d",settings[mGreen]);
  mvwprintw(win,8,3,"Blue%10d",settings[mBlue]);
  mvwprintw(win,9,12,"<SPACE>");
  mvwprintw(win,2+colMenuEntry,2,">");
  mvwprintw(win,2+colMenuEntry,17,"<");
  wrefresh(win);
  return;
}

static int doColor()
{
  static void (*update[mCount])() = {setBrightness,setSaturation,setContrast,
				     setHue,setRed,setGreen,setBlue};
  int ch;

  win = subwin(stdscr,11,20,(LINES-6-9)/2,(COLS-20)/2);
  if (win == NULL)
    return(ERR);
  drwWin = drwColor;
  settings[mBrightness] = getBrightness();
  settings[mSaturation] = getSaturation();
  settings[mContrast]   = getContrast();
  settings[mHue]        = getHue();
  settings[mRed]        = getRed();
  settings[mGreen]      = getGreen();
  settings[mBlue]       = getBlue();
  for (;;) {
    drwColor();
    showColor(colMenuEntry,settings[mBrightness],settings[mSaturation],
	      settings[mContrast],settings[mHue],settings[mRed],
	      settings[mGreen],settings[mBlue]);
    switch (ch = getKey()) {
    case KEY_LEFT:
    case 'B'&0x1F:
      if (settings[colMenuEntry] > 0)
	update[colMenuEntry](--settings[colMenuEntry]);
      break;
    case KEY_RIGHT:
    case 'F'&0x1F:
      if (settings[colMenuEntry] < (colMenuEntry == mHue ? 255 : 63))
	update[colMenuEntry](++settings[colMenuEntry]);
      break;
    case KEY_UP:
    case 'P'&0x1F:
    case KEY_PPAGE:
      if (colMenuEntry > 0)
	colMenuEntry--;
      break;
    case KEY_DOWN:
    case 'N'&0x1F:
    case KEY_NPAGE:
    case 'V'&0x1F:
      if (colMenuEntry < mCount-1)
	colMenuEntry++;
      break;
    case KEY_HOME:
    case 'A'&0x1F:
      colMenuEntry = 0;
      break;
    case KEY_END:
    case 'E'&0x1F:
      colMenuEntry = mCount-1;
      break;
    default:
      delwin(win);
      win = NULL;
      drwWin = NULL;
      termRefresh();
      clearScreen();
      return(ch);
    }
  }
}

static int frq,adj,prgNum,PALNTSC;
static const char *name,*ln = NULL;
static char longName[20];
static int line = 0;

static void drwEdit(void)
{
  werase(win);
  box(win,0,0);
  mvwprintw(win,2,3,"Channel %s, Station ",name);
  if (prgNum >= 0) wprintw(win,"%d",prgNum); else wprintw(win,"--");
  mvwprintw(win,3,3,"Frequency %d%c%d.%dMHz",frq/10,adj>=0?'+':'-',abs(adj)/10,
	    abs(adj)%10);
  mvwprintw(win,4,3,"%cPAL%c %cNTSC%c %cRadio%c",
	    PALNTSC>0?'>':' ',PALNTSC>0?'<':' ',
	    !PALNTSC ?'>':' ',!PALNTSC ?'<':' ',
	    PALNTSC<0?'>':' ',PALNTSC<0?'<':' ');
  mvwprintw(win,5,3+10-strlen(longName)/2,"\"%s\"",longName);
  mvwprintw(win,8,3,"Edit:  UP/DN  LF/RT");
  mvwprintw(win,9,3,"      [a..zA..Z0..9]");
  mvwprintw(win,10,12,"<RETURN>");
  mvwprintw(win,2+line,2,">"); mvwprintw(win,2+line,26,"<");
  wrefresh(win);
  return;
}

static int doEdit(void)
{
  int ch;

  win = subwin(stdscr,12,29,(LINES-6-7)/2,(COLS-29)/2);
  if (win == NULL)
    return(ERR);
  if (channelInfo(&curChannel,1,0,&name,&ln,&frq,&adj,&prgNum,&PALNTSC)) {
    delwin(win);
    win = NULL;
    drwWin = NULL;
    return(ERR); }
  strcpy(longName,ln);
  drwWin = drwEdit;
  for (;;) {
    setChannelInfo(curChannel,longName,adj,prgNum,PALNTSC);
    termRefresh();
    switch (ch = getKey()) {
    case KEY_LEFT:
    case 'B'&0x1F:
      switch (line) {
      case 0: if (--prgNum < -1) prgNum = 99; break;
      case 1: if (--adj < -99) adj = -99; break;
      case 2: if (++PALNTSC>1) PALNTSC = 1; break;
      default:break; }
      break;
    case KEY_RIGHT:
    case 'F'&0x1F:
      switch (line) {
      case 0: if (++prgNum > 99) prgNum = -1; break;
      case 1: if (++adj > 99) adj = 99; break;
      case 2: if (--PALNTSC<-1) PALNTSC = -1; break;
      default:break; }
      break;
    case KEY_UP:
    case 'P'&0x1F:
    case KEY_PPAGE:
      if (line) line--;
      break;
    case KEY_DOWN:
    case 'N'&0x1F:
    case KEY_NPAGE:
    case 'V'&0x1F:
      if (line < 3) line++;
      break;
    case KEY_HOME:
    case 'A'&0x1F:
      line = 0;
      break;
    case KEY_END:
    case 'E'&0x1F:
      line = 3;
      break;
    case KEY_DC:
    case KEY_BACKSPACE:
    case '\010':
    case '\177':
      if (*longName)
	strrchr(longName,'\000')[-1] = '\000';
      break;
    default:
      if (isalnum(ch) || ch == ' ') {
	if (strlen(longName) < 19) {
	  char s[2];
	  strcpy(s," "); *s = ch; strcat(longName,s); }
	break; }
      #if 1
      if (ch != KEY_ENTER && ch != '\n' && ch != '\r')
	break;
      #endif
      delwin(win);
      win = NULL;
      drwWin = NULL;
      termRefresh();
      return(ch);
    }
  }
}

static void drwScanMsg(void)
{
  werase(win);
  box(win,0,0);
  mvwprintw(win,2,2,"I will now sweep over the");
  mvwprintw(win,3,2,"entire frequency range;");
  mvwprintw(win,4,2,"mark stations by pressing");
  mvwprintw(win,5,2,"a key or abort with <ESC>");
  mvwprintw(win,7,2,"     C(oarse, F(ine, <ESC>");
  wrefresh(win);
  return;
}

static int frq,stdFrq,idx,prgNum;
static void drwScan(void)
{
  werase(win);
  box(win,0,0);
  mvwprintw(win,2,3,"Channel %s",channelName(frq,&idx));
  channelInfo(&idx,1,0,NULL,&ln,&stdFrq,NULL,&prgNum,NULL);
  mvwprintw(win,3,3,"Frequency %d%c%d.%dMHz",stdFrq/10,
	    frq>=stdFrq?'+':'-',abs(frq-stdFrq)/10,abs(frq-stdFrq)%10);
  mvwprintw(win,5,3,"Press <ESC> to abort");
  mvwprintw(win,6,3,"Any other key to mark");
  wrefresh(win);
  return;
}

static void doScan(void)
{
  extern int halfdelay(int);
  unsigned char bm[16];
  int           oldChannel= curChannel;
  int           isVGA     = !isVisible();
  int           TVVCR     = getTVVCR();
  int   	oldFrq    = getFrq();
  int   	oldVolume = getVolume();
  int   	oldMute   = getMute();
  int           coarse    = 0;

  win = subwin(stdscr,9,29,(LINES-6-7)/2,(COLS-29)/2);
  if (win == NULL)
    return;
  drwWin = drwScanMsg;
  drwScanMsg();
  for (;;)
    switch (getKey()) {
    case '\x1B':
      delwin(win);
      win = NULL;
      drwWin = NULL;
      termRefresh();
      return;
    case 'c':
    case 'C':
      coarse = 1;
      goto proceed;
    case 'f':
    case 'F':
      goto proceed;
    default:
      break; }
 proceed:
  drwWin = drwScan;
  memset(bm,0,sizeof(bm));
  for (idx = -1; !channelInfo(&idx,1,1,NULL,NULL,NULL,NULL,&prgNum,NULL); )
    if (prgNum >= 0)
      bm[prgNum/8] |= 1 << (prgNum%8);
  setVGA(-1);
  setMute(-1);
  setVolume(31);
  halfdelay(coarse ? 12 : 5);
  for (frq = (coarse ? 480 : 380); frq <= 8650; frq += (coarse ? 0 : 10)) {
    static char name[20];
    termRefresh();
    drwScan();
    curChannel = idx;
    if (!coarse && (prgNum >= 0 || frq-stdFrq < -99 || frq-stdFrq > 99))
      continue;
    setFrq(frq);
    sprintf(name,"%dMHz: %s",(frq+5)/10,channelName(frq,NULL));
    showChannel(-1,NULL,name);
    switch (getKey()) {
    case 0x1B:
      goto abort;
    case ERR:
      break;
    default:
      if (prgNum >= 0)
	break;
      for (prgNum = 0; bm[prgNum] == 0xFF; prgNum++);
      for (prgNum *= 8; bm[prgNum/8] & (1 << (prgNum%8)); prgNum++);
      bm[prgNum/8] |= 1 << (prgNum%8);
      setChannelInfo(idx,ln?(char *)ln:NULL,frq-stdFrq,prgNum,getPALNTSC());
      break; }
    if (coarse) {
      idx = curChannel;
      if (channelInfo(&idx,1,1,NULL,NULL,&frq,NULL,NULL,NULL) < 0)
	break; } }
abort:
  delwin(win);
  win = NULL;
  drwWin = NULL;
  curChannel = oldChannel;
  termRefresh();
  halfdelay(50);
  setMute(oldMute);
  setVolume(oldVolume);
  setFrq(oldFrq);
  if (TVVCR) setTV(); else setVCR();
  setVGA(isVGA);
  return;
}

void gui(int useVCR)
{
  int ch,prgNum,numEnter = -1;
  const char *name,*longName;

  getSettings();
  initializeTuner();
  if (useVCR)
    setVCR();
  curChannel = getChannel(); if (curChannel < 0) curChannel = 0;
  if (curChannel >= 0) {
    channelInfo(&curChannel,1,0,&name,&longName,NULL,NULL,&prgNum,NULL);
    showChannel(prgNum,name,longName); }
  initializeTerminal();
  termRefresh();
  for (;;) {
    ch = getKey();
  loop:
    if (!isdigit(ch))
      numEnter = -1;
    move(LINES-3,0); clrtoeol();
    switch(ch) {
    case KEY_F(10):
    case 'G'&0x1F:
    case 'Q':
      return;
    case KEY_F(1):
    case 'H':
    case '?':
      showHelp();
      break;
    case KEY_F(2):
    case 'S':
      if ((ch = doTVVCR()) == KEY_F(2) || ch == 'S')
	break;
      goto loop;
    case KEY_F(3):
    case 'M':
      doMute();
      break;
    case 0x1B:
    case KEY_F(4):
    case ' ':
      setVGA(isVisible());
      break;
    case 'B':
      if ((ch = doBass()) == 'B')
	break;
      goto loop;
    case 'T':
      if ((ch = doTreble()) == 'T')
	break;
      goto loop;
    case 'V':
      if ((ch = doVolume()) == 'V')
	break;
      goto loop;
    case 'C':
      if ((ch = doColor()) == 'C')
	break;
      goto loop;
    case KEY_UP:
    case 'P'&0x1F:
      channelInfo(&curChannel,1,-1,NULL,NULL,NULL,NULL,NULL,NULL);
      termRefresh();
      break;
    case KEY_DOWN:
    case 'N'&0x1F:
      channelInfo(&curChannel,1,1,NULL,NULL,NULL,NULL,NULL,NULL);
      termRefresh();
      break;
    case KEY_LEFT:
    case 'B'&0x1F:
      setChannel(CHPREV);
      curChannel = getChannel(); if (curChannel < 0) curChannel = 0;
      goto updateChannel;
      break;
    case KEY_RIGHT:
    case 'F'&0x1F:
      setChannel(CHNEXT);
      curChannel = getChannel(); if (curChannel < 0) curChannel = 0;
    updateChannel:
      if (curChannel < 0) curChannel = 0;
      else {
	int PALNTSC;
	channelInfo(&curChannel,1,0,&name,&longName,
		    NULL,NULL,&prgNum,&PALNTSC);
	showChannel(prgNum,name,longName);
	setVGA(PALNTSC < 0);
	setTV(); }
      termRefresh();
      break;
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      if (numEnter >= 0 && numEnter < 10) {
	int oldChannel = curChannel;
	numEnter = 10*numEnter+ch-'0';
	if ((curChannel = setChannel(numEnter)) != oldChannel) {
	  numEnter = ch-'0';
	  goto updateChannel; } }
      numEnter = ch-'0';
      curChannel = setChannel(numEnter);
      goto updateChannel;
    case 'E':
    case KEY_ENTER:
    case '\n':
    case '\r':
      if ((ch = doEdit()) == 'E' || ch == KEY_ENTER ||
	  ch == '\n' || ch == '\r')
	break;
      goto loop;
    case 'P':
      if (getChannel() >= 0 && getTVVCR()) {
	const char *longName;
	int adj,prgNum;
	curChannel = getChannel();
	channelInfo(&curChannel,1,0,NULL,&longName,NULL,&adj,&prgNum,NULL);
	setChannelInfo(curChannel,(char *)longName,adj,prgNum,1); }
      else
	setPAL();
      termRefresh();
      break;
    case 'N':
      if (getChannel() >= 0 && getTVVCR()) {
	const char *longName;
	int adj,prgNum;
	curChannel = getChannel();
	channelInfo(&curChannel,1,0,NULL,&longName,NULL,&adj,&prgNum,NULL);
	setChannelInfo(curChannel,(char *)longName,adj,prgNum,0); }
      else
	setNTSC();
      termRefresh();
      break;
    case 'A':
      doScan();
      break;
    default:
      clearScreen();
      break; } }
}
