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

        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	<assert.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>

#include	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"Directory.h"
#include	"file.h"
#include	"file_ct.h"
#include	"fnames.h"
#include	"log.h"
#include	"memory.h"




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

const int	Directory::SORT_MASK         = 0x0F;
const int	Directory::SORT_NONE         = 0x00;
const int	Directory::SORT_BY_NAME      = 0x01;
const int	Directory::SORT_BY_EXTENSION = 0x02;
const int	Directory::SORT_BY_DATE      = 0x03;
const int	Directory::SORT_BY_SIZE      = 0x04;
const int	Directory::SORT_INVERTED     = 0x10;	/* Flag d'inversion du tri */



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

const int	Directory::FILE_ARRAY_GRANULARITY = 1 << 4;	/* Toujours une puissance de 2 */



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



/*\\\ DEFINITION DES VARIABLES DE CLASSE PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ DEFINITION DES VARIABLES DE CLASSE PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ METHODES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: (constructeur)                                                 */
/*      Description: Initialise le repertoire                               */
/*==========================================================================*/

Directory::Directory (void)
{
	_nbr_files = 0;
	_file_info_ptr_ptr = (FILE_DIR_ENTRY **) MALLOC (  sizeof (*_file_info_ptr_ptr)
	                                                 * Directory::FILE_ARRAY_GRANULARITY);
	_first_file_flag = true;
}



/*==========================================================================*/
/*      Nom: (destructeur)                                                  */
/*      Description: Detruit le repertoire                                  */
/*==========================================================================*/

Directory::~Directory (void)
{
	int		file_count;

	for (file_count = 0; file_count < _nbr_files; file_count ++)
	{
		FREE (_file_info_ptr_ptr [file_count]->name_0);
		FREE (_file_info_ptr_ptr [file_count]);
	}

	if (_file_info_ptr_ptr != NULL)
	{
		FREE (_file_info_ptr_ptr);
		_file_info_ptr_ptr = NULL;
	}
}



/*==========================================================================*/
/*      Nom: check_ok                                                       */
/*      Description: Verifie l'intergrite de l'objet.                       */
/*      Retour: 0 si l'objet parait correct.                                */
/*==========================================================================*/

signed int	Directory::check_ok (void) const
{
	if (_file_info_ptr_ptr == NULL)
	{
		LOG_printf ("Directory::check_ok: Error: file array pointer is null.\n");
		return (-1);
	}

	MCHECK (_file_info_ptr_ptr);

	return (0);
}



/*==========================================================================*/
/*      Nom: self_display                                                   */
/*      Description: Affiche "textuellement" le contenu courant de l'objet. */
/*==========================================================================*/

void	Directory::self_display (void) const
{

	/*** A faire ***/

}



/*==========================================================================*/
/*      Nom: build                                                          */
/*      Description: Lit le repertoire specifie et construit l'objet avec   */
/*                   les informations recuperees.                           */
/*      Parametres en entree:                                               */
/*        - dir_path_0: pointeur sur le chemin du repertoire a charger.     */
/*        - mask_0: poitneur sur le masque de filtrage.                     */
/*      Parametres en entree/sortie:                                        */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	Directory::build (const char *dir_path_0, const char *mask_0)
{
	signed int	ret_val;

	if (clear ())
	{
		LOG_printf ("Directory::build: Error: couldn't clear directory.\n");
		return (-1);
	}

	do
	{
		ret_val = add_file (dir_path_0, mask_0);
		if (ret_val < 0)
		{
			return (-1);
		}
	}
	while (ret_val == 0);

	return (0);
}



/*==========================================================================*/
/*      Nom: clear                                                          */
/*      Description: Efface le contenu courant du repertoire.               */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	Directory::clear (void)
{
	int		file_count;
	void		*temp_ptr;

	_first_file_flag = true;

	if (_nbr_files == 0)
	{
		return (0);
	}

	for (file_count = 0; file_count < _nbr_files; file_count ++)
	{
		FREE (_file_info_ptr_ptr [file_count]->name_0);
		FREE (_file_info_ptr_ptr [file_count]);
	}
	_nbr_files = 0;

	temp_ptr = REALLOC (_file_info_ptr_ptr,
	                      sizeof (*_file_info_ptr_ptr)
	                    * Directory::FILE_ARRAY_GRANULARITY);
	if (temp_ptr == NULL)
	{
		LOG_printf ("Directory::clear: Error: couldn't realloc memory block.\n");
		return (-1);
	}
	_file_info_ptr_ptr = (FILE_DIR_ENTRY **) temp_ptr;

	return (0);
}



/*==========================================================================*/
/*      Nom: sort                                                           */
/*      Description: Trie les fichiers du repertoire selon un critere       */
/*                   donne.                                                 */
/*      Parametres en entree:                                               */
/*        - sort_type: type du tri.                                         */
/*==========================================================================*/

void		Directory::sort (int sort_type)
{
	int		file_count;
	int		file_count_2;
	int		folder_count;
	FILE_DIR_ENTRY	*temp_ptr;

	if (_nbr_files < 2)
	{
		return;
	}

	switch (sort_type & Directory::SORT_MASK)
	{
	case	Directory::SORT_BY_NAME:
		sort_by_extension ();
		sort_by_name ();
		break;

	case	Directory::SORT_BY_EXTENSION:
		sort_by_name ();
		sort_by_extension ();
		break;

	case	Directory::SORT_BY_DATE:
		sort_by_extension ();
		sort_by_name ();
		sort_by_date ();
		break;

	case	Directory::SORT_BY_SIZE:
		sort_by_extension ();
		sort_by_name ();
		sort_by_size ();
		break;
	}

	/* Inversion du classement (si demande) */
	if (sort_type & Directory::SORT_INVERTED)
	{
		for (file_count = 0; file_count < _nbr_files / 2; file_count ++)
		{
			temp_ptr = _file_info_ptr_ptr [_nbr_files - 1 - file_count];
			_file_info_ptr_ptr [_nbr_files - 1 - file_count] = _file_info_ptr_ptr [file_count];
			_file_info_ptr_ptr [file_count] = temp_ptr;
		}
	}

	/* Ramene tous les repertoires vers le haut */
	if ((sort_type & Directory::SORT_MASK) != Directory::SORT_NONE)
	{
		folder_count = 0;
		for (file_count = 0; file_count < _nbr_files; file_count ++)
		{
			temp_ptr = _file_info_ptr_ptr [file_count];
			if ((temp_ptr->attrib & 0x10) != 0)
			{
				for (file_count_2 = file_count; file_count_2 > folder_count; file_count_2 --)
				{
					_file_info_ptr_ptr [file_count_2] = _file_info_ptr_ptr [file_count_2 - 1];
				}
				_file_info_ptr_ptr [folder_count] = temp_ptr;
				folder_count ++;
			}
		}
	}
}



/*==========================================================================*/
/*      Nom: get_length                                                     */
/*      Description: renvoie le nombre de fichiers du repertoire.           */
/*      Retour: Nombre de fichiers.                                         */
/*==========================================================================*/

int		Directory::get_length (void) const
{
	return (_nbr_files);
}



/*==========================================================================*/
/*      Nom: get_file_info                                                  */
/*      Description: Renvoie les informations d'un fichier donne.           */
/*      Parametres en entree:                                               */
/*        - position: numero du fichier dans le repertoire (0 a ...)        */
/*      Retour: pointeur sur le descripteur du fichier.                     */
/*==========================================================================*/

const FILE_DIR_ENTRY	*Directory::get_file_info (int position) const
{
	assert (position < _nbr_files);

	return (_file_info_ptr_ptr [position]);
}



/*==========================================================================*/
/*      Nom: get_selected_flag                                              */
/*      Description: Renvoie l'etat de selection d'un fichier dans le       */
/*                   repertoire.                                            */
/*      Parametres en entree:                                               */
/*        - position: numero du fichier dans le repertoire (0 a ...)        */
/*      Retour: true si le fichier est selectionne, false sinon.            */
/*==========================================================================*/

bool		Directory::get_selected_flag (int position) const
{
	assert (position < _nbr_files);

	return (_file_info_ptr_ptr [position]->selected);
}



/*==========================================================================*/
/*      Nom: set_selected_flag                                              */
/*      Description: Fixe le flag de selection d'un fichier dans le         */
/*                   repertoire.                                            */
/*      Parametres en entree:                                               */
/*        - position: numero du fichier dans le repertoire (0 a ...)        */
/*        - state_flag: true si on doit selectionner le fichier, false si   */
/*                      on doit le deselectionner.                          */
/*==========================================================================*/

void		Directory::set_selected_flag (int position, bool state_flag)
{
	assert (position < _nbr_files);

	_file_info_ptr_ptr [position]->selected = state_flag;
}



/*\\\ METHODES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: add_file                                                       */
/*      Description: Ajoute un fichier au repertoire.                       */
/*      Parametres en entree:                                               */
/*        - dir_path_0: pointeur sur le chemin du repertoire a charger.     */
/*        - mask_0: poitneur sur le masque de filtrage.                     */
/*      Retour: 0 si tout s'est bien passe,                                 */
/*              1 si on a fini de lister le repertoire,                     */
/*              -1 si erreur.                                               */
/*==========================================================================*/

signed int	Directory::add_file (const char *dir_path_0, const char *mask_0)
{
	int		reserved_memory;
	void		*temp_ptr;
	FILE_DIR_ENTRY	temp_file_info;
	char		filename_0 [FNAM_FILENAME_MAXLEN+1];
	char		full_path_0 [FNAM_PATHNAME_MAXLEN+1];

	/* Premier fichier */
	if (_first_file_flag)
	{
		_first_file_flag = false;
		strcpy (full_path_0, dir_path_0);
		strcat (full_path_0, "*.*");
		if (! FILE_get_first_file (full_path_0, &temp_file_info, filename_0))
		{
			return (1);
		}
	}

	/* Fichiers suivants */
	else
	{
		if (! FILE_get_next_file (&temp_file_info, filename_0))
		{
			return (1);
		}
	}

	/* Ces deux repertoires virtuels ne doivent pas figurer dans la liste */
	if (   strcmp (filename_0, ".") == 0
	    || strcmp (filename_0, "..") == 0)
	{
		return (0);
	}

	/* Teste le fichier pour voir s'il est conforme au masque */
	if ((temp_file_info.attrib & 0x10) == 0)
	{
		if (! FNAM_test_filename (filename_0, mask_0))
		{
			return (0);
		}
	}

	/* Fait une petite place pour le nom */
	temp_file_info.name_0 = STRDUP (filename_0);
	if (temp_file_info.name_0 == NULL)
	{
		LOG_printf ("Directory::add_file: Error: couldn't allocate memory for filename.\n");
		return (-1);
	}

	/* Ajoute l'entree dans le repertoire */
	reserved_memory =   (_nbr_files + Directory::FILE_ARRAY_GRANULARITY - 1)
	                  & ~(Directory::FILE_ARRAY_GRANULARITY - 1);
	reserved_memory = MAX (reserved_memory, Directory::FILE_ARRAY_GRANULARITY);

	if (_nbr_files >= reserved_memory)
	{
		reserved_memory += Directory::FILE_ARRAY_GRANULARITY;
		temp_ptr = REALLOC (_file_info_ptr_ptr,
		                    sizeof (*_file_info_ptr_ptr) * reserved_memory);
		if (temp_ptr == NULL)
		{
			LOG_printf ("Directory::add_file: Error: couldn't reallocate memory for file index.\n");
			FREE (temp_file_info.name_0);
			return (-1);
		}
		_file_info_ptr_ptr = (FILE_DIR_ENTRY **) temp_ptr;
	}

	_file_info_ptr_ptr [_nbr_files] = (FILE_DIR_ENTRY *) MALLOC (sizeof (*(_file_info_ptr_ptr [_nbr_files])));
	if (_file_info_ptr_ptr [_nbr_files] == NULL)
	{
		LOG_printf ("Directory::add_file: Error: couldn't allocate memory for file information.\n");
		FREE (temp_file_info.name_0);
		return (-1);
	}
	*_file_info_ptr_ptr [_nbr_files] = temp_file_info;

	_nbr_files ++;

	return (0);
}



void	Directory::sort_by_name (void)
{
	int		file_count;
	int		file_count_2;
	int		scan_file;
	int		mem_file;
	size_t	string_len;
	FILE_DIR_ENTRY	*temp_ptr;
	char		*point_ptr;
	char		*string_0;
	char		string_1 [FNAM_FILENAME_MAXLEN+1];
	char		string_2 [FNAM_FILENAME_MAXLEN+1];

	for (file_count = 0; file_count < _nbr_files - 1; file_count ++)
	{
		mem_file = file_count;

		/* Decoupe le premier nom */
		string_0 = _file_info_ptr_ptr [mem_file]->name_0;
		point_ptr = strrchr (string_0, '.');
		if (point_ptr == NULL)
		{
			strcpy (string_1, string_0);
		}
		else
		{
			string_len = point_ptr - string_0;
			strcpy (string_1, string_0);
			string_1 [string_len] = 0;
		}

		for (scan_file = file_count+1; scan_file < _nbr_files; scan_file ++)
		{
			/* Decoupe le deuxieme nom */
			string_0 = _file_info_ptr_ptr [scan_file]->name_0;
			point_ptr = strrchr (string_0, '.');
			if (point_ptr == NULL)
			{
				strcpy (string_2, string_0);
			}
			else
			{
				string_len = point_ptr - string_0;
				strcpy (string_2, string_0);
				string_2 [string_len] = 0;
			}

			/* Comparaison */
			if (BASE_compare_string (string_2, string_1) < 0)
			{
				mem_file = scan_file;
				strcpy (string_1, string_2);
			}
		}

		if (mem_file != file_count)
		{
			/* Rotation de l'element */
			temp_ptr = _file_info_ptr_ptr [mem_file];
			for (file_count_2 = mem_file; file_count_2 > file_count; file_count_2 --)
			{
				_file_info_ptr_ptr [file_count_2] = _file_info_ptr_ptr [file_count_2 - 1];
			}
			_file_info_ptr_ptr [file_count] = temp_ptr;
		}
	}
}



void	Directory::sort_by_extension (void)
{
	int		file_count;
	int		file_count_2;
	int		scan_file;
	int		mem_file;
	FILE_DIR_ENTRY	*temp_ptr;
	char		*point_ptr;
	char		*string_0;
	char		string_1 [FNAM_FILENAME_MAXLEN+1];
	char		string_2 [FNAM_FILENAME_MAXLEN+1];

	for (file_count = 0; file_count < _nbr_files - 1; file_count ++)
	{
		mem_file = file_count;

		/* Decoupe la premiere extension */
		string_0 = _file_info_ptr_ptr [mem_file]->name_0;
		point_ptr = strrchr (string_0, '.');
		if (point_ptr == NULL)
		{
			string_1 [0] = 0;
		}
		else
		{
			strcpy (string_1, point_ptr);
		}

		for (scan_file = file_count+1; scan_file < _nbr_files; scan_file ++)
		{
			/* Decoupe la deuxieme extension */
			string_0 = _file_info_ptr_ptr [scan_file]->name_0;
			point_ptr = strrchr (string_0, '.');
			if (point_ptr == NULL)
			{
				string_2 [0] = '\0';
			}
			else
			{
				strcpy (string_2, point_ptr);
			}

			/* Comparaison */
			if (BASE_compare_string (string_2, string_1) < 0)
			{
				mem_file = scan_file;
				strcpy (string_1, string_2);
			}
		}

		if (mem_file != file_count)
		{
			/* Rotation de l'element */
			temp_ptr = _file_info_ptr_ptr [mem_file];
			for (file_count_2 = mem_file; file_count_2 > file_count; file_count_2 --)
			{
				_file_info_ptr_ptr [file_count_2] = _file_info_ptr_ptr [file_count_2 - 1];
			}
			_file_info_ptr_ptr [file_count] = temp_ptr;
		}
	}
}



void	Directory::sort_by_date (void)
{
	int		file_count;
	int		file_count_2;
	int		scan_file;
	int		mem_file;
	LWORD		cmp1a;
	LWORD		cmp1b;
	int		cmp2a;
	int		cmp2b;
	FILE_DIR_ENTRY	*temp_ptr;

	for (file_count = 0; file_count < _nbr_files - 1; file_count ++)
	{
		mem_file = file_count;
		cmp1a =   (_file_info_ptr_ptr [mem_file]->year << 16)
				  + (_file_info_ptr_ptr [mem_file]->month << 8)
				  + (_file_info_ptr_ptr [mem_file]->day);
		cmp1b =   (_file_info_ptr_ptr [mem_file]->hour << 8)
				  + (_file_info_ptr_ptr [mem_file]->minute);

		for (scan_file = file_count+1; scan_file < _nbr_files; scan_file ++)
		{
			cmp2a =   (_file_info_ptr_ptr [scan_file]->year << 16)
					  + (_file_info_ptr_ptr [scan_file]->month << 8)
					  + (_file_info_ptr_ptr [scan_file]->day);
			cmp2b =   (_file_info_ptr_ptr [scan_file]->hour << 8)
					  + (_file_info_ptr_ptr [scan_file]->minute);

			if (cmp2a < cmp1a)
			{
				mem_file = scan_file;
				cmp1a = cmp2a;
				cmp1b = cmp2b;
			}
			else if (cmp2a == cmp1a)
			{
				if (cmp2b < cmp1b)
				{
					mem_file = scan_file;
					cmp1a = cmp2a;
					cmp1b = cmp2b;
				}
			}
		}

		if (mem_file != file_count)
		{
			/* Rotation de l'element */
			temp_ptr = _file_info_ptr_ptr [mem_file];
			for (file_count_2 = mem_file; file_count_2 > file_count; file_count_2 --)
			{
				_file_info_ptr_ptr [file_count_2] = _file_info_ptr_ptr [file_count_2 - 1];
			}
			_file_info_ptr_ptr [file_count] = temp_ptr;
		}
	}
}



void	Directory::sort_by_size (void)
{
	int		file_count;
	int		file_count_2;
	int		scan_file;
	int		mem_file;
	FILE_DIR_ENTRY	*temp_ptr;

	for (file_count = 0; file_count < _nbr_files - 1; file_count ++)
	{
		mem_file = file_count;
		for (scan_file = file_count+1; scan_file < _nbr_files; scan_file ++)
		{
			if (_file_info_ptr_ptr [scan_file]->length < _file_info_ptr_ptr [mem_file]->length)
			{
				mem_file = scan_file;
			}
		}

		if (mem_file != file_count)
		{
			/* Rotation de l'element */
			temp_ptr = _file_info_ptr_ptr [mem_file];
			for (file_count_2 = mem_file; file_count_2 > file_count; file_count_2 --)
			{
				_file_info_ptr_ptr [file_count_2] = _file_info_ptr_ptr [file_count_2 - 1];
			}
			_file_info_ptr_ptr [file_count] = temp_ptr;
		}
	}
}



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



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

