/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/
#include "visu_tools.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> /* For the access markers R_OK, W_OK ... */
#include <math.h>

/**
 * tool_getValidPath:
 * @pathList: (element-type filename): a pointer to a GList with all the possible path,
 * @filenames: (array zero-terminated=1) (element-type filename): an array of strings,
 * @accessMode: a value from R_OK, W_OK and X_OK as described in unistd.h.
 *
 * @pathList contains a list of directories (first is most prefered)
 * and fileName is the file name which one likes have informations on. This routine
 * look for the first directory where fileName can be writen or read (depending
 * on accessMode parameter). The pointer to the GList indicates at the end the
 * first valid entry in the GList.
 *
 * Returns: (transfer full): the first valid complete path (from
 * @pathList plus an entry of @filenames) if one can be found depnding
 * on @accessMode or NULL if none found. Free it with g_free() after
 * use.
 */
gchar* tool_getValidPath(GList **pathList, const char **filenames, int accessMode)
{
  gchar *validPath;
  int fileOk;
  guint i;

  g_return_val_if_fail(pathList && filenames, (gchar*)0);
  
  DBG_fprintf(stderr, "Visu Tools : test access (%d) from list of %d elements.\n",
              accessMode, g_list_length(*pathList));
  validPath = (char*)0;
  /* look for a directory to save or read a file. */
  fileOk = 0;
  while (*pathList && !fileOk)
    {
      for (i = 0; filenames[i]; i++)
        {
          validPath = g_build_filename((gchar*)(*pathList)->data, filenames[i], NULL);
          DBG_fprintf(stderr, "Visu Tools : test access (%d) for '%s' ... ",
                      accessMode, validPath);
          fileOk = !access((char*)validPath, accessMode); /* return 0 if success */
          if (fileOk)
            break;
        
          /* if access mode is write access and the file does not already exist :
             we test if the directory has written permitions. */
          if ( accessMode == W_OK && !g_file_test(validPath, G_FILE_TEST_EXISTS) )
            fileOk = !access((char*)(*pathList)->data, accessMode);

          if (fileOk)
            break;

          DBG_fprintf(stderr, " failed.\n");
          g_free(validPath);
        }
      if (!fileOk)
        *pathList = g_list_next(*pathList);
    }
  if (fileOk)
    {
      DBG_fprintf(stderr, " OK.\n");
      return validPath;
    }
  else
    return (gchar*)0;
}

/**
 * tool_modulo_float:
 * @a: a float ;
 * @b: an int.
 * 
 * This function is just like a%b except it works with a float @a argument. 
 * @a can be negative, but the return value of the function is always positive.
 *
 * Returns: the new float value after the modulo.
*/
float tool_modulo_float(float a, int b) {
  float fb = (float)b;
  while(a < fb) a += fb;
  while(a >= fb) a -= fb;
  return a;
}

/**
 * tool_path_normalize:
 * @path: a string, NULL terminated.
 *
 * This function normalizes the path, i.e. it removes all . and .. It should
 * work also on Windows. It must take an absolute path as argument, if not it is converted
 * assuming the current working directory.
 *
 * Returns: a newly created string.
 */
gchar* tool_path_normalize(const gchar* path)
{
#if SYSTEM_X11 == 1
#define FILE_SYSTEM_SEP "/"
#endif
#if SYSTEM_WIN32 == 1
#define FILE_SYSTEM_SEP "\\"
#endif
  gchar **tokens;
  int i;
  GString *normPath;
  GList *lst, *tmplst;
  gchar *allPath, *dir;

  if (!path)
    return (gchar*)0;

  if (!g_path_is_absolute(path))
    {
      dir = g_get_current_dir();
      allPath = g_build_filename(dir, path, NULL);
      g_free(dir);
    }
  else
    allPath = g_strdup(path);

  tokens = g_strsplit(allPath, FILE_SYSTEM_SEP, -1);

  normPath = g_string_new("");

  lst = (GList*)0;
  for (i = 0; tokens[i]; i++)
    {
      /* If tokens[i] == . or is empty (because of //), we ignore. */
	if (!strcmp(tokens[i], "."))
	  continue;
      if (!tokens[i][0])
	continue;
      /* If token[i] == .. then we pop one element from lst. */
      if (!strcmp(tokens[i], ".."))
	{
	  lst = g_list_delete_link(lst, lst);
	  continue;
	}
      /* Token[i] is a valid chain, then we prepend it to the list. */
      lst = g_list_prepend(lst, tokens[i]);
    }
  /* Write the lst to the string. */
  for (tmplst = lst; tmplst; tmplst = g_list_next(tmplst))
    {
      g_string_prepend(normPath, (gchar*)tmplst->data);
      g_string_prepend(normPath, FILE_SYSTEM_SEP);
    }
  g_list_free(lst);
#if SYSTEM_WIN32 == 1
  g_string_erase(normPath, 0,1);
#endif
  g_strfreev(tokens);
  g_free(allPath);
  if (!normPath->str[0])
    g_string_append(normPath, FILE_SYSTEM_SEP);
  DBG_fprintf(stderr, "Visu Tools : normalizing path, from '%s' to '%s'.\n",
	      path, normPath->str);
  return g_string_free(normPath, FALSE);
}

#if GLIB_MINOR_VERSION < 5
/**
 * g_file_set_contents:
 * @fileName: a string ;
 * @str: a string ;
 * @len: a length or -1 ;
 * @error: a location for an error.
 *
 * Compiled only if Glib is lower than 2.5.
 *
 * Returns: TRUE on success.
 */
gboolean g_file_set_contents(const gchar *fileName, const gchar *str,
			     gsize len _U_, GError **error _U_)
{
  FILE *f;

  f = fopen(fileName, "w");
  if (!f)
    return FALSE;
  if (fwrite(str, strlen(str), 1, f) != strlen(str))
    return FALSE;
  fclose(f);

  return TRUE;
}
#endif
#if GLIB_MINOR_VERSION < 27
/**
 * g_list_free_full:
 * @list: a pointer to a #GList
 * @free_func: the function to be called to free each element's data
 *
 * Convenience method, which frees all the memory used by a #GList, and
 * calls the specified destroy function on every element's data.
 *
 * Compiled only if Glib is lower than 2.27.
 */
void
g_list_free_full (GList          *list,
		  GDestroyNotify  free_func)
{
  g_list_foreach (list, (GFunc) free_func, NULL);
  g_list_free (list);
}
#endif

static gchar* tagLookup(const gchar *tag, const gchar *buffer)
{
  char *ptTag, *ptStart, *ptEnd;

  ptTag = strstr(buffer, tag);
  if (!ptTag)
    return (gchar*)0;

  /* We check that tag was not in a commentary section. */
  ptStart = g_strrstr_len(buffer, (gssize)(ptTag - buffer), "<!--");
  if (!ptStart)
    return ptTag;
  ptEnd = g_strstr_len(ptStart, (gssize)(ptTag - ptStart), "-->");
  if (ptEnd)
    return ptTag;
  return tagLookup(tag, ptTag + strlen(tag));
}
/**
 * tool_XML_substitute:
 * @output: a GString to store the substitution ;
 * @filename: the file to read the data from ;
 * @tag: the tag to substitute ;
 * @error: a location to store possible errors.
 *
 * Read @filename (must be XML file) and remove the @tag zone from
 * it. At that place, it puts the contain of @output.
 *
 * Returns: TRUE if no error occured.
 */
gboolean tool_XML_substitute(GString *output, const gchar *filename,
				 const gchar *tag, GError **error)
{
  gchar *contents, *ptStart, *ptStop;
  gchar *tgStart, *tgEnd;
  gboolean valid;

    /* If file does exist, we read it and replace only the tag part. */
  contents = (gchar*)0;
  ptStart = (gchar*)0;
  if (g_file_test(filename, G_FILE_TEST_EXISTS))
    {
      valid = g_file_get_contents(filename, &contents, (gsize*)0, error);
      if (!valid)
	return FALSE;

      tgStart = g_strdup_printf("<%s", tag);

      ptStart = tagLookup(tgStart, contents);
      if (ptStart)
	g_string_prepend_len(output, contents, (gssize)(ptStart - contents));
      else
	{
          g_string_prepend(output, "  ");
	  ptStop = tagLookup("</v_sim>", contents);
	  if (ptStop)
	    g_string_prepend_len(output, contents, (gssize)(ptStop - contents));
	  else
	    {
	      ptStop = tagLookup("<v_sim>", contents);
	      if (ptStop)
		g_string_prepend(output, contents);
	      else
		{
		  g_string_prepend(output, contents);
		  g_string_prepend(output, "<?xml version=\"1.0\""
				   " encoding=\"utf-8\"?>\n<v_sim>");
		}
	    }
	}

      g_free(tgStart);
    }
  else
    g_string_prepend(output, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<v_sim>\n  ");

  /* If file does exist, we add the remaining parts. */
  if (contents && ptStart)
    {
      tgEnd = g_strdup_printf("</%s>", tag);
      
      ptStop = tagLookup(tgEnd, ptStart);
      if (ptStop)
	g_string_append(output, ptStop + strlen(tgEnd));
      else
	g_string_append(output, "\n</v_sim>");

      g_free(tgEnd);
    }
  else
    g_string_append(output, "</v_sim>\n");

  if (contents)
    g_free(contents);

  return TRUE;
}
