/* screader - A screen reader using software TTS
   (c)1997 J. Lemmens
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>
#include "scr.h"
#include "tts.h"

int ptr = 0, old_posx, old_posy, follow_cursor = 1, tc_size, punctuation;
int speak_value = 1, auto_speak = 1, line_speak = 1, repeat_mode = 0;
int word_mode = 0, cap_mode = 0, key_pressed = 0;
char *buftts, old_str[255], *termcap, abt_dir[100], last_tts_character[1];
int file_d, tts_number = 0;
char tts_command[255];

extern char *Speak_Digits (char *str, int *len);
extern void getscrinfo ();

/***********************************************************\
* Show_ABT_position - Puts a reverse space on the screen to *
*                     show the position of the ABT.         *
\***********************************************************/

void Show_ABT_position (int y)
{
#ifdef __JABT
   int w, attr_on = 0x70, attr_off = 0x07, x;

   if (scr.posx == 65)
     return;
   w = open ("/dev/vcsa0", O_RDWR);
   for (x = 0; x <= y; x++)
   {
      lseek (w, 3 + 2 * x * scr.cols, 0);
      write (w, &attr_off, 1);
   } /* if */
   lseek (w, 3 + 2 * (y + 1) * scr.cols, 0);
   write (w, &attr_on, 1);
   for (x = y + 2; x <= scr.rows; x++)
   {
      lseek (w, 3 + 2 * x * scr.cols, 0);
      write (w, &attr_off, 1);
   } /* if */
   close (w);
#endif
} /* Show_ABT_position */

/*********************************************************\
* beep - Make a beep in a certain frequency and duration. *
\*********************************************************/

void beep (int x, int dur)
{
#ifdef linux
   int snd;
   
   snd = open ("/dev/console", O_WRONLY);
   ioctl (snd, KDMKTONE, (dur << 16) + x);
   close (snd);
#endif
} /* beep */

/*********************************************************************\
* getline - gets a line from file descriptor and returns its pointer. *
\*********************************************************************/

char *getline (FILE *r)
{
   static char hulp[100];
   int i = -1;

   do
   {
      hulp[++i] = fgetc (r);
      if (feof (r))
         hulp[i] = '\n';
   } while (hulp[i] != '\n');
   hulp[i] = 0;
   return (char *) hulp;
} /* getline */

/*************************************************************\
* Get_TTS_command - Get the TTS-command from the config-file. *
\*************************************************************/

void Get_TTS_command ()
{
   int x = 0;
   FILE *r;

   sprintf (tts_command, "%s/%s", abt_dir, TTS_FILE);
   if (! (r = fopen (tts_command, "r")))
   {
#ifdef __SCREADER
      sprintf (abt_dir, "\7%s", tts_command);
      perror (abt_dir);
      exit (1);
#else
      *tts_command = 0;
      return;
#endif
   } /* if */
   do
   {
      if (feof (r))
      {
         rewind (r);
         tts_number = x = 0;
      } // if
      strcpy (tts_command, getline (r));
      if (*tts_command == 0 || *tts_command == '#')
      {
         x--;
         continue;
      } // if
   } while (tts_number != x++);
   tts_number++;
   fclose (r);
   for (x = 0; tts_command[x] != '#' && tts_command[x] != 0; x++);
   tts_command[x] = 0;
} /* Get_TTS_command */

/****\
* Repeat_str - Search repeated chars and substitute with char/n_times. *
\*****/

char *Repeat_str (char *str, int *len)
{
   char *s;
   int l = 0, n, x;

   s = (char *) malloc (4096);
   bzero (s, (scr.rows + 1) * scr.cols);
   x = -1;
   while (l < *len)
   {
      s[++x] = str[l++];
      if (!isdigit (s[x]) && s[x] != ' ' && s[x] == str[l] && s[x] == str[l + 1])
      {
	 n = l;
	 while (str[l++] == s[x]);
	 sprintf (s, "%s %d times ", s, l-- - n);
	 x = strlen (s) - 1;
      } /* if */
   } /* while */
   *len = strlen (s);
   return s;
} /* Repeat_str */

#ifdef __SCREADER
/*******************************************************************\
* Cap_str - Prefix every capitalized character with the word 'cap'. *
\*******************************************************************/

char *Cap_str (char *str, int *len)
{
   char *s;
   int l = 0, x;
   
   s = malloc (4096);
   bzero (s, (scr.rows + 1) * scr.cols);
   x = 0;
   while (l < *len)
   {
      if (isupper (str[l]))
      {
	 sprintf (s, "%s cap  ", s);
	 x = strlen (s) - 1;
      } /* if */
      s[x++] = str[l++];
   } /* while */
   *len = strlen (s);
   return s;
} /* Cap_str */
#endif 

char *Punctuation (char *str, int *len)
{
   char *s, filename[100];
   int l = -1;

   sprintf (filename, "%s/punctuation.%d.sh", abt_dir, tts_number);
   s = (char *) malloc (4096);
   bzero (s, (scr.rows + 1) * scr.cols);
   while (++l < *len)
   {
      FILE *p;
      char hulp[100];
      uid_t euid;

      if (ispunct (str[l]) && access (filename, R_OK) == 0)
      {
         int x;

         euid = geteuid ();
         seteuid (getuid ());
         sprintf (hulp, "%s \\%c", filename, str[l]);
         p = popen (hulp, "r");
         fgets (hulp, 50, p);
         pclose (p);
         for (x = strlen (hulp) - 3; hulp[x] == ' '; x--);
         hulp[x + 2] = 0;
         strcat (s, " ");
         strcat (s, hulp);
         strcat (s, " ");
         seteuid (euid);
      }
      else
      {
         s[strlen (s)] = str[l];
         s[strlen (s)] = 0;
      } // if
   } /* while */
   *len = strlen (s);
   return s;
} // Punctuation

/*******************************\
* TTS - Text-to-speech handler. *
\*******************************/

void TTS (char *str, int length, int mode)
{
   int sublength;
   char *end, *substr;
   static pid_t pgid;
   FILE *r, *w, *pipe;
   uid_t euid;

   if (fork ())
     return;

   fclose (stdin);
   fclose (stdout);
   fclose (stderr);
   signal (SIGTERM, SIG_DFL);
// kill old speech
   if ((r = fopen (TTS_PID_FILE, "r")))
   {
      fscanf (r, "%d", &pgid);
      fclose (r);
      remove (TTS_PID_FILE);
      killpg (pgid, SIGTERM);

// for the festival TTS
      if (strncmp (tts_command, "festival", 8) == 0)
	system ("killall audsp > /dev/null 2>&1");
   } /* if */

   if (mode == 0)
     raise (SIGTERM);

// save pid
   w = fopen (TTS_PID_FILE, "w");
   fprintf (w, "%d\n", pgid = setsid ());
   fclose (w);

// process string str
#ifdef __SCREADER
   if (! speak_value)
     str = Speak_Digits (str, &length);
   if (repeat_mode)
#endif
     str = Repeat_str (str, &length);
#ifdef __SCREADER
   if (cap_mode)
      str = Cap_str (str, &length);
#endif

   euid = geteuid ();
   seteuid (getuid ());
// split str into substrings sothat synths can react on end of string.
   end = str + length;
   while (str < end)
   {
      sublength = 0;
      substr = (char *) malloc (4096);
      while (str[sublength] != '.' && str[sublength] != ',' &&
             str[sublength] != ';' && str[sublength] != ':' &&
             str + sublength < end - 1)
         sublength++;
      sublength++;
      memcpy (substr, str, sublength);
      str += sublength;
      if (punctuation)
         substr = Punctuation (substr, &sublength);
      else
      {
         int l = -1;

         while (++l < sublength)
            if (ispunct (substr[l]))
               substr[l] = ' ';
      } // if
      if (! (pipe = popen (tts_command, "w")))
      {
         remove (TTS_PID_FILE);
         raise (SIGTERM);
      } /* if */
      fwrite (substr, sublength, 1, pipe);
      pclose (pipe);
      free (substr);
   } // while
   if (! (pipe = popen (tts_command, "w")))
   {
      remove (TTS_PID_FILE);
      raise (SIGTERM);
   } /* if */
   fwrite ("\n", 1, 1, pipe);
   pclose (pipe);
   seteuid (euid);
   remove (TTS_PID_FILE);
   raise (SIGTERM);
} /* TTS */

#if defined (__SCREADER) || defined (__XSCREADER)
/***********************************************\
* Speak_Digits - Speak digits instead of value. *
\***********************************************/

char *Speak_Digits (char *str, int *len)
{
   static char hulp[4096];
   char *p, *s;

   p = hulp;
   s = str;
   while ((*len)--)
   {
      *p++ = *s;
      if (isdigit (*s++) || word_mode)
         *p++ = ' ';
   } /* while */
   *len = p - hulp;
   return hulp;
} /* Speak_Digits */
#endif

/*************************************************\
* Speak_Word - Speak words instead of characters. *
\*************************************************/

void Speak_Word (char *buftts, int end)
{
   int start;

   if (end % scr.cols == 0)
     return;
   start = end - 1;
   if (! isalnum (buftts[start]))
   {        
     TTS (buftts + start, 1, 1);
     return;
   } // if
   while (start > 0 && start % scr.cols != scr.cols - 1 && isalnum (buftts[start]))
     start--;
   start++;
   TTS (buftts + start, end - start, 1);
} /* Speak_Word */

#if defined (__SCREADER) || defined (__XSCREADER)
/***************************************************************\
* Auto_TTS - Reads screen and puts through to TTS if necessary. *
\***************************************************************/

inline void Auto_TTS ()
{
   int p, hlp;

   Show_ABT_position (ptr / scr.cols);
   memcpy (old_str, buftts + ptr / scr.cols  * scr.cols, scr.cols);
   old_posx = scr.posx;
   old_posy = scr.posy;
   scr = getstat ();
   if (getscr (0, 0, scr.cols, scr.rows, buftts, SCR_TEXT)   == NULL)
   {
      FILE *w = fopen ("/dev/console", "w");
      beep (TOGGLE_OFF, DURATION);
      fputs ("An buffer-overflow has occurred!\n\r\n\r", w);
      fclose (w);
      raise (SIGQUIT);
   } /* if */
   if (follow_cursor && (old_posx != scr.posx || old_posy != scr.posy))
     ptr = scr.posy * scr.cols + scr.posx;
   if (line_speak && ! key_pressed &&
       memcmp (old_str, buftts + ptr / scr.cols * scr.cols, scr.cols) != 0)
     TTS (buftts + ptr / scr.cols * scr.cols, scr.cols, 1);
   if (word_mode && (scr.posx != old_posx || scr.posy != old_posy) &&
       // fout als cursor regel naar beneden gaat
       !isalnum (buftts[scr.posy * scr.cols + scr.posx - 1]))
   {
      p   = ptr;
      ptr = old_posy * scr.cols + old_posx - 1;
      hlp = punctuation;
      punctuation = 1;
      Speak_Word (buftts, ptr);
      punctuation = hlp;
      ptr = p;
   } /* if */
   if (auto_speak && ! word_mode)
   {
      if (old_posx + 1 == scr.posx)
	if (isspace (buftts[scr.posy * scr.cols + scr.posx - 1]))
	  TTS ("space", 5, 1);
      else
      {
	hlp = punctuation;
	punctuation = 1;
	TTS (buftts + scr.posy * scr.cols + scr.posx - 1, 1, 1);
	punctuation = hlp;
      } // if
      if (old_posx - 1 == scr.posx)
	if (isspace (*last_tts_character))
	  TTS ("space", 5, 1);
      else
      {
	hlp = punctuation;
	punctuation = 1;
	TTS (last_tts_character, 1, 1);
	punctuation = hlp;
      } // if
   } // if
   key_pressed = 0;

   // save last character
   *last_tts_character = buftts[scr.posy * scr.cols + scr.posx - 1];
} /* Auto_TTS */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
void inline top_of_screen ()
{
   ptr = 0;
   TTS ("top of screen", 13, 1);
} /* top_of_screen */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
void inline end_of_screen ()
{
   ptr = scr.rows * scr.cols - 1;
   TTS ("end of screen", 13, 1);
} /* end_of_screen */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
inline void space_left ()
{
   while (buftts[ptr] == ' ')
   {
      if (--ptr < 0)
      {
	 top_of_screen ();
	 return;
      } /* if */
      if (ptr % scr.cols == scr.cols - 1)
	beep (TOGGLE_ON, DURATION);
   } /* while */
} /* space_left */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
inline void space_right ()
{
   while (buftts[ptr] == ' ')
   {
      if (++ptr >= scr.rows * scr.cols)
      {
	 end_of_screen ();
	 return;
      } /* if */
      if (ptr % scr.cols == 0)
	beep (TOGGLE_ON, DURATION);
   } /* while */
} /* space_right */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
inline void begin_of_word ()
{
   while (isalnum (buftts[ptr - 1]) && ptr % scr.cols != 0)
     if (--ptr < 0)
   {
      top_of_screen ();
      return;
   } /* if */
} /* begin_of_word */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
inline void end_of_word ()
{
   while (isalnum (buftts[ptr + 1]) && ptr % scr.cols != scr.cols - 1)
     if (++ptr >= scr.rows * scr.cols)
   {
      end_of_screen ();
      return;
   } /* if */
} /* end_of_word */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
/******************************************\
* Do_TTS_Action - Handle the TTS requests. *
\******************************************/

int Do_TTS_Action (char *ibuf, int ilen)
{
   char hulp[255];
   static int key_click = 0, direct_mode = 0;
   int hlp;
   
   if (key_click)
     beep (2000, 10);
   if (! direct_mode)
     if (ilen != 2 || *ibuf != 0x1b)
       return -1;
   
   key_pressed = 1;
   switch (ibuf[1 - direct_mode])
   {
/* to build in
 speak out attributes
 speak out light bars
 when number contain a hyphen, speak digits
 helpscreen in screader
*/
    case 'a': /* auto_speak */
      auto_speak = 1 - auto_speak;
      if (auto_speak)
	beep (TOGGLE_ON, DURATION);
      else
	beep (TOGGLE_OFF, DURATION);
      break;
    case 'b': /* begin of line */
      ptr = ptr / scr.cols * scr.cols;
      if (line_speak)
	TTS (buftts + ptr /scr.cols * scr.cols, scr.cols, 1);
      break;
    case 'c': /* speak line containing cursor */
      TTS (buftts + scr.posy * scr.cols, scr.cols, 1);
      break;
    case 'd': /* down */
      if ((ptr += scr.cols) >= scr.cols * scr.rows)
      {
	 ptr = scr.cols * scr.rows - 1;
	 TTS ("end of screen", 13, 1);
      } /* if */
      else
	if (line_speak)
	  TTS (buftts + ptr / scr.cols * scr.cols, scr.cols, 1);
      break;
    case 'e': /* speak char/word under cursor */
      hlp = punctuation;
      punctuation = 1;
      if (word_mode)
	Speak_Word (buftts, scr.posy * scr.cols + scr.posx);
      else
	TTS (buftts + scr.posy * scr.cols + scr.posx, 1, 1);
      punctuation = hlp;
      break;
    case 'f': /* follow cursor */
      if ((follow_cursor = 1 - follow_cursor))
	beep (TOGGLE_ON, DURATION);
      else
      {
	 beep (TOGGLE_OFF, DURATION);
	 Silence ();
      } /* if */
      break;
    case 'g': // bottom
      ptr = (scr.rows - 1) * scr.cols;
      if (line_speak)
	TTS (buftts + ptr, scr.cols, 1);
      break;
    case 'h': /* home */
      ptr = 0;
      if (line_speak)
	TTS (buftts, scr.cols, 1);
      break;
    case 'i': /* line_speak */
      line_speak = 1 - line_speak;
      if (line_speak)
	beep (TOGGLE_ON, DURATION);
      else
	beep (TOGGLE_OFF, DURATION);
      break;
    case 'k': /* key click */
      key_click = 1 - key_click;
      if (key_click)
	beep (TOGGLE_ON, DURATION);
      else
      {
	 beep (TOGGLE_OFF, DURATION);
	 Silence ();
      } /* if */
      break;
    case ',': /* left one word */
      if (isalnum (buftts[ptr]))
	begin_of_word ();
      if (--ptr < 0)
      {
	 top_of_screen ();
	 break;
      } /* if */
      if (ptr % scr.cols == scr.cols - 1)
	beep (TOGGLE_ON, DURATION);
      if (buftts[ptr] == ' ')
	space_left ();
      if (isalnum (buftts[ptr]))
	begin_of_word ();
      hlp = punctuation;
      punctuation = 1;
      if (auto_speak)
	if (isalnum (buftts[ptr]))
	  Speak_Word (buftts, ptr);
      else
	TTS (buftts + ptr, 1, 1);
      punctuation = hlp;
      break;
    case 'l': /* left */
      ptr--;
      if (ptr < 0)
      {
	 ptr = 0;
	 top_of_screen ();
	 break;
      } /* if */
      if ((ptr + 1) % scr.cols == 0)
	beep (TOGGLE_OFF, DURATION);
      hlp = punctuation;
      punctuation = 1;
      if (auto_speak)
	TTS (buftts + ptr, 1, 1);
      punctuation = hlp;
      break;
    case 'm': /* word_mode */
      word_mode = 1 - word_mode;
      if (word_mode)
      {
        line_speak = 0;
	beep (TOGGLE_ON, DURATION);
      }
      else
      {
        line_speak = 1;
	beep (TOGGLE_OFF, DURATION);
      } // if
      break;
    case 'n': /* numbers */
      speak_value = 1 - speak_value;
      if (speak_value)
	beep (TOGGLE_ON, DURATION);
      else
	beep (TOGGLE_OFF, DURATION);
      break;
    case 'o': /* silence */
      beep (TOGGLE_OFF, DURATION);
      Silence ();
/* jos
      {
	 FILE *pipe;

	 pipe = popen (tts_command, "w");
	 fwrite ("", 1, 1, pipe);
	 pclose (pipe);
      }
*/
      break;
    case 'p': /* speak char/word under ptr */
      hlp = punctuation;
      punctuation = 1;
      if (word_mode)
	Speak_Word (buftts, ptr);
      else
	TTS (buftts + ptr, 1, 1);
      punctuation = hlp;
      break;
    case 'q': // punctuation
      if ((punctuation = 1 - punctuation))
	beep (TOGGLE_ON, DURATION);
      else
	beep (TOGGLE_OFF, DURATION);
      break;
    case '.': /* right one word */
      if (isalnum (buftts[ptr]))
	end_of_word ();
      if (++ptr >= scr.rows * scr.cols)
      {
	 end_of_screen ();
	 break;
      } /* if */
      if (ptr % scr.cols == 0)
	beep (TOGGLE_ON, DURATION);
      if (buftts[ptr] == ' ')
	space_right ();
      if (isalnum (buftts[ptr]))
	begin_of_word ();
      hlp = punctuation;
      punctuation = 1;
      if (auto_speak)
	if (isalnum (buftts[ptr]))
	  Speak_Word (buftts, ptr);
      else
	TTS (buftts + ptr, 1, 1);
      punctuation = hlp;
      break;
    case 'r': /* right */
      ptr++;
      if (ptr >= scr.cols * scr.rows)
      {
	 ptr = scr.cols * scr.rows - 1;
	 TTS ("end of screen" , 13, 1);
	 break;
      } /* if */
      if (ptr % scr.cols == 0)
	beep (TOGGLE_OFF, DURATION);
      hlp = punctuation;
      punctuation = 1;
      if (auto_speak)
	TTS (buftts + ptr, 1, 1);
      punctuation = hlp;
      break;
    case 's': /* speak line containing ptr */
      TTS (buftts + ptr / scr.cols * scr.cols, scr.cols, 1);
      break;
    case 'u': /* up */
      if ((ptr -= scr.cols) < 0)
      {
	 top_of_screen ();
      } /* if */
      else
	if (line_speak)
	  TTS (buftts + ptr / scr.cols * scr.cols, scr.cols, 1);
      break;
    case 'w': /* whole screen */
      TTS (buftts, scr.rows * scr.cols, 1);
      break;
    case 'x': /* other tts */
      beep (TOGGLE_ON, DURATION);
      Get_TTS_command ();
      sprintf (hulp, "This is the %s speech synthesizer", tts_command);
      TTS (hulp, strlen (hulp), 1);
      break;
    case 'z': /* suspend / direct mode */
      direct_mode = 1 - direct_mode;
      if (direct_mode)
	beep (TOGGLE_ON, DURATION);
      else
	beep (TOGGLE_OFF, DURATION);
      break;
    case '-': /* repeat_mode */
      repeat_mode = 1 - repeat_mode;
      if (repeat_mode)
	beep (TOGGLE_ON, DURATION);
      else
	beep (TOGGLE_OFF, DURATION);
      break;
    case '\'': /* cap_mode */
      cap_mode = 1 - cap_mode;
      if (cap_mode)
	beep (TOGGLE_ON, DURATION);
      else
	beep (TOGGLE_OFF, DURATION);
      break;
    case '=': /* ptr = cursor */
      ptr = scr.posy * scr.cols + scr.posx;
      break;
    default:
      // logger (" ibuf: %d", ibuf[1]);
      return -1;
   } /* switch */
   return 0;
} /* Do_TTS_Action */
#endif

#if defined (__SCREADER) || defined (__XSCREADER)
/***********************************************\
* Init_TTS - Initialize everything for the tts. *
\***********************************************/

void Init_TTS ()
{
   int d, kdmode;
   struct stat st;
   FILE *r;

   punctuation = 1;
   stat ("/usr/lib/terminfo/v/vt100", &st);
   tc_size = st.st_size;
   termcap = malloc (tc_size);
   r = fopen ("/usr/lib/terminfo/v/vt100", "rb");
   fread (termcap, tc_size, 1, r);
   fclose (r);
   getscrinfo ();
   if ((buftts   = malloc (4096)) == NULL)
   {
      FILE *w = fopen ("/dev/console", "w");
      beep (TOGGLE_OFF, DURATION);
      fprintf (w, "Can't allocate enough memory.\n\r");
      fclose (w);
      raise (SIGKILL);
   } /* if */
   ptr = scr.posy;
   Get_TTS_command ();
   d = open ("/dev/console", O_RDONLY);
   ioctl (d, KDGETMODE, &kdmode);
   close (d);
} /* Init_TTS */
#endif
