/* templates.c
 * 
 * This file handles the parsing of keyboard files, and dealing with the
 * data types such as KEY and KEYBOARD that come along with that.
 */
/* GTKeyboard - A Graphical Keyboard For X
 * Copyright (C) 1999, 2000 David Allen  
 *
 * 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 Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

#define TEMPLATES_C
#include "master.h"

#ifdef PARANOID_DEBUGGING
static void print_tokens(char **toks)
{
     int x=0;
     
     while(toks[x])
     {
          fprintf(Q,"%d: \"%s\" ", x, toks[x]);
          x++;
     }

     fprintf(Q,"\n");
     fflush(Q);
} /* End print_tokens() */
#endif /* PARANOID_DEBUGGING */

void destroy_key_wrapper(GtkWidget *emitter, gpointer data)
{
     fprintf(Q,"destroy_key_wrapper called!\n"); fflush(Q);
     gtkeyboard_destroy_key((KEY *)data);
} /* End destroy_key_wrapper() */

KEY *gtkeyboard_key_copy(KEY *key)
{
     KEY *newkey;

     if(!key)
	  return(NO_KEY);

     newkey = gtkeyboard_new_key(key->lower_case,
				 key->upper_case,
				 key->alt_gr,
				 key->aux_string);
     newkey->code = key->code;
     return(newkey);
} /* End gtkeyboard_key_copy() */

void gtkeyboard_print_key(KEY *input)
{
     if(!input)
     {
          fprintf(Q,"No Key\n");
     }
     else
     {
          fprintf(Q,"KeyCode %d Syms \"%s\" \"%s\" \"%s\" (%ld, %ld, %ld)\n",
                  input->code,
                  XKeysymToString(input->lower_case),
                  XKeysymToString(input->upper_case),
                  XKeysymToString(input->alt_gr),
                  input->lower_case,
                  input->upper_case,
                  input->alt_gr);
     } /* End else */

     fflush(Q);
} /* End gtkeyboard_print_key() */

/* Returns the "keyno"th key in row "row" of keyb 
 * This is allocated memory which must be g_free()'d later.
 * This will ALWAYS return allocated memory - it just might always be
 * filled with NoSymbol
 */
KEY *gtkeyboard_keyboard_get_key(KEYBOARD *keyb, int row, int keyno)
{
     KEY *foobar;
     int index;
     int x, findex = 0;

     foobar = gtkeyboard_new_key(NoSymbol, NoSymbol, NoSymbol, NULL);

     if(row > MAXIMUM_ROWS)
     {
	  fprintf(stderr,"gtkeyboard_keyboard_get_key:  Row out of range.\n");
	  fflush(stderr);
	  return(foobar);
     } /* End if */

     if(!keyb)
     {
	  fprintf(stderr,"gtkeyboard_keyboard_get_key:  Null keyb.\n");
	  fflush(stderr);
	  return(foobar);
     } /* End if */

     x = findex = 0;

     while(x < row)
     {
	  /* Add up the number of keys on all lines preceeding the one we're
	   * looking for
	   */
	  findex += keyb->row_values[x];
	  x++;
     } /* End while */

     index = (findex * 3) + (keyno * 3);

     if(index > ((keyb->keycount * 3)-3))
     {
	  fprintf(stderr,"gtkeyboard_keyboard_get_key():  ");
	  fprintf(stderr,"Illegal index %d of a total keycount of %d (%d).\n",
		  index, keyb->keycount, ((keyb->keycount*3)-3)); 
	  fflush(stderr);
	  return(foobar);
     } /* End if */

     /* Three consecutive KeySyms */
     foobar->lower_case = keyb->syms[index];
     foobar->upper_case = keyb->syms[(index+1)];
     foobar->alt_gr     = keyb->syms[(index+2)];
     foobar->aux_string = (char *)NULL;             /* No auxilliary */

     if(keyb->codes)
	  foobar->code       = keyb->codes[(findex+keyno)];
     else foobar->code       = 0;

     return(foobar);
} /* End gtkeyboard_keyboard_get_key() */

/* Return 1 if we would normally have to press shift to get this key.  
 * Otherwise return 0 
 */
int is_uppercase_key(KEYBOARD *keyb, KeySym i)
{
     int x=0;
     if(!keyb)
     {
#ifdef DEBUGGING
	  fprintf(Q,"is_uppercase_key:  Bad KEYBOARD argument.\n");
	  fflush(Q);
#endif
	  return(0);
     } /* End if */

     /* We need to know if the KeySym in question needs to be logical or'd 
      * with ShiftMask, so we have to determine if, under the current mapping,
      * we would have to press shift or not to get this key.  Since everything
      * is in keyb->syms in groups of 3, (lower case, uppercase, altgr)
      * then if we find it, and it's in one of the 2nd places, (i.e. uppercase)
      * then return 1.  Else 0.  Since everything is grouped by 3's, 
      * if the position % 3 == 1, then it's good.  
      * Lowercase % 3 => 0
      * uppercase % 3 => 1
      * altgr     % 3 => 2
      */

     for(x=0; x<(keyb->keycount * 3); x++)
     {
	  if(keyb->syms[x] == i)
	  {
	       if((x % 3) == 1) return(1);
	       else             return(0);
	  } /* End if */
     } /* End for */

#ifdef DEBUGGING
     fprintf(Q,"is_uppercase_key():  KeySym not found in keyboard.\n");
     fflush(Q);
#endif
     return(0);
} /* End is_uppercase_key() */

void gtkeyboard_key_set_code(KEY *key, KeyCode input)
{
     if(key)
	  key->code = input;
} /* End gtkeyboard_key_set_code */

int gtkeyboard_keyboard_row_count_items(KEYBOARD *input, int rowno)
{
     if(!input)
     {
	  fprintf(stderr,"gtkeyboard_keyboard_row_count_items:  ");
	  fprintf(stderr,"Invalid keyboard passed.\n"); fflush(stderr);
	  return(0);
     } /* End if */
     else return(input->row_values[rowno]);
} /* End gtkeyboard_keyboard_row_count_items */

KEY *gtkeyboard_new_key(const KeySym lower, const KeySym upper, 
			const KeySym altgr, const char *alt)
{
     KEY *somekey        = g_new0_(KEY, 1);
     somekey->lower_case = lower;
     somekey->upper_case = upper;
     somekey->alt_gr     = altgr;
     somekey->code       = 0;
     if(alt)
     {
	  somekey->aux_string = g_strdup_(alt);
     } /* End if */
     else somekey->aux_string = NULL;

     return(somekey);
} /* End gtkeyboard_new_key() */

KEY *gtkeyboard_destroy_key(KEY *input)
{
     if(!input)
     {
          fprintf(Q,"Error:  gtkeyboard_destroy_key:  NULL argument.\n");
          fflush(Q);
          return(NO_KEY);
     } /* End if */

     if(input->aux_string)
     {
	  g_free_(input->aux_string);
	  input->aux_string = (char *)NULL;
     } /* End if */

     g_free_(input);
     input = NO_KEY;     /* Null pointer so it won't be reused */

     return(NO_KEY);
} /* End gtkeyboard_destroy_key() */

KEYBOARD *gtkeyboard_destroy_keyboard(KEYBOARD *input)
{
     if(!input)
     {
	  fprintf(stderr,
		  "gtkeyboard_destroy_keyboard: Cannot destroy NULL ptr.\n");
	  fflush(stderr);
	  return(NO_KEYBOARD);
     } /* End if */

     if(input->syms)
     {
	  g_free_(input->syms);
	  input->syms = NULL;
     } /* End if */

     if(input->name)
     {
	  g_free_(input->name);
	  input->name = NULL;
     } /* End if */

     if(input->codes)
     {
	  g_free_(input->codes);
	  input->codes = NULL;
     } /* End if */

     if(input->modmap) 
     {
          ModmapTable_destroy(input->modmap);
          input->modmap = (ModmapTable *)NULL;
     } /* End if */

     if(input->trans)
     {
          CONDFREE(input->trans->space);
          CONDFREE(input->trans->tab);
          CONDFREE(input->trans->alt_gr);
          CONDFREE(input->trans->alt);
          CONDFREE(input->trans->control);
          CONDFREE(input->trans->shift);
          CONDFREE(input->trans->backspace);
          
          /* Free the parent pointer */
          CONDFREE(input->trans);
     } /* End if */

     g_free_(input);
     input = NO_KEYBOARD;

     return(NO_KEYBOARD);
} /* End gtkeyboard_destroy_keyboard() */

static int get_valid_line(FILE *fp, char *mem, const int maxlen)
{
     mem[0] = '\0';
     
     while(!feof(fp) && mem[0] == '\0')
     {
	  FILE_readline(fp, mem, maxlen);
	  
	  if(mem[0] != '\0')
	  {
	       strcpy(mem, g_strstrip(mem));
	       
	       if((mem[0] && mem[0] == '#') || (mem[0] && mem[0] == '!'))
		    mem[0] = '\0';
	  } /* End if */
     } /* End while */
     
     if(mem[0] == '\0')
	  return(0);

     else return(1);
} /* End get_valid_line() */

/* Parses the contents of a keyboard description file and returns
 * a corresponding KEYBOARD structure.
 */
KEYBOARD *read_keyboard_template(char *filename)
{
     KEYBOARD *keyb = NO_KEYBOARD;
     FILE *fp;
     register int x=0, y=0;
     int line_size = 1024;
     int index = 0;
     char linedata[line_size];
     char warnbuf[1024];
     char **tokens;
     char **tofree;
     char *ptr;

     if(!filename || !file_exists(filename))
     {
          fprintf(stderr,"Error loading keyboard file \"%s\":  ",
                  (filename ? filename : "NULL"));
          fprintf(stderr,"File doesn't exist.");
          fflush(stderr);
	  return(NO_KEYBOARD);
     } /* End if */

     fp = fopen(filename, "r");

     if(!fp)
     {
          gtkeyboard_error(4, "Error:  Couldn't open ",
                           filename, " for reading:  ",
                           g_strerror(errno));
	  return(NO_KEYBOARD);
     } /* End if */

     /* Check if it's a regular file.  This way we can skip symlinks, 
      * and not get hung on directories
      */
     if(!is_regular_file(filename))
     {
          char buffer[4096];
          sprintf(buffer,"The file:\n\"%s\"\nis not a regular file.",filename);

          gtkeyboard_error(1, buffer);

          /* Bail out with an error */
          return(NO_KEYBOARD);
     } /* End else */

#ifdef DEBUGGING
     fprintf(Q,"Reading keyboard template at file \"%s\"\n",filename);
     fflush(Q);
#endif /* DEBUGGING */

     linedata[0] = '\0';

     if(!get_valid_line(fp, linedata, line_size))
     {
          gtkeyboard_error(3, "Error in file ",
                           filename, ":\nCouldn't read header values.\n");
	  fclose(fp);
	  return(NO_KEYBOARD);
     } /* End if */

     keyb = g_new0_(KEYBOARD, 1);
     keyb->modmap = ModmapTable_new();

     tofree = g_strsplit(linedata, " ", -1);
     tokens = tofree;

     keyb->keycount = 0;
     keyb->trans    = g_new_(KeyboardTranslation, 1);

     /* Initialize it's various elements */
     keyb->trans->shift = keyb->trans->backspace = keyb->trans->space = 
          keyb->trans->caps_lock = keyb->trans->control = keyb->trans->tab = 
          keyb->trans->alt = keyb->trans->alt_gr = (char *)NULL;

     for(x=0; x<MAXIMUM_ROWS; x++)
     {
	  if(*tokens)
	       ptr = *tokens++;
	  else ptr = NULL;
	  
	  if(ptr)
	       keyb->row_values[x] = atoi(ptr);
	  else
	  {
	       *tokens = NULL;
	       keyb->row_values[x] = 0;
	  } /* End else */

	  keyb->keycount += keyb->row_values[x];
     } /* End for */
     
     g_strfreev(tofree);     tofree = tokens = NULL;  ptr = NULL;

     /* We now know how many keys we have to allocate, how many lines to read,
      * and all that good stuff.
      *
      * Each key must have 3 syms, (lower case, upper case, and Alt Gr)
      * So allocate 3*keyb->keycount items, and read keyb->keycount lines.
      */
     keyb->syms  = g_new0_(KeySym, (3 * keyb->keycount));
     keyb->codes = g_new0_(KeyCode, keyb->keycount);
     keyb->name  = g_strdup_(filename);  /* Save the name of the keyboard */

     for(x=0; x<keyb->keycount; x++)
     {
	  keyb->codes[x] = 0;  /* Initialize that keycode since we're already
				* paying the price of the loop and it needs
				* to be done.
				*/

	  if(!get_valid_line(fp, linedata, line_size))
	  {
	       fprintf(stderr,"Error reading file %s: Bad line %d.\n",
                       filename, (x+1)); fflush(stderr);
               fflush(stderr);
               gtkeyboard_error(3, "Bad line found in file ",filename,"\n");
	       keyb = gtkeyboard_destroy_keyboard(keyb);
	       fclose(fp);
	       return(NO_KEYBOARD);
	  } /* End if */

	  tokens = tofree = g_strsplit(linedata, " ", -1);

#ifdef PARANOID_DEBUGGING
          print_tokens(tofree);
#endif /* PARANOID_DEBUGGING */
	  
	  for(y=0; y<3; y++)
	  {
	       if(*tokens)
		    ptr = *tokens++;
	       else ptr = NULL;

	       index = (x * 3) + y;

	       if(ptr)
	       {
		    /* Translate a string into a KeySym */
		    keyb->syms[index] = XStringToKeysym(ptr);

		    /* Error check that KeySym */
		    if(!keyb->syms[index] || keyb->syms[index] == NoSymbol)
		    {
			 keyb->syms[index] = NoSymbol;
			 sprintf(warnbuf,
                                 "GTKeyboard Error: Bad sym: \"%s\" ", ptr);
                         gtkeyboard_message(1, warnbuf);
			 sprintf(warnbuf,"in key #%d position %d.\n",x,y);
                         gtkeyboard_message(1, warnbuf);
                         keyb = gtkeyboard_destroy_keyboard(keyb);
                         return(NO_KEYBOARD);
		    } /* End if */
	       } /* End if */
	       else 
	       {
		    /* We had a NULL string pointer.  This is OK, but 
		     * warn the user.
		     */
		    switch(y){
		    case 1: fprintf(stderr,"1st "); break;
		    case 2: fprintf(stderr,"2nd "); break;
		    case 3: fprintf(stderr,"3rd "); break;
		    default: fprintf(stderr,"%dth ",y); break;
		    } /* End switch */

                    sprintf(warnbuf,
                            "No KeySym found for key %d\nPlease check %s %s\n",
                            index, "the format of", filename);

                    gtkeyboard_message(1, warnbuf);

		    /* This kinda sucks */
		    keyb->syms[index] = NoSymbol;
	       } /* End else */
	  } /* End for */

	  if(ptr)
          {
	       ptr = *tokens++;
          } /* End if */

	  /* Grab the KeyCode if it's there */
          keyb->codes[x] = atoi(ptr ? ptr : "0");

          if(ptr)
          {
               ptr = *tokens++;
          } /* End if */
          
          if(ptr && strcmp(ptr, "") != 0)
          {
               fprintf(Q,"Reading proposed mask \"%s\"\n", ptr);
               fflush(Q);
               
               if(!ModmapTable_insert(keyb->modmap, keyb->codes[x], 
                                      mask_name_to_slot_number(ptr)))
               {
                    fprintf(Q,"*** Warning:  Failed to insert %d into %d\n",
                            keyb->codes[x], mask_name_to_slot_number(ptr));
               } /* End if */

               fprintf(Q,"Inserted code %d in slot %d\n",
                       keyb->codes[x], mask_name_to_slot_number(ptr));
               fflush(Q);
          } /* End if */

	  g_strfreev(tofree);
     } /* End for */

     fclose(fp);

     /* Go grab any bang variables from the file. */
     /* special_variables_parse(filename, keyb); */

     sprintf(warnbuf,
             "Successful reading of %s completed: %d total keys.\n",
             (keyb->name ? keyb->name : "keyboard"), keyb->keycount);
     gtkeyboard_message(1, warnbuf);
     
     return(keyb);
} /* End read_keyboard_template() */
