/*  $Id: lesson.c,v 1.33 2005/03/27 21:55:39 marcusva Exp $
 *
 *  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"
#include "lesson.h"

/* some static XPaths, which will usually never change */
#define QUERY_LAST_ID "//meaning[last()]/@id"
#define COUNT_IDS "count(//meaning)"
#define COUNT_TRANS "count(//translation)"

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

/*
 * returns a xmlXPathContextPtr of the given xmlDocPtr
 * the return value needs to be freed.
 */
xmlXPathContextPtr
lesson_get_xpath (xmlDocPtr doc)
{
     xmlXPathInit ();
     return xmlXPathNewContext (doc);
}

/*
 * creates a new empty lessonData 
 */
lessonData*
lesson_data_new (void)
{
     lessonData *data = malloc (sizeof (lessonData));
     if (!data)
          return NULL;
     data->lesson = NULL;
     data->x_path = NULL;
     data->path = NULL;
     return data;
}

/* 
 * returns the last lesson from the list
 */
lingLesson*
lesson_get_last (lingLesson *lesson)
{
     if (lesson)
          while (lesson->next)
               lesson = lesson->next;
     return lesson;
}

/*
 * creates and returns the lesson data for a file
 * the return value needs to be freed if not needed anymore
 */
void*
lesson_create_lesson_data (char *filename, lingConfig *settings)
{
     lessonData *data = NULL;
     xmlValidCtxtPtr val = NULL; 

     data = lesson_data_new ();
     if (!data)
	  return NULL;

     val = xmlNewValidCtxt ();
     if (!val)
        return NULL;
 
     data->lesson = xmlParseFile (filename);
     if (!data->lesson) /* the lesson */
     {
	  free (data);
	  return NULL;
     }

     /* validate the lesson file */
     if (xmlValidateDtd (val, data->lesson,
                         ((lingPrivateConfig *) settings->pdata)->lesson_dtd)
         == 0)
     {
          xmlFreeValidCtxt (val);
          xmlFreeDoc (data->lesson);
          free (data);
          return NULL;
     }
     xmlFreeValidCtxt (val);

     /* create the xmlXPath stuff */
     xmlXPathOrderDocElems (data->lesson); /* speed up searches */
     data->x_path = lesson_get_xpath (data->lesson);
     if (!data->x_path)
     {
	  xmlFreeDoc (data->lesson);
	  free (data);
	  return NULL;
     }
     
     /* copy filepath, stored as 'normal' char* */
     data->path = malloc (strlen (filename) + 1);
     if (!data->path)
     {
	  xmlFreeDoc (data->lesson);
	  xmlXPathFreeContext (data->x_path);
	  free (data);
	  return NULL;
     }
     strncpy (data->path, filename, strlen (filename) + 1);
     return data;
}

/*
 * frees all memory hold by the passed lessonData, including the lessonData
 * itself
 */
void
lesson_free_lesson_data (lessonData *data)
{
     if (data->lesson)
          xmlFreeDoc (data->lesson);
     if (data->x_path)
          xmlXPathFreeContext (data->x_path);
     if (data->path)
          free (data->path);
     free (data);
     return;
}

/*
 * saves the passed lesson to the passed file
 */
lingbool
lesson_save_lesson (lingLesson *lesson, char *filename)
{
     xmlDocPtr doc = ((lessonData *) lesson->pdata)->lesson;
 
     xmlKeepBlanksDefault (0);

     
#ifdef WITH_COMP
     xmlSetDocCompressMode (doc, 3);
#endif
     
     if (xmlSaveFormatFile (filename, doc, 1) == -1) /* save */
	  return FALSE;
     
     return TRUE;
}

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

/**
 * Creates a new, empty lingLesson. 
 * The lingLesson should be freed using ling_lesson_free().
 *
 * \return A new, empty lingLesson.
 */
lingLesson*
ling_lesson_new (void)
{
     lingLesson *lesson = malloc (sizeof (lingLesson));
     if (!lesson)
	  return NULL;
     
     lesson->pdata = NULL;
     lesson->type  = NULL;
     lesson->sound = NULL;
     lesson->next  = NULL;
     return lesson;
}


/**
 * Frees the memory hold by a lingLesson.
 *
 * \param lesson The lingLesson to free.
 */
void
ling_lesson_free (lingLesson *lesson)
{
     if (lesson->pdata)
	  lesson_free_lesson_data ((lessonData *) lesson->pdata);
     if (lesson->type)
	  xmlFree (lesson->type);
     if (lesson->sound)
	  xmlFree (lesson->sound);
     free (lesson);
     return;
}

/**
 * Creates a lingLesson from a lesson file.
 * 
 * \param filename The full qualified path of the file.
 * \param settings The settings to use for the lesson.
 * \return A new lingLesson.
 */
lingLesson*
ling_lesson_create (char *filename, lingConfig *settings)
{
     lingLesson *lesson = NULL;
     lessonData *data = NULL;

     /* allocate new lingLesson */
     lesson = ling_lesson_new ();
     if (!lesson)
	  return NULL;

     /* create lessonData */
     lesson->pdata = lesson_create_lesson_data (filename, settings);
     if (!lesson->pdata)
     {
#ifdef DEBUG
	  printf ("Debug: lesson data structure could not be created!\n");
#endif
	  ling_lesson_free (lesson);
	  return NULL;
     }
     lesson->next = NULL;

     /* determine the type */
     data = (lessonData *) lesson->pdata;
     data->lesson->parent = xmlDocGetRootElement (data->lesson);
     lesson->type = xmlGetProp (data->lesson->parent, "type");
     lesson->sound = xmlGetProp (data->lesson->parent, "sound");
     lesson->config = settings;

     return lesson;
}

/**
 * Adds a lingLesson to a list of lingLessons.
 *
 * \param list  The lesson list, the new lesson should be added to.
 * \param lesson The lesson to add.
 * \returns The modified list.
 */
lingLesson*
ling_lesson_add (lingLesson* list, lingLesson *lesson)
{
     if (list)
     {
         lingLesson *tmp = lesson_get_last (list);
         tmp->next = lesson;
     }
     else
        list = lesson;
     return list;
}

/**
 * Removes a lesson from a lingLesson list and frees it.
 *
 * \param lesson The lesson list to the lesson should be removed from.
 * \param node The lesson to remove.
 * \return The modified lesson list.
 */
lingLesson*
ling_lesson_remove (lingLesson *lesson, lingLesson *node)
{
     lingLesson *tmp = NULL;
     lingLesson *prev = NULL;
  
     tmp = lesson;
     while (tmp)
     {
	  if (tmp == node)
	  {
	       /* link the previous and next node */
	       (prev) ? (prev->next = tmp->next) : (lesson = tmp->next);

	       /* free all memory hold by the lesson */
	       ling_lesson_free (tmp);
	       break;
	  }
	  prev = tmp;
	  tmp = prev->next;
     }
     return lesson;
}

/**
 * Returns the full qualified file path of the lesson. The return value must
 * not be freed.
 *
 * \param lesson The lesson for which the path should be returned.
 * \return The lesson path of the lesson.
 */
char*
ling_lesson_get_path (lingLesson *lesson)
{
     return ((lessonData *) lesson->pdata)->path;
}

/**
 * Sets the full qualified file path of the lesson to path and returns it.
 * The return value must not be freed.
 *
 * \param lesson The lesson for which the path should be set.
 * \param path The path to set.
 * \return The new path of the lesson or NULL on error.
 */
char*
ling_lesson_set_path (lingLesson *lesson, char *path)
{
     lessonData *data = (lessonData *) lesson->pdata;
     char *tmp = NULL;

     tmp = malloc (strlen (path) + 1);
     if (!tmp)
          return NULL;
     strncpy (tmp, path, strlen (path) + 1);
     
     if (data->path)
          free (data->path);
     data->path = tmp;
     return data->path;
}

/**
 * Returns the last meaning id of the given lesson.
 * 
 * \param lesson The lesson, for which the last meaning id should be returned.
 * \return The last meaning id of the lesson or -1 on failure.
 */
int
ling_lesson_get_last_meaning_id (lingLesson *lesson)
{
     xmlXPathObjectPtr ptr = NULL;
     lingchar *tmp = NULL;
     luint i = 0;
     lessonData *data = (lessonData *) lesson->pdata;
     xmlXPathContextPtr lessonCtxt = data->x_path;

     /* create the XPath object */
     ptr = xmlXPathEvalExpression (QUERY_LAST_ID, lessonCtxt);
     if (!ptr)
	  return -1;

     tmp = xmlXPathCastToString (ptr);
     xmlXPathFreeObject (ptr);
     if (!tmp || xmlStrncmp (tmp, "", xmlStrlen (tmp)) == 0)
     {
	  xmlXPathFreeObject (ptr);
	  return -1;
     }
    
     tmp = strtok (tmp, "m");
     i   = abs (atoi (tmp));
     xmlFree (tmp);

#ifdef DEBUG
     printf ("Debug: Maximum of meanings: %i\n", i);
#endif

     return i;
}

/**
 * Returns the total amount of meanings for the given lesson.
 *
 * \param lesson The lesson, the amount should be got for.
 * \return The total amount of meanings or -1 on failure.
 */
int
ling_lesson_get_meaning_amount (lingLesson *lesson)
{
     xmlXPathObjectPtr ptr = NULL;
     xmlXPathContextPtr lessonCtxt = ((lessonData *) lesson->pdata)->x_path;
     int i = 0;

     ptr = xmlXPathEvalExpression (COUNT_IDS, lessonCtxt);
     if (!ptr)
          return -1;

     i = (int) xmlXPathCastToNumber (ptr);
     xmlXPathFreeObject (ptr);

     return i;
}

/**
 * Returns the total amount of translations for the given lesson.
 *
 * \param lesson The lesson, the amount should be got for.
 * \return The total amount of translations or -1 on failure.
 */
int
ling_lesson_get_translation_amount (lingLesson *lesson)
{
     xmlXPathObjectPtr ptr = NULL;
     xmlXPathContextPtr lessonCtxt = ((lessonData *) lesson->pdata)->x_path;
     int i = 0;

     ptr = xmlXPathEvalExpression (COUNT_TRANS, lessonCtxt);
     if (!ptr)
          return -1;

     i = (int) xmlXPathCastToNumber (ptr);
     xmlXPathFreeObject (ptr);

     return i;
}

/**
 * Saves a lesson into the passed file.
 * If the file does not exist, it will be automatically created, else
 * its contents will be completely overwritten.
 *
 * \param lesson The lesson to save.
 * \param filename The file to save the lesson to.
 * \return TRUE, if the lesson could be saved, else FALSE.
 */
lingbool
ling_lesson_save_lesson (lingLesson *lesson, char *filename)
{
     FILE *fp = NULL;
     lessonData *data = (lessonData *) lesson->pdata;

     if (!filename)
     {
	  filename = data->path;
	  if (!filename)
	       return FALSE;
     }
     
     fp = fopen (filename, "r");
     if (!fp)
     {
          if (!ling_lesson_create_template (filename, 0, lesson->config))
	       return FALSE;
     }
     else
	  fclose (fp);

     return lesson_save_lesson (lesson, filename);
}

/**
 * Creates a new template lesson with optional empty meanings. The function 
 * uses the application name specified in the settings as DTD indentifier.
 *
 * \param filename The lesson file to create.
 * \param meanings The amount of meaning templates to create.
 * \param settings The settings to use.
 * \return The filename on success or NULL in case of an error.
 */
char*
ling_lesson_create_template (char *filename, int meanings, 
			     lingConfig *settings)
{
     FILE *fp = fopen (filename, "w+");
     if (fp)
     {
	  /* dump a basic tree into the file */
	  fprintf (fp, "<?xml version =\"1.0\"?>\n");
	  fprintf (fp, "<!DOCTYPE %s SYSTEM \"%s.dtd\">\n",
		   settings->appname, settings->appname);
	  fprintf (fp, "<!-- automatically created by liblingoteach -->\n");
	  fprintf (fp, 
		   "<!-- report errors on http://www.lingoteach.org -->\n\n");
	  fprintf (fp, "<lingoteach type= \"\" sound=\"\">\n");

	  while (--meanings > 0)
	       fprintf (fp, "  <meaning id=\"m%i\" type=\"\">\n  </meaning>\n",
			meanings);
	  fprintf (fp, "</lingoteach>\n");
	  fclose (fp);
	  return filename;
     }
     return NULL;
}

/**
 * Creates a linked list of all meanings, which are available in the given
 * lesson.
 * 
 * \param lesson The lesson, for which the meaning tree should be created.
 * \return A linked list of meanings of the lesson or NULL if an error occurs.
 */
lingMeaning*
ling_lesson_create_tree (lingLesson *lesson)
{
     xmlNodePtr child = NULL;
     xmlNodePtr trans = NULL;
     int id = 0;
     lingchar *tmp = NULL;
     lingMeaning *meaning = NULL;
     lingMeaning *newone = NULL;
     lingMeaning *prev = NULL;
     lessonData *data = (lessonData *) lesson->pdata;
  
     if (!data)
	  return NULL;

     /* check the parent */  
     data->lesson->parent = xmlDocGetRootElement (data->lesson);
     if (!data->lesson->parent || !data->lesson->parent->name)
	  return NULL;
      
     /* check for children */
     child = data->lesson->parent->children;
  
     /* create tree */
     for (child = data->lesson->parent->children; child != NULL;
          child = child->next)
     {
	  if (xmlStrncmp (child->name, "meaning",
                          xmlStrlen (child->name)) != 0)
	       continue;
	  
	  /* necessary content for all similar meanings */
	  tmp = xmlGetProp (child, "id");
	  if (!tmp) /* this should not happen! */
	  {
	       if (meaning)
		    ling_meaning_free (meaning);
	       return NULL;
	  }
	  id = abs (atoi (strtok (tmp, "m")));
	  xmlFree (tmp);

	  /* get translations */
	  for (trans = child->children; trans != NULL; trans = trans->next)
	  {
	       if (xmlIsBlankNode (trans) == 1
		   || xmlStrncmp (trans->name, "translation", 
                                  xmlStrlen (trans->name) != 0))
		    continue;
	       
	       if (!meaning)
	       {
		    meaning = ling_meaning_new ();
		    if (!meaning)
			 return NULL;
		    newone = meaning;
		    newone->prev = NULL;
		    newone->next = NULL;
	       }
	       else
	       {
		    newone->next = ling_meaning_new ();
		    if (!newone->next)
		    {
			 ling_meaning_free (meaning);
			 return NULL;
		    }
		    prev = newone;
		    newone = prev->next;
		    newone->prev = prev;
		    newone->next = NULL;
	       }
	       /* fill meanings and link them */
	       newone->id          = id;
	       newone->type        = xmlGetProp (child, "type");
	       newone->language    = xmlGetProp (trans, "language");
	       newone->translation = xmlNodeGetContent (trans);
	       newone->lesson      = lesson;
	  }
     }
     return meaning;
}
