/*  $Id: meaning.c,v 1.45 2005/04/20 06:51:32 marcusva Exp $4~
 *  
 *  This file is part of LingoTeach, the Language Teaching program 
 *  Copyright (C) 2001-2004 Marcus von Appen. All rights reserved.
 *
 *  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 <string.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include "lingoteach.h"
#include "conf.h"
#include "meaning.h"

/* hardcoded file path - this should be made more flexible by time */
#ifdef _WIN32
#define PLAYBASE "%s\\%s\\%s\\%i.ogg"
#else
#define PLAYBASE "%s/%s/%s/%i.ogg"
#endif

/* some static XPaths, which will usually never change */
#define QUERY_FIND  "//meaning[@id='m%i']/translation[@language='%s']/text()"
#define QUERY_DESCR "//meaning[@id='m%i']/description[@language='%s']/text()"
#define QUERY_SOUND "//lang[@id='%s']/speaker/text()"
#define QUERY_TYPE  "//meaning[@id='m%i']/@type"
#define QUERY_PHON  "//meaning[@id='m%i']/translation[@language='%s']/@phonetic"
#define QUERY_IMAGE "//meaning[@id='m%i']/image"

/*********************
 * private functions *
 *********************/

/*
 * searches the word, which matches int meaning, in the language, which is 
 * given as char* language
 * the return value has to be freed
 */
lingchar*
meaning_get_word (luint id, lingchar *language, lessonData *lesson)
{
     xmlXPathObjectPtr ptr = NULL;
     lingchar *query = NULL;
     lingchar *retval = NULL;
     int len = xmlStrlen (QUERY_FIND) + xmlStrlen (language) + sizeof (id);

     /* build the search query */
     query = xmlMalloc ((luint) len);
     if (!query)
	  return NULL;
     xmlStrPrintf (query, len, QUERY_FIND, id, language);
  
     ptr = xmlXPathEvalExpression (query, lesson->x_path);
     xmlFree (query);
     if (!ptr)
	  return NULL;
  
     retval = xmlXPathCastToString (ptr);
     xmlXPathFreeObject (ptr);
     return retval;
}

/*
 * searches the sound matching the meaning id and language
 * the return value has to be freed
 */
lingchar*
meaning_get_sound (luint id, lingchar *language, lingLesson *lesson)
{
     lingchar *query = NULL;
     lingchar *speaker = NULL;
     lingchar *playbase = PLAYBASE;
     lingchar *soundname = NULL;
     xmlXPathObjectPtr ptr = NULL;
     int len = xmlStrlen (QUERY_SOUND) + xmlStrlen (language);

     /* the speaker */
     query = xmlMalloc ((luint) len);
     if (!query)
	  return NULL;
     xmlStrPrintf (query, len, QUERY_SOUND, language);

#ifdef DEBUG
     printf ("Debug: Speaker query: %s\n", query);
#endif

     ptr = xmlXPathEvalExpression (query,
                                   ((lingPrivateConfig *)lesson->config->pdata)->lang);
     xmlFree (query);
     if (!ptr)
	  return NULL;

     speaker = xmlXPathCastToString (ptr);
     xmlXPathFreeObject (ptr);

     len = xmlStrlen (playbase) + xmlStrlen (language) + xmlStrlen (speaker)
             + xmlStrlen (lesson->sound) + sizeof (id);

     soundname = xmlMalloc ((luint) len);
     if (!soundname)
	  return NULL;
     xmlStrPrintf (soundname, len, playbase, language,
                   speaker, lesson->sound, id);
     xmlFree (speaker);

#ifdef DEBUG
     printf ("Debug: Looking for sound:  %s\n", soundname);
#endif

     return soundname;
}

/*
 * returns the type of a meaning
 * the return value has to be freed
 */
lingchar* 
meaning_get_type (luint id, lessonData *lesson)
{
     xmlXPathObjectPtr ptr = NULL;
     lingchar *query = NULL;
     lingchar *retval = NULL;
     int len = xmlStrlen (QUERY_TYPE) + sizeof (id);

     /* build the search query */
     query = xmlMalloc ((luint) len);
     if (!query)
	  return NULL;
     xmlStrPrintf (query, len, QUERY_TYPE, id);

     ptr = xmlXPathEvalExpression (query, lesson->x_path);
     xmlFree (query);
     if (!ptr)
	  return NULL;

     retval = xmlXPathCastToString (ptr);
     xmlXPathFreeObject (ptr);
     return retval;
}

/* 
 * creates a new node tree of meanings
 * the return value has to be freed
 */
xmlNodePtr
meaning_create_xml_node_tree (lingMeaning *meaning, xmlNodePtr parent)
{
     xmlNodePtr trans = NULL;
     xmlNodePtr newone = NULL;
     xmlNodePtr descr = NULL;
     xmlNodePtr image = NULL;
     lingchar *tmp = NULL;
     luint id = 0;
     int i = 0;

     while (meaning)
     {
	  id = meaning->id;
          newone = xmlNewChild (parent, NULL, "meaning", NULL);

	  /* create the id attribute */
	  tmp = xmlMalloc (sizeof (id) + 2);
	  if (!tmp)
	       return NULL; /* leave it to the caller to free all up */
	  xmlStrPrintf (tmp, sizeof (id) + 2, "m%i", id);
	  xmlNewProp (newone, "id", tmp);
	  xmlFree (tmp);
      
	  /* create type attribute */
	  if (meaning->type)
	       xmlNewProp (newone, "type", meaning->type);
	
	  /* add the translations */
	  while (meaning && id == meaning->id)
	  {
	       /* translation */
	       trans = xmlNewTextChild (newone, NULL, "translation", 
					meaning->translation);
	       xmlNewProp (trans, "language", meaning->language);

               /* now add the lingMInfo nodes */
               if (meaning->info)
               {
                    xmlNewProp (trans, "phonetic", meaning->info->phonetic);
                    descr = xmlNewTextChild (newone, NULL, "description",
                                             meaning->info->description);
                    xmlNewProp (descr, "language", meaning->language);

                    /* add images */
                    i = 0;
                    if (meaning->info->images)
                         while (meaning->info->images[i])
                         {
                              image =
                                   xmlNewTextChild (newone, NULL, "image", 
                                                    meaning->info->images[i]);
                              i++;
                         }
               }
	       meaning = meaning->next;
	  }
     }
     return parent;
}


/********************
 * public functions *
 ********************/

/**
 * Creates a new lingMeaning and returns it. 
 * The lingMeaning has to be freed by the user.
 *
 * \return A new, empty lingMeaning.
 */
lingMeaning*
ling_meaning_new (void)
{
     lingMeaning *mn = malloc (sizeof (lingMeaning));
     if (!mn)
	  return NULL;

     mn->translation = NULL;
     mn->lesson = NULL;
     mn->language = NULL;
     mn->type = NULL;
     mn->id = 0;
     mn->info = NULL;
     mn->next = NULL;
     mn->prev = NULL;

     return mn;
}

/**
 * Creates a new lingMInfo and returns it. 
 * The lingMInfo has to be freed by the user.
 *
 * \return A new, empty lingMinfo.
 */
lingMInfo*
ling_meaning_info_new (void)
{
     lingMInfo *info = malloc (sizeof (lingMInfo));
     if (!info)
	  return NULL;
     
     info->description = NULL;
     info->phonetic = NULL;
     info->images = NULL;
     return info;
}

/**
 * Gets a specific lingMeaning from the given lesson.
 * The lingMeaning has to be freed by the user.
 * 
 * \param lesson The lesson to get the meaning from.
 * \param id The id, which should be searched for.
 * \param language The language, which should be used.
 * \return a lingMeaning containing the meaning, which has the given id.
 * If none is found with the given language, the function returns NULL.
 */
lingMeaning*
ling_meaning_get_by_id (lingLesson *lesson, luint id, lingchar *language)
{
     lingMeaning *current = ling_meaning_new ();
     if (!current)
	  return NULL;

     current->translation = meaning_get_word (id, language, 
					      (lessonData *) lesson->pdata);
     if (!current->translation)
     {
	  free (current);
	  return NULL;
     }
  
     current->language = xmlStrdup (language);
     current->type = meaning_get_type (id, (lessonData *) lesson->pdata);
     current->id = id;
     current->lesson = lesson;
     
     return current;
}

/**
 * Retrieves additional information of a meaning and returns them.
 * It also fills the info member with the return value.
 * 
 * \param meaning The meaning, the information should be retrieved for.
 * \returns A lingMInfo with information about the meaning.
 */
lingMInfo*
ling_meaning_get_information (lingMeaning *meaning)
{
     lingMInfo *info = ling_meaning_info_new ();
     if (!info)
	  return NULL;
     
     info->description = ling_meaning_get_description (meaning);
     info->phonetic = ling_meaning_get_phonetic (meaning);
     info->images = ling_meaning_get_images (meaning);

     meaning->info = info;
     return info;
}

/**
 * Frees the memory used by a list of lingMeaning and the lingMeanings itself
 *
 * \param meaning The meaning list to free.
 */
void
ling_meaning_free (lingMeaning *meaning)
{
     lingMeaning *prev = NULL;
     while (meaning)
     {

#ifdef DEBUG
	  printf ("Debug: Freeing Meaning...\n");
#endif
	  prev = meaning;
	  meaning = prev->next;
	  if (prev->translation)
	       xmlFree (prev->translation);
	  if (prev->language)
	       xmlFree (prev->language);
	  if (prev->type)
	       xmlFree (prev->type);
          if (prev->info)
               ling_meaning_info_free (prev->info);
	  free (prev);
     }
     return;
}

/**
 * Frees the memory used by a lingMeaning and the lingMeaning itself
 *
 * \param tree The list of meanings, in which the meanings is.
 * \param meaning The meaning, which should be freed.
 * return The new list without the freed meaning.
 */
lingMeaning*
ling_meaning_free_1 (lingMeaning *tree, lingMeaning *node)
{
     lingMeaning *tmp = tree;
  
     if (tmp == node)
	  tree = tree->next;
     else 
     {
	  while (tmp != node)
	       tmp = tmp->next;

	  if (tmp->prev)
	       tmp->prev->next = tmp->next;
	  if (tmp->next)
	       tmp->next->prev = tmp->prev;
     }
    
     tmp->next = NULL;
     tmp->prev = NULL;

     if (tmp->translation)
	  xmlFree (tmp->translation);
     if (tmp->type)
	  xmlFree (tmp->type);
     if (tmp->language)
	  xmlFree (tmp->language);
     if (tmp->info)
          ling_meaning_info_free (tmp->info);
     free (tmp);
    
     return tree;
}

/**
 * Frees the memory used by a lingMInfo and the lingMInfo itself.
 * 
 * \param info The lingMInfo to free,
 */
void 
ling_meaning_info_free (lingMInfo *info)
{

#ifdef DEBUG
     printf ("Debug: Freeing MeaningInfo...\n");
#endif
     if (info->description)
	  xmlFree (info->description);
     if (info->phonetic)
	  xmlFree (info->phonetic);
     if (info->images)
          ling_strings_free (info->images);
     free (info);
     return;
}

/**
 * Creates a lingLesson from a list of meanings. The node format is 
 * the standard lingoteach lesson format.
 * 
 * \param meaning The list of meanings to put into the lingLesson.
 * \param settings The settings to use for the lingLesson.
 * \param type The type (name) of the lesson.
 * \param sound The sound subdirectory parameter of the lesson.
 * \return the new lingLesson or NULL in case of an error.
 */ 
lingLesson*
ling_meaning_create_lesson (lingMeaning *meaning, lingConfig *settings,
			    lingchar *type, lingchar *sound)
{
     xmlNodePtr node = NULL;
     lingLesson *lesson = ling_lesson_new ();
     lessonData *data = lesson_data_new ();
     
     if (!lesson)
	  return NULL;

     /* copy type */
     lesson->type = xmlStrdup (type);
     
     /* copy sound */
     lesson->sound = xmlStrdup (sound);
          
     /* create the doc */
     data->lesson = xmlNewDoc ("1.0");
     node = xmlNewNode (NULL, settings->appname);
     xmlDocSetRootElement (data->lesson, node);
     xmlSetProp (node, "type", type);
     xmlSetProp (node, "sound", sound);

     /* create the nodes to dump */
     node = meaning_create_xml_node_tree (meaning, node);
     if (!node)
     {
	  ling_lesson_free (lesson);
	  return NULL;
     }

     data->x_path = lesson_get_xpath (data->lesson);
     if (!data->x_path)
     {
	  xmlFreeDoc (data->lesson);
	  free (data);
          ling_lesson_free (lesson);
	  return NULL;
     }
     lesson->pdata = data;

     /* store settings */
     lesson->config = settings;
     return lesson;
}

/**
 * Replaces a meaning in the given list of meanings.
 *
 * \param tree The list of meanings in which the meaning exists.
 * \param id The id of the meaning, which should be replaced.
 * \param meaning The meaning to use as replacement.
 * \return The tree with the replaced meaning or NULL in case of an error.
 */
lingMeaning*
ling_meaning_replace (lingMeaning *tree, unsigned int id, lingMeaning *meaning)
{
     lingMeaning *node = NULL;
     lingMeaning *prev = NULL;
     lingMeaning *next = NULL;

     if (tree)
     {
	  node = tree;
	  while (node->id != id)
	  {
	       if (!node->next)
		    return NULL;
	       node = node->next;
	  }
	  while (xmlStrcmp (node->language, meaning->language) != 0)
	  {
	       if (!node->next)
		    return NULL;
	       node = node->next;
	  }
	  prev = node->prev;
	  next = node->next;
	  meaning->next = next;
	  meaning->prev = prev;
      
	  ling_meaning_free_1 (tree, node);
      
	  next->prev = meaning;
	  if (prev)
	       prev->next = meaning;
     }
     return tree;
}

/**
 * Adds a new meaning at the end of the given meaning list.
 *
 * \param tree The meaning list to which the meaning should be added or NULL
 * to start a new list.
 * \param meaning The meaning to add to the tree.
 * \return The new, modified meaning tree.
 */
lingMeaning*
ling_meaning_add (lingMeaning *tree, lingMeaning *meaning)
{
     lingMeaning *tmp = tree;

     if (tmp)
          while (tmp->next)
               tmp = tmp->next;
     else
     {
          tree = meaning;
          return tree;
     }
     tmp->next = meaning;
     meaning->prev = tmp;
  
     return tree;
}

/**
 * Inserts a meaning after specific meaning into a meaning list.
 *
 * \param tree The meaning list to which the meaning should be added.
 * \param parent The parent meaning, after which the child should be added.
 * \param child The meaning to add.
 * \return The new, modified tree.
 */
lingMeaning*
ling_meaning_insert_after (lingMeaning *tree, lingMeaning *parent,
			   lingMeaning *child)
{
     lingMeaning *last = tree;

     if (parent)
     {
          child->next = parent->next;
          if (parent->next)
               parent->next->prev = child;
          child->prev = parent;
          parent->next = child;
     }
     else /* append as last element */
     {
          while (last->next)
               last = last->next;
          last->next = child;
          child->prev = last;
     }
     return tree;
}

/**
 * Returns the path to the sound snippet for the given meaning.
 * The result has to be freed by the user.
 *
 * \param meaning The lingMeaning the sound snippet has to be found for.
 * \return The relative path to the sound snippet of the meaning.
 */
lingchar*
ling_meaning_get_sound (lingMeaning *meaning)
{
     return meaning_get_sound (meaning->id, meaning->language,
                               meaning->lesson);
}


/**
 * Returns the path to the images for the given meaning. The result has to be 
 * freed by the user using ling_strings_free().
 * 
 * \param meaning The lingMeaning the images have to be found for.
 * \return A NULL-terminated array containing the full qualified paths
 * of the images for that meaning.
 */
lingchar**
ling_meaning_get_images (lingMeaning *meaning)
{
     int i = 0;
     lingchar *query = NULL;
     lingchar **retval = NULL;
     xmlXPathObjectPtr ptr = NULL;
     lessonData *data = (lessonData *) meaning->lesson->pdata;
     int len = xmlStrlen (QUERY_IMAGE) + sizeof (meaning->id);

     query = xmlMalloc ((luint) len);
     if (!query)
	  return NULL;
     xmlStrPrintf (query, len, QUERY_IMAGE, meaning->id);

     ptr = xmlXPathEvalExpression (query, data->x_path);
     xmlFree (query);
     if (!ptr)
	  return NULL;
  
     if (ptr->nodesetval->nodeNr <= 0) /* no nodes */
     {
          xmlXPathFreeObject (ptr);
          return NULL;
     }
          
     /* allocate space */
     retval = xmlMalloc (sizeof (lingchar *) * (ptr->nodesetval->nodeNr + 1));
     if (!retval)
     {
	  xmlXPathFreeObject (ptr);
	  return NULL;
     }

     /* init all elements with NULL */
     while (i <= ptr->nodesetval->nodeNr)
          retval[i++] = NULL;

     /* fill the array */
     for (i = 0; i < ptr->nodesetval->nodeNr; i++)
     {
	  retval[i] = xmlXPathCastNodeToString (ptr->nodesetval->nodeTab[i]);
	  if (!retval[i])
	  {
	       ling_strings_free (retval);
	       xmlXPathFreeObject (ptr);
	       return NULL;
	  }

     }
     retval[ptr->nodesetval->nodeNr] = NULL;

     xmlXPathFreeObject (ptr);
     return retval;
}


/**
 * Retrieves the additional description for a specific meaning. The return
 * value has to be freed by the user using ling_free().
 *
 * \param meaning The meaning to retrieve the description for.
 * \return The description of the meaning in the given language.
 */
lingchar* 
ling_meaning_get_description (lingMeaning *meaning)
{
     xmlXPathObjectPtr ptr = NULL;
     lingchar *query = NULL;
     lingchar *retval = NULL;
     lessonData *lesson = (lessonData *) meaning->lesson->pdata;
     int len = xmlStrlen (QUERY_FIND) + xmlStrlen (meaning->language)
               + sizeof (meaning->id);
     
     /* build the search query */
     query = xmlMalloc ((luint) len);
     if (!query)
	  return NULL;
     xmlStrPrintf (query, len, QUERY_DESCR, meaning->id, meaning->language);
  
     ptr = xmlXPathEvalExpression (query, lesson->x_path);
     xmlFree (query);
     if (!ptr)
	  return NULL;

     retval = xmlXPathCastToString (ptr);
     xmlXPathFreeObject (ptr);
     return retval;
}

/**
 * Retrieves the phonetic transcription for the meaning. The return value
 * has to be freed by the user using ling_free().
 *
 * \param meaning The meaning to retrieve the phonetic transcription for.
 * \return The phonetic transcription of a meaning translation.
 */
lingchar*
ling_meaning_get_phonetic (lingMeaning *meaning)
{
     xmlXPathObjectPtr ptr = NULL;
     lingchar *retval = NULL;
     lingchar *query = NULL;
     lessonData *lesson = (lessonData *) meaning->lesson->pdata;
     int len = xmlStrlen (QUERY_PHON) + xmlStrlen (meaning->language)
               + sizeof (meaning->id);

     query = xmlMalloc ((luint) len);
     if (!query)
	  return NULL;
     xmlStrPrintf (query, len, QUERY_PHON, meaning->id, meaning->language);
  
     ptr = xmlXPathEvalExpression (query, lesson->x_path);
     xmlFree (query);
     if (!ptr)
	  return NULL;

     retval = xmlXPathCastToString (ptr);
     xmlXPathFreeObject (ptr);
     return retval;
}

/**
 * Allocates a chunk of memory for usage.
 *
 * \param size The amount of bytes to allocate.
 * \return A pointer to the newly allocated space.
 */
void*
ling_malloc (size_t size)
{
     return xmlMalloc (size);
}

/**
 * Frees the memory hold by a pointer, which was previously allocated
 * using ling_malloc().
 *
 * \param ptr The pointer to free.
 */
void
ling_free (void *ptr)
{
     xmlFree (ptr);
     return;
}

/**
 * Frees an array of strings, which was previously allocated by the libraray.
 *
 * \param array The string array to free.
 */
void
ling_strings_free (lingchar **array)
{    
     int i = 0;
     while (array[i])
     {
          xmlFree (array[i]);
          i++;
     }
     xmlFree (array);
     return;
}
