/** \file paths.c
* Path and file name handling functions.
*/

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2017 Martin Fischer
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <dynstring.h>
#include "track.h"
#include "common.h"
#include "misc.h"
#include "uthash.h"
#include "paths.h"

struct pathTable {
	char type[ PATH_TYPE_SIZE]; 			/**< type of path */
	DynString path;							/**< path */
	UT_hash_handle hh; 						/**< makes this structure hashable */
};

static struct pathTable *paths;

static void AddPath(const char *type, char*path);
static struct pathTable *FindPath(const char *type);

#define PATHS_SECTION "paths"

/**
* Find the path for a given type in the hash table
*
* \param type IN the searched type
* \param entry OUT the table entry
* \return
*/

static struct pathTable *
FindPath(const char *type)
{
	struct pathTable *entry;
	HASH_FIND_STR(paths, type, entry);
	return (entry);
}

/**
 * Add a path to the table. If it already exists, the value list updated.
 *
 * \param type IN type of path
 * \param path  IN path
 */
static void
AddPath(const char *type, char*path)
{
	struct pathTable *tableEntry;
	tableEntry = FindPath(type);

	if (tableEntry) {
		DynStringClear(&(tableEntry->path));
	} else {
		tableEntry = malloc(sizeof(struct pathTable));
		DynStringMalloc(&tableEntry->path, 16);
		strcpy(tableEntry->type, type);
#ifdef WINDOWS
#pragma warning( disable : 4267)
#endif
		// This generates warning C4267 on windows
		HASH_ADD_STR(paths, type, tableEntry);
	}

	DynStringCatCStr(&(tableEntry->path), path);
}

/**
 * Update the current directory for a specific filetype. Get the directory part from the current file.
 * XTrackCAD keeps seprate directories for different filetypes.(for trackplans, bitmaps and DXF files).
 * The paths are stored in a hash table using the file type as the hash.
 *
 * \param pathType IN file type
 * \param fileName IN fully qualified filename
 * \return
 *
 */

void SetCurrentPath(
        const char * pathType,
        const char * fileName)
{
	char *path;
	char *copy;
	CHECK(fileName != NULL);
	CHECK(pathType != NULL);
	copy = strdup(fileName);
	path = strrchr(copy, FILE_SEP_CHAR[0]);

	if (path) {
		*path = '\0';
		AddPath(pathType, copy);
		wPrefSetString(PATHS_SECTION, pathType, copy);
	}

	free(copy);
}

/**
 * Get the current path for a given file type. Following options are searched:
 * 1. the hash array
 * 2. the preferences file for this type
 * 3. the older general setting (for backwards compatibility)
 * 4. the user's home directory as default
 * Search is finished when one of the options returns a valid path
 *
 * \param IN pathType the type
 * \return pointer to path of NULL if not existing
 */

char *GetCurrentPath(
        const char *pathType)
{
	struct pathTable *currentPath;
	const char *path;
	CHECK(pathType != NULL);
	currentPath = FindPath(pathType);

	if (currentPath) {
		return (DynStringToCStr(&(currentPath->path)));
	}

	path = wPrefGetString(PATHS_SECTION, pathType);

	if (!path) {
		path = wPrefGetString("file", "directory");
	}

	if (!path) {
		path = wGetUserHomeDir();
	}

	AddPath(pathType, (char *)path);
	return ((char *)path);
}

/**
 * Convert path to forward slash
 *
 * \param [in,out] string If non-null, the string.
 */

void ConvertPathForward(char *string)
{
	char *ptr = string;
	while ((ptr = strchr(ptr, '\\')) != NULL) {
		ptr[0] = '/';
		ptr++;
	}
}

/**
* Find the filename/extension piece in a fully qualified path
*
* \param path IN the full path
* \return pointer to the filename part
*/

char *FindFilename(char *path)
{
	char *name;
	name = strrchr(path, FILE_SEP_CHAR[0]);

	if (name) {
		name++;
	} else {
		name = path;
	}

	return (name);
}

/**
 * Find file extension in a filename
 *
 * \param path IN full or partial path
 * \return pointer to the file extension part, empty string if no extension present
 */

char *FindFileExtension(char *path)
{
	char *ext;
	ext = strrchr(path, '.');

	if (ext) {
		ext++;
	} else {
		ext = path + strlen(path);
	}

	return ext;
}

/**
* Make a full path definition from directorys and filenames. The individual pieces are
* Concatenated. Where necessary a path delimiter is added. A pointer to the resulting
* string is returned. This memory should be free'd when no longer needed.
* Windows: to construct an absolute path, a leading backslash has to be included after
* the drive delimiter ':' or at the beginning of the first directory name.
*
* \param str OUT pointer to the complete path
* \param ... IN one or more parts of the path, terminate with NULL
*/

void
MakeFullpath(char **str, ...)
{
	va_list valist;
	const char *part;
	char *separator = FILE_SEP_CHAR;
	char lastchar = '\0';
	DynString path;
	DynStringMalloc(&path, 0);
	va_start(valist, str);

	while ((part = va_arg(valist, const char *))) {
		if (part[0] !=separator[0] && lastchar && lastchar != separator[0] &&
		    lastchar != ':') {
			DynStringNCatCStr(&path, 1, separator);
		}

		DynStringCatCStr(&path, part);
		lastchar = part[strlen(part) - 1];
	}

	*str = strdup(DynStringToCStr(&path));
	DynStringFree(&path);
	va_end(valist);
}