/*****************************************************************************

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

*****************************************************************************/



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	"base.h"
#include	"base_ct.h"
#include	"fnames.h"
#include	"memory.h"
#include	"String.h"

#include	<cassert>
#include	<cctype>
#include	<cstdlib>
#include	<cstring>



/*\\\ CONSTANTES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ TYPES & STRUCTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ PROTOTYPES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES EXTERNES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ FONCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: FNAM_get_drive                                                 */
/*      Description: Recupere le nom du lecteur dans un                     */
/*                   chemin quelconque. Un espace est alloue automatique-   */
/*                   ment avec un MALLOC (). Le resultat est indetermine si */
/*                   le chemin n'est pas correct. Les espaces avant et      */
/*                   apres sont elimines.                                   */
/*      Parametres en entree:                                               */
/*        - path_0: pointeur sur le chemin, se terminant par 0.             */
/*      Retour: pointeur sur le lecteur, a liberer par un FREE (). Aucun    */
/*              lecteur: chaine vide. Ou NULL si erreur.                    */
/*==========================================================================*/

String	FNAM_get_drive (String path)
{
	long		antislash;
	long		two_points;

	/* Vire les espaces du debut */
	path = path.trim ();

	/* Regarde si on est sur un lecteur distant */
	if (path.left (2) == String ("\\\\"))
	{
		antislash = path.after (1).find ('\\');
		if (antislash < 0)
		{
			return (path);
		}
		return (path.left (antislash));
	}

	two_points = path.find (':');

	/* Pas de ':', pas de lecteur */
	if (two_points < 0)
	{
		return ("");
	}

	return (path.left (two_points + 1));
}



/*==========================================================================*/
/*      Nom: FNAM_get_path                                                  */
/*      Description: Recupere le nom du chemin sans le lecteur dans un      */
/*                   chemin quelconque. Un espace est alloue automatique-   */
/*                   ment avec un MALLOC (). Le resultat est indetermine si */
/*                   le chemin n'est pas correct. Un morceau de chemin est  */
/*                   toujours termine par '\'. Les espaces avant et apres   */
/*                   sont elimines.                                         */
/*      Parametres en entree:                                               */
/*        - path_0: pointeur sur le chemin, se terminant par 0.             */
/*      Retour: pointeur sur le chemin, a liberer par un FREE (). Aucun     */
/*              chemin: chaine vide. Ou NULL si erreur.                     */
/*==========================================================================*/

String	FNAM_get_path (String path)
{
	long		antislash;
	long		two_points;

	/* Deja, vire les espaces du debut */
	path = path.trim ();

	/* Cherche le debut du chemin, apres le lecteur */
	two_points = path.find (':');
	if (two_points >= 0)
	{
		path = path.after (two_points);
	}

	/* Cherche la fin du chemin, qui est termine par '\' */
	antislash = path.rfind ('\\');
	if (antislash < 0)
	{
		return ("");
	}

	/* On renvoie le debut du chemin jusqu'a '\' */
	return (path.left (antislash + 1));
}



/*==========================================================================*/
/*      Nom: FNAM_get_full_path                                             */
/*      Description: Recupere le lecteur + nom du chemin dans un            */
/*                   chemin quelconque. Un espace est alloue automatique-   */
/*                   ment avec un MALLOC (). Le resultat est indetermine si */
/*                   le chemin n'est pas correct. Un morceau de chemin est  */
/*                   toujours termine par '\'. Les espaces avant et apres   */
/*                   sont elimines.                                         */
/*      Parametres en entree:                                               */
/*        - path_0: pointeur sur le chemin, se terminant par 0.             */
/*      Retour: pointeur sur le chemin, a liberer par un FREE (). Aucun     */
/*              chemin: chaine vide. Ou NULL si erreur.                     */
/*==========================================================================*/

String	FNAM_get_full_path (String path)
{
	long		antislash;

	/* Deja, vire les espaces du debut */
	path = path.trim ();

	/* Cherche la fin du chemin, qui est termine par '\' */
	antislash = path.rfind ('\\');
	if (antislash < 0)
	{
		return ("");
	}

	/* On renvoie le debut du chemin jusqu'a '\' */
	return (path.left (antislash + 1));
}



/*==========================================================================*/
/*      Nom: FNAM_get_full_filename                                         */
/*      Description: Recupere le nom du fichier + extension dans un         */
/*                   chemin quelconque. Un espace est alloue automatique-   */
/*                   ment avec un MALLOC (). Le resultat est indetermine si */
/*                   le chemin n'est pas correct. Les espaces avant et      */
/*                   apres sont elimines.                                   */
/*      Parametres en entree:                                               */
/*        - path_0: pointeur sur le chemin, se terminant par 0.             */
/*      Retour: pointeur sur le nom, a liberer par un FREE (). Aucun        */
/*              chemin: chaine vide. Ou NULL si erreur.                     */
/*==========================================================================*/

String	FNAM_get_full_filename (String path)
{
	/* On se place apres le lecteur s'il existe, termine par ':' */
	path = path.after (path.find (':'));

	/* On se place apres le chemin s'il existe, termine par '\' */
	path = path.after (path.rfind ('\\'));

	/* On vire les enventuels espaces et on retourne ce qui reste */
	return (path.trim ());
}


/*==========================================================================*/
/*      Nom: FNAM_parse_path_ext                                            */
/*      Description: Recupere l'extension d'un nom de fichier dans un       */
/*                   chemin quelconque. Un espace est alloue automatique-   */
/*                   ment avec un MALLOC (). Le resultat est indetermine si */
/*                   le chemin n'est pas correct. Les espaces apres sont    */
/*                   elimines.                                              */
/*      Parametres en entree:                                               */
/*        - path_0: pointeur sur le chemin, se terminant par 0.             */
/*      Retour: pointeur sur l'extension, a liberer par un FREE (). Ou NULL */
/*              si erreur.                                                  */
/*==========================================================================*/

String	FNAM_parse_path_ext (String path)
{
	long		point;

	/* Cherche d'abord la position du fichier en trouvant le dernier '\\' */
	path = path.after (path.rfind ('\\'));

	/* Cherche son extension (dernier point) */
	point = path.rfind ('.');
	if (point < 0)
	{
		return ("");
	}

	return (path.after (point).trim ());
}



/*==========================================================================*/
/*      Nom: FNAM_test_filename                                             */
/*      Description: Teste si un nom de fichier correspond au masque        */
/*                   demande. Si le nom n'a pas d'extension et pas de       */
/*                   point, la recherche sera validee si le masque demande  */
/*                   une extension avec jocker '*'.                         */
/*      Parametres en entree:                                               */
/*        - filename_0: nom du fichier, termine par un 0.                   */
/*        - mask_0: Masque, termine par un 0. Peut comporter les jockers    */
/*                  '?' et '*'. Plusieurs masques peuvent etre testes, ils  */
/*                  doivent etre separes par ','. Les masques ne doivent    */
/*                  pas comporter d'espaces superflus.                      */
/*      Retour: true si le nom de fichier correspond au masque.             */
/*==========================================================================*/

bool	FNAM_test_filename (const char *filename_0, const char *mask_0)
{
	int				char_nbr;

	int				length;

	const char *	temp_0;

	char *			mask_2_0;
	const char *	virgule_0;

/*______________________________________________
 *
 * Cas de masques multiples
 *______________________________________________
 */

	while ((virgule_0 = strchr (mask_0, ',')) != NULL)
	{
		length = int (virgule_0 - mask_0);
		mask_2_0 = (char *) MALLOC (length + 1);
		memcpy (mask_2_0, mask_0, length);
		mask_2_0 [length] = 0;
		if (FNAM_test_filename (filename_0, mask_2_0))
		{
			FREE (mask_2_0);
			return (true);
		}
		FREE (mask_2_0);
		mask_0 = virgule_0 + 1;
	}

/*______________________________________________
 *
 * Recherche sur un masque simple
 *______________________________________________
 */

	while (*mask_0 != 0)
	{
		if (*filename_0 == 0)
		{
			if (*mask_0++ == '.')
			{
				while (*mask_0 == '*')
				{
					mask_0 ++;
				}
				if (*mask_0 == 0)
				{
					return (true);
				}
			}
			return (false);
		}

		if (*mask_0 == '\?')
		{
			filename_0 ++;
		}

		else if (*mask_0 == '*')
		{
			while (mask_0 [1] == '*')
			{
				mask_0 ++;
			}
			char_nbr = 0;
			while (mask_0 [1] == '\?')
			{
				char_nbr ++;
				mask_0 ++;
			}
			if (mask_0 [1] == 0)
			{
				return (true);
			}

			temp_0 = strchr (filename_0, toupper (mask_0 [1]));
			if (temp_0 == NULL)
			{
				temp_0 = strchr (filename_0, tolower (mask_0 [1]));
			}

			if (temp_0 == NULL)
			{
				if (   mask_0 [1] == '.'
				    && mask_0 [2] == '*'
				    && strlen (mask_0) == 3)
				{
					return (true);
				}

				return (false);
			}
			if (temp_0 - filename_0 < char_nbr)
			{
				return (false);
			}
			filename_0 = temp_0;
		}

		else
		{
			if (toupper (*filename_0) != toupper (*mask_0))
			{
				if (   mask_0 [1] == '.'
				    && mask_0 [2] == '*'
				    && strlen (mask_0) == 3)
				{
					return (true);
				}

				return (false);
			}
			filename_0 ++;
		}

		mask_0 ++;
	}

	if (*filename_0 != 0)
	{
		return (false);
	}
		
	return (true);
}



/*==========================================================================*/
/*      Nom: FNAM_get_abs_path_from_rel                                     */
/*      Description: Fabrique un nom de chemin absolu a partir d'un chemin  */
/*                   absolu et d'un chemin qui lui est relatif.             */
/*      Parametres en entree:                                               */
/*        - main_path_0: pointeur sur le chemin de base, absolu.            */
/*        - rel_path_0: pointeur sur le chemin relatif.                     */
/*      Retour: Le chemin absolu final, a liberer avec un FREE. NULL si     */
/*              erreur.                                                     */
/*==========================================================================*/

String	FNAM_get_abs_path_from_rel (String main_path, String rel_path)
{
	String	m_drive = FNAM_get_drive (main_path);
	String	r_drive = FNAM_get_drive (rel_path);

	/* Si le lecteur du chemin principal et celui du chemin relatif sont
	   differents, on renvoie le chemin relatif en entier. */
	if (   BASE_compare_string (m_drive.c_str (), r_drive.c_str ()) != 0
	    && r_drive.get_len () != 0)
	{
		FNAM_simplify_path (rel_path);
		return (rel_path);
	}

	/* On a deja le lecteur */
	String	abs_path = m_drive;

	String	m_path = FNAM_get_path (main_path);

	/* On obtient les chemins principaux et relatifs sans leur lecteur */
	String	r_path =   FNAM_get_path (rel_path)
	                  + FNAM_get_full_filename (rel_path);

	/* Si le chemin relatif ne commence pas a la racine, on joint
	   le chemin principal au lecteur trouve. */
	if (r_path [0] != '\\')
	{
		abs_path += m_path;
	}

	/* On connecte ensuite le chemin relatif */
	abs_path += r_path;

	/* On simplifie l'expression et on la retourne */
	FNAM_simplify_path (abs_path);

	return (abs_path);
}



/*==========================================================================*/
/*      Nom: FNAM_get_rel_path_from_abs                                     */
/*      Description: Fabrique un chemin relatif a partir d'un chemin de     */
/*                   base et d'un chemin absolu.                            */
/*      Parametres en entree:                                               */
/*        - main_path_0: pointeur sur le chemin de base.                    */
/*        - abs_path_0: chemin absolu a relativiser.                        */
/*      Retour: Le chemin relatif final, a liberer avec un FREE. NULL si    */
/*              erreur.                                                     */
/*==========================================================================*/

String	FNAM_get_rel_path_from_abs (String main_path, String abs_path)
{
	int		nbr;
	long		length;
	long		antislash;
	String	m_drive;
	String	a_drive;
	String	a_path;
	String	m_path;
	String	r_path;
	String	m_tree;
	String	a_tree;
	String	m_dir;
	String	a_dir;
	String	filename;

	/* Recupere les deux lecteurs des chemins */
	m_drive = FNAM_get_drive (main_path);
	a_drive = FNAM_get_drive (abs_path);

	/* Lecteurs differents: on renvoie tout le chemin absolu */
	if (BASE_compare_string (m_drive.c_str (), a_drive.c_str ()) != 0)
	{
		FNAM_simplify_path (abs_path);
		return (abs_path);
	}

	m_path = FNAM_get_path (main_path);
	a_path = FNAM_get_path (abs_path);

	/* Recherche l'endroit ou les repertoires ne sont plus communs, et compte
	   les remontees de repertoire necessaires. */
	m_tree = m_path;
	a_tree = a_path;
	nbr = 0;
	while (m_tree.get_len () > 0)
	{
		/* Recupere le repertoire de reference */
		antislash = m_tree.find ('\\');
		length = antislash + 1;
		m_dir = m_tree.left (length);
		m_tree = m_tree.after (length - 1);

		if (   a_tree.get_len () > 0
		    && nbr == 0)
		{
			/* Recupere le repertoire absolu */
			antislash = a_tree.find ('\\');
			length = antislash + 1;
			a_dir = a_tree.left (length);

			if (BASE_compare_string (m_dir.c_str (), a_dir.c_str ()) != 0)
			{
				nbr ++;
			}
			else
			{
				a_tree = a_tree.after (length - 1);
			}
		}

		else
		{
			nbr ++;
		}
	}

	r_path = "";

	/* Ajoute les remontees de repertoire necessaires */
	for ( ; nbr > 0; nbr --)
	{
		r_path += "..\\";
	}

	/* Complete avec la difference de chemin et ajoute le nom de fichier */
	return (r_path + a_tree + FNAM_get_full_filename (abs_path));
}



/*==========================================================================*/
/*      Nom: FNAM_simplify_path                                             */
/*      Description: Simplifie un chemin en interpretant les ..\ et en      */
/*                   supprimant les .\                                      */
/*      Parametres en entree/sortie:                                        */
/*        - path: chemin a simplifier.                                      */
/*==========================================================================*/

void	FNAM_simplify_path (String &path)
{
	const long		drive_end_pos = path.find (':');

	path.find_replace ("\\", "/");
	path.find_replace ("/./", "/");
	path.find_replace ("//", "/", 1);

	if (drive_end_pos >= 0)
	{
		assert (drive_end_pos + 1 < path.get_len ());
		assert (path [drive_end_pos + 1] == '/');
	}

	// Remove all the unused "/xxx/.." but not "/../.."
	long				pos = 0;
	while (pos >= 0)
	{
		pos = path.find ("/..", pos);
		if (pos >= 0 && pos > 0)
		{
			const long		beg_pos = path.rfind ('/', pos - 1);
			bool				replace_flag = (beg_pos >= 0);
			long				erase_pos = beg_pos + 1;

			// Special case for relative pathnames
			if (   beg_pos < 0
				 && drive_end_pos < 0)
			{
				erase_pos = 0;
				replace_flag = true;
			}

			// Do not erase "../.." patterns
			if (   replace_flag
				 && pos - erase_pos == 2
				 && path [erase_pos    ] == '.'
				 && path [erase_pos + 1] == '.')
			{
				replace_flag = false;
			}

			if (replace_flag)
			{
				const long	erase_len = pos + 3 - (erase_pos);
				path.erase (erase_pos, erase_len);
				pos = MAX (erase_pos - 1L, 0L);
			}
			else
			{
				pos += 3;
			}
		}
	}
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

