/*---------------------------------------------------------------------------------
Name               : config.c
Author             : Marvin Raaijmakers
Description        : Reads and writes the configuration files
Date of last change: 20-Jul-2007
History            : 20-Jul-2007 Added support for USB keyboards
                     19-Mar-2006 get_current_keyboard() first checks if
                                 current_keyboard.xml exists
                     26-Jan-2006 Modified read_keyboard_file() so that a keyboard
                                 file without a scancode element is still valid
                     11-Dec-2005 read_key_settings() now skips acpi-hotkeys

    Copyright (C) 2005-2007 Marvin Raaijmakers

    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 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    You can contact me at: marvinr(at)users(dot)sf(dot)net
    (replace (at) by @ and (dot) by .)
-----------------------------------------------------------------------------------*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mxml.h>

#include <string_to_keycode.h>
#include <keytouch-init.h> 

static KTKeySettings *read_key_settings (XmlContent       *keylist_element,
                                         int              *index,
                                         XmlElementType   *key_type,
                                         XmlElementType   *scancode_type,
                                         XmlElementType   *usagecode_type,
                                         XmlElementType   *keycode_type,
                                         XmlAttributeName *key_type_attrname);
static void read_usb_ids (KTKeyboard *keyboard, XmlDocument *document, XmlContent  *keyboard_element);


KTKeySettings
*read_key_settings (XmlContent       *keylist_element,
                    int              *index,
                    XmlElementType   *key_type,
                    XmlElementType   *scancode_type,
                    XmlElementType   *usagecode_type,
                    XmlElementType   *keycode_type,
                    XmlAttributeName *key_type_attrname)
/*
Input:
	keylist_element   - The element to read from.
	index             - Read the index'th key element
	key_type          - The XmlElementType for the key element
	scancode_type     - The XmlElementType for the scancode element
	usagecode_type    - The XmlElementType for the usagecode element
	keycode_type      - The XmlElementType for the keycode element
	key_type_attrname - The XmlAttributeName for the key-type attribute name
Output:
	index - If the value of "key-type" attribute of the *index'th element is
	        "acpi-hotkey". The integer pointed to by index will be the first
	        key element not of key-type "acpi-hotkey" after the *index'th
	        key element.
Returns:
	A pointer to the new KTKeySettings if it was read, otherwise NULL.
Desciption:
	This function reads the key_name and keycode member from the *index'th key
	element that is a child of keylist_element.
*/
{
	XmlContent	*key_element,
			*scancode_element,
			*usagecode_element,
			*keycode_element;
	char		*tmp_string,
			*end_pointer,
			*key_type_string;
	KTKeySettings	*key_settings;
	
	(*index)--;
	do {
		(*index)++;
		key_element = XmlGetElementOfType(XmlElementContentList(keylist_element), key_type, *index);
		if (key_element == NULL)
		{
			return (NULL);
		}
		key_type_string = XmlGetAttributeValue(key_type_attrname, key_element);
	} while (key_type_string != NULL && strcmp(key_type_string, "acpi-hotkey") == EQUAL);
	
	scancode_element = XmlGetElementOfType(XmlElementContentList(key_element), scancode_type, 0);
	usagecode_element = XmlGetElementOfType(XmlElementContentList(key_element), usagecode_type, 0);
	keycode_element = XmlGetElementOfType(XmlElementContentList(key_element), keycode_type, 0);
	if ((scancode_element == NULL && usagecode_element == NULL) || keycode_element == NULL)
	{
		KTError ("The keyboard file contains an incomplete key element.", "");
		exit (EXIT_FAILURE);
	}
	
	key_settings = keytouch_malloc(sizeof(KTKeySettings));
	
	if (scancode_element)
	{
		/* Read the scancode */
		tmp_string = XmlGetElementString(scancode_element, "");
		/* Convert the string of the scancode to a integer */
		key_settings->scancode = strtol(tmp_string, &end_pointer, 0);
		/* If the scancode string is not a valid scancode */
		if (*end_pointer != '\0')
		{
			KTError ("%s is an invalid scancode", tmp_string);
			exit (EXIT_FAILURE);
		}
		XmlFree (tmp_string);
	}
	else
	{
		key_settings->scancode = 0;
	}
	
	if (usagecode_element)
	{
		/* Read the usage code */
		tmp_string = XmlGetElementString(usagecode_element, "");
		/* Convert the string of the usagecode to a integer */
		key_settings->usagecode = strtol(tmp_string, &end_pointer, 0);
		/* If the usage code string is not a valid usage */
		if (*end_pointer != '\0')
		{
			KTError ("%s is an invalid usb-code", tmp_string);
			exit (EXIT_FAILURE);
		}
		XmlFree (tmp_string);
	}
	else
	{
		key_settings->usagecode = 0;
	}
	
	/* Read the string of the keycode */
	tmp_string = XmlGetElementString(keycode_element, "");
	/* Convert the keycode */
	key_settings->keycode = string_to_kernel_keycode (tmp_string);
	/* If the string is not a string for a keycode */
	if (key_settings->keycode == 0)
	{
		KTError ("The keyboard file contains an invalid keycode.", "");
		exit (EXIT_FAILURE);
	}
	XmlFree (tmp_string);
	
	key_settings->next = NULL;
	
	return (key_settings);
}


void
read_usb_ids (KTKeyboard  *keyboard,
              XmlDocument *document,
              XmlContent  *keyboard_element)
/*
Input:
	document         - The document that contains 'keyboard_element'.
	keyboard_element - The element that contains the ID's
Output:
	keyboard - The 'vendor_id' and 'product_id' members will be set to the
	           readen values.
Desciption:
	This function reads the values for the 'vendor_id' and 'product_id' members
	of 'keyboard' from 'keyboard_element'. If one of the values could not be
	read or is invalid, then the 'vendor_id' and 'product_id' members will both
	be set to 0.
*/
{
	XmlContent *element,
	           *vendor_id_elem,
	           *product_id_elem;
	XmlElementType *kb_info_type,
	               *usb_info_type,
	               *vendor_id_type,
	               *product_id_type;
	char *tmp_string, *end_pointer;
	
	keyboard->vendor_id = 0;
	keyboard->product_id = 0;
	
	kb_info_type = XmlGetElementType("keyboard-info", document, FALSE);
	usb_info_type = XmlGetElementType("usb-info", document, FALSE);
	vendor_id_type = XmlGetElementType("vendor-id", document, FALSE);
	product_id_type = XmlGetElementType("product-id", document, FALSE);
	if (kb_info_type && usb_info_type && vendor_id_type && product_id_type)
	{
		/* Get the "keyboard-info" element */
		element = XmlGetElementOfType (XmlElementContentList(keyboard_element), kb_info_type, 0);
		if (element)
		{
			/* Get the "usb-info" element */
			element = XmlGetElementOfType (XmlElementContentList(element), usb_info_type, 0);
			if (element)
			{
				vendor_id_elem = XmlGetElementOfType (XmlElementContentList(element), vendor_id_type, 0);
				product_id_elem = XmlGetElementOfType (XmlElementContentList(element), product_id_type, 0);
				if (vendor_id_elem && product_id_elem)
				{
					/* Read the vendor id */
					tmp_string = XmlGetElementString (vendor_id_elem, "");
					/* Convert it to an integer */
					keyboard->vendor_id = strtol(tmp_string, &end_pointer, 16);
					/* If the string is an invalid hexadecimal number */
					if (*end_pointer != '\0')
					{
						keyboard->vendor_id = 0;
					}
					XmlFree (tmp_string);
					
					if (keyboard->vendor_id)
					{
						/* Read the product id */
						tmp_string = XmlGetElementString (product_id_elem, "");
						/* Convert it to an integer */
						keyboard->product_id = strtol(tmp_string, &end_pointer, 16);
						/* If the string is an invalid hexadecimal number */
						if (*end_pointer != '\0')
						{
							keyboard->product_id = 0;
						}
						XmlFree (tmp_string);
					}
				}
			}
		}
	}
}


void
read_keyboard_file (KTKeySettingsList *key_list,
                    KTKeyboard        *keyboard)
/*
Input:
	keyboard - Contains the name of the keyboard to read the configuration of.
Output:
	key_list - List containing information for every key.
	keyboard - The 'vendor_id' and 'product_id' will be set if these values
	           appear in the keyboard file.
Returns:
	-
Desciption:
	This function reads the keyboard file of the keyboard named as in 'keyboard',
	which means that it for every special function key an entry in key_list and
	fills in the following elements:
	- scancode
	- usagecode
	- keycode
	It also fills in the 'vendor_id' and 'product_id' members of keyboard, if
	these values can be read from the keyboard file.
*/
{
	XmlDocument		*document;
	XmlContent		*keyboard_element,
				*keylist_element;
	XmlElementType		*keyboard_type,
				*keylist_type,
				*key_type,
				*scancode_type,
				*usagecode_type,
				*keycode_type;
	XmlAttributeName	*key_type_attrname;
	KTKeySettings		*key_settings;
	char			*file_name;
	int			count;
	
	file_name = (char *)keytouch_malloc(strlen(KEYBOARD_FILES_DIR "/.")+
	                                    strlen(keyboard->manufacturer)+
	                                    strlen(keyboard->model)+1 );
	strcpy (file_name, KEYBOARD_FILES_DIR "/");
	strcat (file_name, keyboard->model);
	strcat (file_name, ".");
	strcat (file_name, keyboard->manufacturer);
	
	document = XmlParseDocument(file_name);
	if (document == NULL)
	{
		KTError ("'%s' is an invalid keyboard file.", file_name);
		exit (EXIT_FAILURE);
	}
	
	keyboard_type = XmlGetElementType("keyboard", document, FALSE);
	if (keyboard_type == NULL ||
	    (keyboard_element = XmlGetElementOfType(XmlDocumentContentList(document), keyboard_type, 0)) == NULL)
	{
		KTError ("The keyboard file '%s' does not contain a keyboard element.", file_name);
		exit (EXIT_FAILURE);
	}
	
	read_usb_ids (keyboard, document, keyboard_element);
	
	keylist_type = XmlGetElementType("key-list", document, FALSE);
	if (keylist_type == NULL ||
	    (keylist_element =
	       XmlGetElementOfType(XmlElementContentList(keyboard_element), keylist_type, 0)) == NULL)
	{
		KTError ("The keyboard file '%s' does not contain a key-list element.", file_name);
		exit (EXIT_FAILURE);
	}
	
	if ((keycode_type = XmlGetElementType("keycode", document, FALSE)) == NULL ||
	    (key_type = XmlGetElementType("key", document, FALSE)) == NULL)
	{
		KTError ("The keyboard file '%s' is invalid.", file_name);
		exit (EXIT_FAILURE);
	}
	scancode_type = XmlGetElementType("scancode", document, TRUE);
	usagecode_type = XmlGetElementType("usb-code", document, TRUE);
	key_type_attrname = XmlGetAttributeName ("key-type", document, TRUE);
	
	count = 0;
	/* Read the first key of the key settings list */
	key_list->head = key_list->tail = read_key_settings(keylist_element, &count, key_type,
	                                                    scancode_type, usagecode_type, keycode_type,
	                                                    key_type_attrname);
	key_settings = key_list->head;
	/* Read the rest of the key settings list */
	for (count++; key_settings != NULL; count++)
	{
		key_settings = read_key_settings(keylist_element, &count, key_type,
		                                 scancode_type, usagecode_type, keycode_type,
		                                 key_type_attrname);
		key_list->tail->next = key_settings;
		if (key_settings != NULL)
		{
			key_list->tail = key_settings;
		}
	}
	
	free (file_name);
	XmlFreeDocument (document);
}


void
get_current_keyboard (KTKeyboard *keyboard)
/*
Input:
	-
Output:
	keyboard - The manufacturer and model members will be set to the readen
	           strings and the vendor_id and product_id members will be set
	           to 0.
Returns:
	-
Desciption:
	This function reads the name of the current keyboard from
	CURRENT_KEYBOARD_FILE. If CURRENT_KEYBOARD_FILE this function will exit the
	program quietly.
*/
{
	XmlDocument	*document;
	XmlElementType	*current_keyboard_type,
			*model_type,
			*manufacturer_type;
	XmlContent	*current_keyboard_element,
			*model_element,
			*manufacturer_element;
	
	keyboard->vendor_id = 0;
	keyboard->product_id = 0;
	
	/* If current_keyboard.xml does not exist */
	if (access( CURRENT_KEYBOARD_FILE, F_OK ) == -1)
	{
		exit (EXIT_SUCCESS);
	}
	document = XmlParseDocument(CURRENT_KEYBOARD_FILE);
	if (document == NULL)
	{
		KTError ("'%s' is invalid", CURRENT_KEYBOARD_FILE);
		exit (EXIT_FAILURE);
	}
	if ((current_keyboard_type = XmlGetElementType("current-keyboard", document, FALSE)) == NULL ||
	    (model_type = XmlGetElementType("model", document, FALSE)) == NULL ||
	    (manufacturer_type = XmlGetElementType("manufacturer", document, FALSE)) == NULL)
	{
		KTError ("'%s' is invalid", CURRENT_KEYBOARD_FILE);
		exit (EXIT_FAILURE);
	}
	if ((current_keyboard_element =
	      XmlGetElementOfType(XmlDocumentContentList(document), current_keyboard_type, 0)) == NULL)
	{
		KTError ("'%s' is invalid because it has no current-keyboard "
		         "element as the root element", CURRENT_KEYBOARD_FILE);
		exit (EXIT_FAILURE);
	}
	if ((model_element =
	      XmlGetElementOfType(XmlElementContentList(current_keyboard_element), model_type, 0)) == NULL ||
	    (manufacturer_element = 
	      XmlGetElementOfType(XmlElementContentList(current_keyboard_element), manufacturer_type, 0)) == NULL)
	{
		KTError ("'%s' is invalid", CURRENT_KEYBOARD_FILE);
		exit (EXIT_FAILURE);
	}
	keyboard->model = XmlGetElementString(model_element, "");
	keyboard->manufacturer = XmlGetElementString(manufacturer_element, "");
	
	XmlFreeDocument (document);
}
