/** \file fileio.c
 * Loading and saving files. Handles trackplans as well as DXF export.
 *
 * $Header: /home/dmarkle/xtrkcad-fork-cvs/xtrkcad/app/bin/fileio.c,v 1.18 2009-05-08 15:28:54 m_fischer Exp $
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <stdio.h>
#ifndef WINDOWS
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#endif
#include <math.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#ifdef WINDOWS
#include <io.h>
#include <windows.h>
	#if _MSC_VER >=1400
		#define strdup _strdup
	#endif
#else
#endif
#include <sys/stat.h>
#include <stdarg.h>
#include <locale.h>

#include <stdint.h>

#include "track.h"
#include "version.h"
#include "common.h"
#include "utility.h"
#include "draw.h"
#include "misc.h"
#include "compound.h"
#include "i18n.h"

/*#define TIME_READTRACKFILE*/

EXPORT const char * workingDir;
EXPORT const char * libDir;

static char * customPath = NULL;
static char * customPathBak = NULL;

EXPORT char curPathName[STR_LONG_SIZE];
EXPORT char * curFileName;
EXPORT char curDirName[STR_LONG_SIZE];

EXPORT char * clipBoardN;

EXPORT wBool_t executableOk = FALSE;

static int log_paramFile;

EXPORT void SetCurDir(
		const char * pathName,
		const char * fileName )
{
	memcpy( curDirName, pathName, fileName-pathName );
	curDirName[fileName-pathName-1] = '\0';
	wPrefSetString( "file", "directory", curDirName );
}

#ifdef WINDOWS
#define rename( F1, F2 ) Copyfile( F1, F2 )

static int Copyfile( char * fn1, char * fn2 )
{
	FILE *f1, *f2;
	size_t size;
	f1 = fopen( fn1, "r" );
	if ( f1 == NULL )
		return 0;
	f2 = fopen( fn2, "w" );
	if ( f2 == NULL ) {
		fclose( f1 );
		return -1;
	}
	while ( (size=fread( message, 1, sizeof message, f1 )) > 0 )
		fwrite( message, size, 1, f2 );
	fclose( f1 );
	fclose( f2 );
	return 0;
}
#endif

/**
 * Save the old locale and set to new.
 *
 * \param newlocale IN the new locale to set
 * \return    pointer to the old locale
 */

char *
SaveLocale( char *newLocale )
{
	char *oldLocale;
	char *saveLocale = NULL;

	/* get old locale setting */
	oldLocale = setlocale(LC_ALL, NULL);

	/* allocate memory to save */
	if (oldLocale)
		saveLocale = strdup( oldLocale );

	setlocale(LC_ALL, newLocale );

	return( saveLocale );
}

/**
 * Restore a previously saved locale.
 *
 * \param locale IN return value from earlier call to SaveLocale
 */

void
RestoreLocale( char * locale )
{
	if( locale ) {
		setlocale( LC_ALL, locale );
		free( locale );
	}
}


/****************************************************************************
 *
 * PARAM FILE INPUT
 *
 */

EXPORT FILE * paramFile = NULL;
EXPORT char paramFileName[STR_LONG_SIZE];
EXPORT wIndex_t paramLineNum = 0;
EXPORT char paramLine[STR_LONG_SIZE];
EXPORT char * curContents;
EXPORT char * curSubContents;
static long paramCheckSum;

#define PARAM_DEMO (-1)

typedef struct {
		char * name;
		readParam_t proc;
		} paramProc_t;
static dynArr_t paramProc_da;
#define paramProc(N) DYNARR_N( paramProc_t, paramProc_da, N )


EXPORT void Stripcr( char * line )
{
	char * cp;
	cp = line + strlen(line);
	if (cp == line)
		return;
	cp--;
	if (*cp == '\n')
		*cp-- = '\0';
	if (cp >= line && *cp == '\r')
		*cp = '\0';
}

EXPORT void ParamCheckSumLine( char * line )
{
	long mult=1;
	while ( *line )
		paramCheckSum += (((long)(*line++))&0xFF)*(mult++);
}

EXPORT char * GetNextLine( void )
{
	if (!paramFile) {
		paramLine[0] = '\0';
		return NULL;
	}
	if (fgets( paramLine, sizeof paramLine, paramFile ) == NULL) {
		AbortProg( "Permature EOF on %s", paramFileName );
	}
	Stripcr( paramLine );
	ParamCheckSumLine( paramLine );
	paramLineNum++;
	return paramLine;
}


/**
 * Show an error message if problems occur during loading of a param or layout file.
 * The user has the choice to cancel the operation or to continue. If operation is
 * canceled the open file is closed.
 *
 * \param IN msg error message
 * \param IN showLine set to true if current line should be included in error message
 * \param IN ... variable number additional error information
 * \return TRUE to continue, FALSE to abort operation
 *
 */

EXPORT int InputError(
		char * msg,
		BOOL_T showLine,
		... )
{
	va_list ap;
	char * mp = message;
	int ret;

	mp += sprintf( message, "INPUT ERROR: %s:%d\n",
		paramFileName, paramLineNum );
	va_start( ap, showLine );
	mp += vsprintf( mp, msg, ap );
	va_end( ap );
	if (showLine) {
		*mp++ = '\n';
		strcpy( mp, paramLine );
	}
	strcat( mp, _("\nDo you want to continue?") );
	if (!(ret = wNoticeEx( NT_ERROR, message, _("Continue"), _("Stop") ))) {
		if ( paramFile )
			fclose(paramFile);
		paramFile = NULL;
	}
	return ret;
}


EXPORT void SyntaxError(
		char * event,
		wIndex_t actual,
		wIndex_t expected )
{
	InputError( "%s scan returned %d (expected %d)",
		TRUE, event, actual, expected );
}

/**
 * Parse a line in XTrackCAD's file format
 *
 * \param line IN line to parse
 * \param format IN ???
 *
 * \return FALSE in case of parsing error, TRUE on success
 */

EXPORT BOOL_T GetArgs(
		char * line,
		char * format,
		... )
{
	char * cp, * cq;
	int argNo;
	long * pl;
	unsigned long *pul;
	int * pi;
	FLOAT_T *pf;
	coOrd p, *pp;
	char * ps;
	char ** qp;
	va_list ap;
	char *oldLocale = NULL;

	oldLocale = SaveLocale("C");

	cp = line;
	va_start( ap, format );
	for (argNo=1;*format;argNo++,format++) {
		while (isspace((unsigned char)*cp)) cp++;
		if (!*cp && strchr( "XZYzc", *format ) == NULL ) {
			RestoreLocale(oldLocale);
			InputError( "Arg %d: EOL unexpected", TRUE, argNo );
			return FALSE;
		}
		switch (*format) {
		case '0':
			(void)strtol( cp, &cq, 10 );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected integer", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			break;
		case 'X':
			pi = va_arg( ap, int * );
			*pi = 0;
			break;
		case 'Z':
			pl = va_arg( ap, long * );
			*pl = 0;
			break;
		case 'Y':
			pf = va_arg( ap, FLOAT_T * );
			*pf = 0;
			break;
		case 'L':
			pi = va_arg( ap, int * );
			*pi = (int)strtol( cp, &cq, 10 );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected integer", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			break;
		case 'd':
			pi = va_arg( ap, int * );
			*pi = (int)strtol( cp, &cq, 10 );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected integer", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			break;
		case 'w':
			pf = va_arg( ap, FLOAT_T * );
			*pf = (FLOAT_T)strtol( cp, &cq, 10 );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected integer", TRUE, argNo );
				return FALSE;
			}
			if (*cq == '.')
				*pf = strtod( cp, &cq );
			else
				*pf /= mainD.dpi;
			cp = cq;
			break;
		case 'u':
			pul = va_arg( ap, unsigned long * );
			*pul = strtoul( cp, &cq, 10 );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected integer", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			break;
		case 'l':
			pl = va_arg( ap, long * );
			*pl = strtol( cp, &cq, 10 );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected integer", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			break;
		case 'f':
			pf = va_arg( ap, FLOAT_T * );
			*pf = strtod( cp, &cq );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected float", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			break;
		case 'z':
			pf = va_arg( ap, FLOAT_T * );
#ifdef LATER
			if ( paramVersion >= 9 ) {
				*pf = strtod( cp, &cq );
				if (cp == cq) {
					RestoreLocale(oldLocale);
					InputError( "Arg %d: expected float", TRUE, argNo );
					return FALSE;
				}
				cp = cq;
			} else {
				*pf = 0.0;
			}
#endif
			*pf = 0.0;
			break;
		case 'p':
			pp = va_arg( ap, coOrd * );
			p.x = strtod( cp, &cq );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected float", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			p.y = strtod( cp, &cq );
			if (cp == cq) {
				RestoreLocale(oldLocale);
				InputError( "Arg %d: expected float", TRUE, argNo );
				return FALSE;
			}
			cp = cq;
			*pp = p;
			break;
		case 's':
			ps = va_arg( ap, char * );
			while (isspace((unsigned char)*cp)) cp++;
			while (*cp && !isspace((unsigned char)*cp)) *ps++ = *cp++;
			*ps++ = '\0';
			break;
		case 'q':
			qp = va_arg( ap, char * * );
			if (*cp != '\"')
				/* Stupid windows */
				cq = strchr( cp, '\"' );
			else
				cq = cp;
			if (cq!=NULL) {
				cp = cq;
				ps = &message[0];
				cp++;
				while (*cp) {
					if ( (ps-message)>=sizeof message)
						AbortProg( "Quoted title argument too long" );
					if (*cp == '\"') {
						if (*++cp == '\"') {
							*ps++ = '\"';
						} else {
							*ps = '\0';
							cp++;
							break;
						}
					} else {
						*ps++ = *cp;
					}
					cp++;
				}
				*ps = '\0';
			} else {
				message[0] = '\0';
			}
			*qp = (char*)MyStrdup(message);
			break;
		case 'c':
			qp = va_arg( ap, char * * );
			while (isspace((unsigned char)*cp)) cp++;
			if (*cp)
				*qp = cp;
			else
				*qp = NULL;
			break;
		default:
			AbortProg( "getArgs: bad format char" );
		}
	}
	va_end( ap );
	RestoreLocale(oldLocale);
	return TRUE;
}

EXPORT wBool_t ParseRoomSize(
		char * s,
		coOrd * roomSizeRet )
{
	coOrd size;
	char *cp;

	size.x = strtod( s, &cp );
	if (cp != s) {
		s = cp;
		while (isspace((unsigned char)*s)) s++;
		if (*s == 'x' || *s == 'X') {
			size.y = strtod( ++s, &cp );
			if (cp != s) {
#ifdef LATER
				if (units == UNITS_METRIC) {
					size.x /= 2.54;
					size.y /= 2.54;
				}
#endif
				*roomSizeRet = size;
				return TRUE;
			}
		}
	}
	return FALSE;
}


EXPORT void AddParam(
		char * name,
		readParam_t proc )
{
	DYNARR_APPEND( paramProc_t, paramProc_da, 10 );
	paramProc(paramProc_da.cnt-1).name = name;
	paramProc(paramProc_da.cnt-1).proc = proc;
}


EXPORT BOOL_T ReadParams(
		long key,
		const char * dirName,
		const char * fileName )
{
	FILE * oldFile;
	char *cp;
	wIndex_t oldLineNum;
	wIndex_t pc;
	long oldCheckSum;
	long checkSum=0;
	BOOL_T checkSummed;
	long paramVersion = -1;
	char *oldLocale = NULL;

	if (dirName) {
		strcpy( paramFileName, dirName );
		strcat( paramFileName, FILE_SEP_CHAR );
		strcat( paramFileName, fileName );
	} else {
		strcpy( paramFileName, fileName );
	}
	paramLineNum = 0;
	curBarScale = -1;
	curContents = strdup( fileName );
	curSubContents = curContents;

LOG1( log_paramFile, ("ReadParam( %s )\n", fileName ) )

	oldLocale = SaveLocale("C");

	paramFile = fopen( paramFileName, "r" );
	if (paramFile == NULL) {
		/* Reset the locale settings */
		RestoreLocale( oldLocale );

		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Parameter"), paramFileName, strerror(errno) );

		return FALSE;
	}
	paramCheckSum = key;
	paramLineNum = 0;
	checkSummed = FALSE;
	while ( paramFile && ( fgets(paramLine, 256, paramFile) ) != NULL ) {
		paramLineNum++;
		Stripcr( paramLine );
		if (strncmp( paramLine, "CHECKSUM ", 9 ) == 0) {
			checkSum = atol( paramLine+9 );
			checkSummed = TRUE;
			goto nextLine;
		}
		ParamCheckSumLine( paramLine );
		if (paramLine[0] == '#') {
			/* comment */
		} else if (paramLine[0] == 0) {
			/* empty paramLine */
		} else if (strncmp( paramLine, "INCLUDE ", 8 ) == 0) {
			cp = &paramLine[8];
			while (*cp && isspace((unsigned char)*cp)) cp++;
			if (!*cp) {
				InputError( "INCLUDE - no file name", TRUE );

				/* Close file and reset the locale settings */
				if (paramFile) fclose(paramFile);
				RestoreLocale( oldLocale );

				return FALSE;
			}
			oldFile = paramFile;
			oldLineNum = paramLineNum;
			oldCheckSum = paramCheckSum;
			ReadParams( key, dirName, cp );
			paramFile = oldFile;
			paramLineNum = oldLineNum;
			paramCheckSum = oldCheckSum;
			if (dirName) {
				strcpy( paramFileName, dirName );
				strcat( paramFileName, FILE_SEP_CHAR );
				strcat( paramFileName, fileName );
			} else {
				strcpy( paramFileName, fileName );
			}
		} else if (strncmp( paramLine, "CONTENTS ", 9) == 0 ) {
			curContents = MyStrdup( paramLine+9 );
			curSubContents = curContents;
		} else if (strncmp( paramLine, "SUBCONTENTS ", 12) == 0 ) {
			curSubContents = MyStrdup( paramLine+12 );
		} else if (strncmp( paramLine, "PARAM ", 6) == 0 ) {
			paramVersion = atol( paramLine+6 );
		} else {
			for (pc = 0; pc < paramProc_da.cnt; pc++ ) {
				if (strncmp( paramLine, paramProc(pc).name,
							 strlen(paramProc(pc).name)) == 0 ) {
					paramProc(pc).proc( paramLine );
					goto nextLine;
				}
			}
			InputError( "Unknown param line", TRUE );
		}
 nextLine:;
	}
	if ( key ) {
		if ( !checkSummed || checkSum != paramCheckSum ) {
			/* Close file and reset the locale settings */
			if (paramFile) fclose(paramFile);
			RestoreLocale( oldLocale );

			NoticeMessage( MSG_PROG_CORRUPTED, _("Ok"), NULL, paramFileName );

			return FALSE;
		}
	}
	if (paramFile)fclose( paramFile );

	RestoreLocale( oldLocale );

	return TRUE;
}


static void ReadCustom( void )
{
	FILE * f;
	customPath =
		(char*)MyMalloc( strlen(workingDir) + 1 + strlen(sCustomF) + 1 );
	sprintf( customPath, "%s%s%s", workingDir, FILE_SEP_CHAR, sCustomF );
	customPathBak = MyStrdup( customPath );
	customPathBak[ strlen(customPathBak)-1 ] = '1';
	f = fopen( customPath, "r" );
	if ( f != NULL ) {
		fclose( f );
		curParamFileIndex = PARAM_CUSTOM;
		ReadParams( 0, workingDir, sCustomF );
	}
}


/*
 * Open the file and then set the locale to "C". Old locale will be copied to
 * oldLocale. After the required file I/O is done, the caller must call
 * CloseCustom() with the same locale value that was returned in oldLocale by
 * this function.
 */
EXPORT FILE * OpenCustom( char *mode )
{
	FILE * ret = NULL;

	if (inPlayback)
		return NULL;
	if ( *mode == 'w' )
		rename( customPath, customPathBak );
	if (customPath) {
		ret = fopen( customPath, mode );
		if (ret == NULL) {
			NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Custom"), customPath, strerror(errno) );
		}
	}

	return ret;
}


EXPORT char * PutTitle( char * cp )
{
	static char title[STR_SIZE];
	char * tp = title;
	while (*cp && (tp-title)<=(sizeof title)-3) {
		if (*cp == '\"') {
			*tp++ = '\"';
			*tp++ = '\"';
		} else {
			*tp++ = *cp;
		}
		cp++;
	}
	if ( *cp )
		NoticeMessage( _("putTitle: title too long: %s"), _("Ok"), NULL, title );
	*tp = '\0';
	return title;
}

/**
 * Set the title of the main window. After loading a file or changing a design
 * this function is called to set the filename and the changed mark in the title
 * bar.
 */

void SetWindowTitle( void )
{
	if ( changed > 2 || inPlayback )
		return;
	sprintf( message, "%s%s - %s(%s)",
		(curFileName==NULL||curFileName[0]=='\0')?_("Unnamed Trackplan"):curFileName,
		changed>0?"*":"", sProdName, sVersion );
	wWinSetTitle( mainW, message );
}

/*****************************************************************************
 *
 * LOAD / SAVE TRACKS
 *
 */

static struct wFilSel_t * loadFile_fs;
static struct wFilSel_t * saveFile_fs;

static wWin_p checkPointingW;
static paramData_t checkPointingPLs[] = {
   {    PD_MESSAGE, N_("Check Pointing") } };
static paramGroup_t checkPointingPG = { "checkpoint", 0, checkPointingPLs, sizeof checkPointingPLs/sizeof checkPointingPLs[0] };

static char * checkPtFileName1;
static char * checkPtFileName2;

/** Read the layout design.
 *
 * \param IN pathName filename including directory
 * \param IN fileName pointer to filename part in pathName
 * \param IN full
 * \param IN noSetCurDir if FALSE current diurectory is changed to file location
 * \param IN complain  if FALSE error messages are supressed
 *
 * \return FALSE in case of load error
 */

static BOOL_T ReadTrackFile(
		const char * pathName,
		const char * fileName,
		BOOL_T full,
		BOOL_T noSetCurDir,
		BOOL_T complain )
{
	int count;
	coOrd roomSize;
	long scale;
	char * cp;
	char *oldLocale = NULL;
	int ret = TRUE;

	oldLocale = SaveLocale( "C" );

	paramFile = fopen( pathName, "r" );
	if (paramFile == NULL) {
		/* Reset the locale settings */
		RestoreLocale( oldLocale );

		if ( complain )
			NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, sProdName, pathName, strerror(errno) );

		return FALSE;
	}

	paramLineNum = 0;
	strcpy( paramFileName, fileName );

	InfoMessage("0");
	count = 0;
	while ( paramFile && ( fgets(paramLine, sizeof paramLine, paramFile) ) != NULL ) {
		count++;
		if (count%10 == 0) {
			InfoMessage( "%d", count );
			wFlush();
		}
		paramLineNum++;
		if (strlen(paramLine) == (sizeof paramLine) -1 &&
			paramLine[(sizeof paramLine)-1] != '\n') {
			if( !(ret = InputError( "Line too long", TRUE )))
				break;
		}
		Stripcr( paramLine );
		if (paramLine[0] == '#' ||
			paramLine[0] == '\n' ||
			paramLine[0] == '\0' ) {
			/* comment */
			continue;
		}

		if (ReadTrack( paramLine )) {

		} else if (strncmp( paramLine, "END", 3 ) == 0) {
			break;
		} else if (strncmp( paramLine, "VERSION ", 8 ) == 0) {
			paramVersion = strtol( paramLine+8, &cp, 10 );
			if (cp)
				while (*cp && isspace((unsigned char)*cp)) cp++;
			if ( paramVersion > iParamVersion ) {
				if (cp && *cp) {
					NoticeMessage( MSG_UPGRADE_VERSION1, _("Ok"), NULL, paramVersion, iParamVersion, sProdName, cp );
				} else {
					NoticeMessage( MSG_UPGRADE_VERSION2, _("Ok"), NULL, paramVersion, iParamVersion, sProdName );
				}
				break;
			}
			if ( paramVersion < iMinParamVersion ) {
				NoticeMessage( MSG_BAD_FILE_VERSION, _("Ok"), NULL, paramVersion, iMinParamVersion, sProdName );
				break;
			}
		} else if (!full) {
			if( !(ret = InputError( "unknown command", TRUE )))
				break;
		} else if (strncmp( paramLine, "TITLE1 ", 7 ) == 0) {
			strncpy( Title1, &paramLine[7], TITLEMAXLEN );
			Title1[ TITLEMAXLEN - 1 ] = '\0';
			/*wStringSetValue( title1PD.control, Title1 );*/
		} else if (strncmp( paramLine, "TITLE2 ", 7 ) == 0) {
			strncpy( Title2, &paramLine[7], TITLEMAXLEN );
			Title2[ TITLEMAXLEN - 1 ] = '\0';
			/*wStringSetValue( title2PD.control, Title2 );*/
		} else if (strncmp( paramLine, "ROOMSIZE", 8 ) == 0) {
			if ( ParseRoomSize( paramLine+8, &roomSize ) ) {
				SetRoomSize( roomSize );
				/*wFloatSetValue( roomSizeXPD.control, PutDim(roomSize.x) );*/
				/*wFloatSetValue( roomSizeYPD.control, PutDim(roomSize.y) );*/
			} else {
				if( !(ret = InputError( "ROOMSIZE: bad value", TRUE )))
					break;
			}
		} else if (strncmp( paramLine, "SCALE ", 6 ) == 0) {
			if ( !DoSetScale( paramLine+5 ) ) {
				if( !(ret = InputError( "SCALE: bad value", TRUE )))
					break;
			}
		} else if (strncmp( paramLine, "MAPSCALE ", 9 ) == 0) {
			scale = atol( paramLine+9 );
			if (scale > 1) {
				mapD.scale = mapScale = scale;
			}
		} else if (strncmp( paramLine, "LAYERS ", 7 ) == 0) {
			ReadLayers( paramLine+7 );
		} else {
			if( !(ret = InputError( "unknown command", TRUE )))
				break;
		}
	}

	if (paramFile)
		fclose(paramFile);

	if( ret ) {
		if (!noSetCurDir)
			SetCurDir( pathName, fileName );

		if (full) {
			strcpy( curPathName, pathName );
			curFileName = &curPathName[fileName-pathName];
			SetWindowTitle();
		}
	}

	RestoreLocale( oldLocale );

	paramFile = NULL;
	InfoMessage( "%d", count );
	return ret;
}


EXPORT int LoadTracks(
		const char * pathName,
		const char * fileName,
		void * data)
{
#ifdef TIME_READTRACKFILE
	long time0, time1;
#endif
	if (pathName == NULL)
		return TRUE;
	paramVersion = -1;
	wSetCursor( wCursorWait );
	Reset();
	ClearTracks();
/*	DefaultLayerProperties(); */
	ResetLayers();
	checkPtMark = changed = 0;
	UndoSuspend();
	useCurrentLayer = FALSE;
#ifdef TIME_READTRACKFILE
	time0 = wGetTimer();
#endif
	if (ReadTrackFile( pathName, fileName, TRUE, FALSE, TRUE )) {
		wMenuListAdd( fileList_ml, 0, fileName, MyStrdup(pathName) );
		ResolveIndex();
#ifdef TIME_READTRACKFILE
		time1 = wGetTimer();
		printf( "time= %ld ms \n", time1-time0 );
#endif
		RecomputeElevations();
		AttachTrains();
		DoChangeNotification( CHANGE_ALL );
		DoUpdateTitles();
		LoadLayerLists();
	}
	UndoResume();
	/*DoRedraw();*/
	Reset();
	wSetCursor( wCursorNormal );
	return TRUE;
}

/**
 * Load the layout specified by data. Filename may contain a full
 * path.
 * \param index IN ignored
 * \param label IN ignored
 * \param data IN filename
 */

EXPORT void DoFileList(
		int index,
		char * label,
		void * data )
{
	char * fileName, * pathName = (char*)data;
	fileName = strrchr( pathName, FILE_SEP_CHAR[0] );
	if (fileName == NULL)
		fileName = pathName;
	else
		fileName++;
	LoadTracks( pathName, fileName, NULL );
}


static BOOL_T DoSaveTracks(
		const char * fileName )
{
	FILE * f;
	time_t clock;
	BOOL_T rc = TRUE;
	char *oldLocale = NULL;

	oldLocale = SaveLocale( "C" );

	f = fopen( fileName, "w" );
	if (f==NULL) {
		RestoreLocale( oldLocale );

		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Track"), fileName, strerror(errno) );

		return FALSE;
	}
	wSetCursor( wCursorWait );
	time(&clock);
	rc &= fprintf(f,"#%s Version: %s, Date: %s\n", sProdName, sVersion, ctime(&clock) )>0;
	rc &= fprintf(f, "VERSION %d %s\n", iParamVersion, PARAMVERSIONVERSION )>0;
	Stripcr( Title1 );
	Stripcr( Title2 );
	rc &= fprintf(f, "TITLE1 %s\n", Title1 )>0;
	rc &= fprintf(f, "TITLE2 %s\n", Title2 )>0;
	rc &= fprintf(f, "MAPSCALE %ld\n", (long)mapD.scale )>0;
	rc &= fprintf(f, "ROOMSIZE %0.6f x %0.6f\n", mapD.size.x, mapD.size.y )>0;
	rc &= fprintf(f, "SCALE %s\n", curScaleName )>0;
	rc &= WriteLayers( f );
	rc &= WriteMainNote( f );
	rc &= WriteTracks( f );
	rc &= fprintf(f, "END\n")>0;
	if ( !rc )
		NoticeMessage( MSG_WRITE_FAILURE, _("Ok"), NULL, strerror(errno), fileName );
	fclose(f);

	RestoreLocale( oldLocale );

	checkPtMark = changed;
	wSetCursor( wCursorNormal );
	return rc;
}


static doSaveCallBack_p doAfterSave;

static int SaveTracks(
		const char * pathName,
		const char * fileName,
		void * data )
{
	if (pathName == NULL)
		return TRUE;
	SetCurDir( pathName, fileName );
	DoSaveTracks( pathName );
	wMenuListAdd( fileList_ml, 0, fileName, MyStrdup(pathName) );
	checkPtMark = changed = 0;
	if (curPathName != pathName)
	  strcpy( curPathName, pathName );
	curFileName = &curPathName[fileName-pathName];
	if (doAfterSave)
		doAfterSave();
	doAfterSave = NULL;
	return TRUE;
}


EXPORT void DoSave( doSaveCallBack_p after )
{
	doAfterSave = after;
	if (curPathName[0] == 0) {
		if (saveFile_fs == NULL)
			saveFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Save Tracks"),
				sSourceFilePattern, SaveTracks, NULL );
		wFilSelect( saveFile_fs, curDirName );
	} else {
		SaveTracks( curPathName, curFileName, NULL );
	}
	SetWindowTitle();
}

EXPORT void DoSaveAs( doSaveCallBack_p after )
{
	doAfterSave = after;
	if (saveFile_fs == NULL)
		saveFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Save Tracks"),
			sSourceFilePattern, SaveTracks, NULL );
	wFilSelect( saveFile_fs, curDirName );
	SetWindowTitle();
}

EXPORT void DoLoad( void )
{
	loadFile_fs = wFilSelCreate( mainW, FS_LOAD, 0, _("Open Tracks"),
		sSourceFilePattern, LoadTracks, NULL );
	wFilSelect( loadFile_fs, curDirName );
}


EXPORT void DoCheckPoint( void )
{
	int rc;

	if (checkPointingW == NULL) {
		ParamRegister( &checkPointingPG );
		checkPointingW = ParamCreateDialog( &checkPointingPG, MakeWindowTitle(_("Check Pointing")), NULL, NULL, NULL, FALSE, NULL, F_TOP|F_CENTER, NULL );
	}
	rename( checkPtFileName1, checkPtFileName2 );
	wShow( checkPointingW );
	rc = DoSaveTracks( checkPtFileName1 );

	/* could the check point file be written ok? */
	if( rc ) {
		/* yes, delete the backup copy of the checkpoint file */
		remove( checkPtFileName2 );
	} else {
		/* no, rename the backup copy back to the checkpoint file name */
		rename( checkPtFileName2, checkPtFileName1 );
	}
	wHide( checkPointingW );
}

/**
 * Remove all temporary files before exiting.When the program terminates
 * normally through the exit choice, files that are created temporarily are removed:
 * xtrkcad.ckp
 *
 * \param none
 * \return none
 *
 */

EXPORT void CleanupFiles( void )
{
	if( checkPtFileName1 )
		remove( checkPtFileName1 );
}

/**
 * Check for existance of checkpoint file. Existance of a checkpoint file means that XTrkCAD was not properly
 * terminated.
 *
 * \param none
 * \return TRUE if exists, FALSE otherwise
 *
 */

EXPORT int ExistsCheckpoint( void )
{
	int len;
	char *pattern = sCheckPointF;
	char *search;

	struct stat fileStat;

	len = strlen( workingDir ) + 1 + strlen( sCheckPointF ) + 1;
	checkPtFileName1 = (char*)MyMalloc(len);
	sprintf( checkPtFileName1, "%s%s%s", workingDir, FILE_SEP_CHAR, sCheckPointF );
	checkPtFileName2 = (char*)MyMalloc(len);
	sprintf( checkPtFileName2, "%s%s%s", workingDir, FILE_SEP_CHAR, sCheckPoint1F );

	len = strlen( workingDir ) + 1 + strlen( pattern ) + 1;
	search = (char*)MyMalloc(len);
	sprintf( search, "%s%s%s", workingDir, FILE_SEP_CHAR, pattern );

	if( !stat( search, &fileStat ) ) {
		MyFree( search );
		return TRUE;
	} else {
		MyFree( search );
		return FALSE;
	}


#ifdef LATER
	DIR *dir;

	dir = opendir( search );
	MyFree( search );

	if( dir )	{
		closedir( dir );
		return TRUE;
	} else {
		return FALSE;
	}
#endif

}

/**
 * Load checkpoint file
 *
 * \return TRUE if exists, FALSE otherwise
 *
 */

EXPORT int LoadCheckpoint( void )
{
	int len;
	char *search;

	paramVersion = -1;
	wSetCursor( wCursorWait );

	len = strlen( workingDir ) + 1 + strlen( sCheckPointF ) + 1;
	search = (char*)MyMalloc(len);
	sprintf( search, "%s%s%s", workingDir, FILE_SEP_CHAR, sCheckPointF );

	UndoSuspend();

	if (ReadTrackFile( search, search + strlen(search) - strlen( sCheckPointF ), TRUE, TRUE, TRUE )) {
		ResolveIndex();

		RecomputeElevations();
		AttachTrains();
		DoChangeNotification( CHANGE_ALL );
		DoUpdateTitles();
	}

	Reset();
	UndoResume();

	wSetCursor( wCursorNormal );

	strcpy( curPathName, "" );
	curFileName = curPathName;
	SetWindowTitle();
	changed = TRUE;
	MyFree( search );
	return TRUE;
}

/*****************************************************************************
 *
 * IMPORT / EXPORT
 *
 */

static struct wFilSel_t * exportFile_fs;
static struct wFilSel_t * importFile_fs;
static struct wFilSel_t * exportDXFFile_fs;


static int ImportTracks(
		const char * pathName,
		const char * fileName,
		void * data )
{
	long paramVersionOld = paramVersion;

	if (pathName == NULL)
		return TRUE;
	paramVersion = -1;
	wSetCursor( wCursorWait );
	Reset();
	SetAllTrackSelect( FALSE );
	ImportStart();
	UndoStart( _("Import Tracks"), "importTracks" );
	useCurrentLayer = TRUE;
	ReadTrackFile( pathName, fileName, FALSE, FALSE, TRUE );
	ImportEnd();
	/*DoRedraw();*/
	EnableCommands();
	wSetCursor( wCursorNormal );
	paramVersion = paramVersionOld;
	importMove = TRUE;
	DoCommandB( (void*)(intptr_t)selectCmdInx );
	SelectRecount();
	return TRUE;
}


EXPORT void DoImport( void )
{
	if (importFile_fs == NULL)
		importFile_fs = wFilSelCreate( mainW, FS_LOAD, 0, _("Import Tracks"),
			sImportFilePattern, ImportTracks, NULL );

	wFilSelect( importFile_fs, curDirName );
}


/**
 * Export the selected track pieces
 *
 * \param pathname IN full path and filename for export file
 * \param filename IN pointer to filename part *within* pathname
 * \param data IN unused
 * \return FALSE on error, TRUE on success
 */

static int DoExportTracks(
		const char * pathName,
		const char * fileName,
		void * data )
{
	FILE * f;
	time_t clock;
	char *oldLocale = NULL;

	if (pathName == NULL)
		return TRUE;
	SetCurDir( pathName, fileName );
	f = fopen( pathName, "w" );
	if (f==NULL) {
		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Export"), fileName, strerror(errno) );
		return FALSE;
	}

	oldLocale = SaveLocale("C");

	wSetCursor( wCursorWait );
	time(&clock);
	fprintf(f,"#%s Version: %s, Date: %s\n", sProdName, sVersion, ctime(&clock) );
	fprintf(f, "VERSION %d %s\n", iParamVersion, PARAMVERSIONVERSION );
	ExportTracks( f );
	fprintf(f, "END\n");
	fclose(f);

	RestoreLocale( oldLocale );

	Reset();
	wSetCursor( wCursorNormal );
	UpdateAllElevations();
	return TRUE;
}


EXPORT void DoExport( void )
{
	if (selectedTrackCount <= 0) {
		ErrorMessage( MSG_NO_SELECTED_TRK );
		return;
	}
	if (exportFile_fs == NULL)
		exportFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Export Tracks"),
				sImportFilePattern, DoExportTracks, NULL );

	wFilSelect( exportFile_fs, curDirName );
}


static FILE * dxfF;
static void DxfLine(
		drawCmd_p d,
		coOrd p0,
		coOrd p1,
		wDrawWidth width,
		wDrawColor color )
{
	fprintf(dxfF, "  0\nLINE\n" );
	fprintf(dxfF, "  8\n%s%d\n", sProdNameUpper, curTrackLayer+1 );
	fprintf(dxfF, "  10\n%0.6f\n  20\n%0.6f\n  11\n%0.6f\n  21\n%0.6f\n",
		p0.x, p0.y, p1.x, p1.y );
	fprintf(dxfF, "  6\n%s\n", (d->options&DC_DASH)?"DASHED":"CONTINUOUS" );
}

static void DxfArc(
		drawCmd_p d,
		coOrd p,
		DIST_T r,
		ANGLE_T angle0,
		ANGLE_T angle1,
		BOOL_T drawCenter,
		wDrawWidth width,
		wDrawColor color )
{
	angle0 = NormalizeAngle(90.0-(angle0+angle1));
	if (angle1 >= 360.0) {
		fprintf(dxfF, "  0\nCIRCLE\n" );
		fprintf(dxfF, "  10\n%0.6f\n  20\n%0.6f\n  40\n%0.6f\n",
				p.x, p.y, r );
	} else {
		fprintf(dxfF, "  0\nARC\n" );
		fprintf(dxfF, "  10\n%0.6f\n  20\n%0.6f\n  40\n%0.6f\n  50\n%0.6f\n  51\n%0.6f\n",
				p.x, p.y, r, angle0, angle0+angle1 );
	}
	fprintf(dxfF, "  8\n%s%d\n", sProdNameUpper, curTrackLayer+1 );
	fprintf(dxfF, "  6\n%s\n", (d->options&DC_DASH)?"DASHED":"CONTINUOUS" );
}

static void DxfString(
		drawCmd_p d,
		coOrd p,
		ANGLE_T a,
		char * s,
		wFont_p fp,
		FONTSIZE_T fontSize,
		wDrawColor color )
{
	fprintf(dxfF, "  0\nTEXT\n" );
	fprintf(dxfF, "  1\n%s\n", s );
	fprintf(dxfF, "  8\n%s%d\n", sProdNameUpper, curTrackLayer+1 );
	fprintf(dxfF, "  10\n%0.6f\n  20\n%0.6f\n", p.x, p.y );
	fprintf(dxfF, "  40\n%0.6f\n", fontSize/72.0 );
}

static void DxfBitMap(
		drawCmd_p d,
		coOrd p,
		wDrawBitMap_p bm,
		wDrawColor color )
{
}

static void DxfFillPoly(
		drawCmd_p d,
		int cnt,
		coOrd * pts,
		wDrawColor color )
{
	int inx;
	for (inx=1; inx<cnt; inx++) {
		DxfLine( d, pts[inx-1], pts[inx], 0, color );
	}
	DxfLine( d, pts[cnt-1], pts[0], 0, color );
}

static void DxfFillCircle( drawCmd_p d, coOrd center, DIST_T radius, wDrawColor color )
{
	DxfArc( d, center, radius, 0.0, 360, FALSE, 0, color );
}


static drawFuncs_t dxfDrawFuncs = {
		0,
		DxfLine,
		DxfArc,
		DxfString,
		DxfBitMap,
		DxfFillPoly,
		DxfFillCircle };

static drawCmd_t dxfD = {
		NULL, &dxfDrawFuncs, 0, 1.0, 0.0, {0.0,0.0}, {0.0,0.0}, Pix2CoOrd, CoOrd2Pix, 100.0 };

static int DoExportDXFTracks(
		const char * pathName,
		const char * fileName,
		void * data )
{
	time_t clock;
	char *oldLocale;

	if (pathName == NULL)
		return TRUE;
	SetCurDir( pathName, fileName );
	dxfF = fopen( pathName, "w" );
	if (dxfF==NULL) {
		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, "DXF", fileName, strerror(errno) );
		return FALSE;
	}

	oldLocale = SaveLocale( "C" );
	wSetCursor( wCursorWait );
	time(&clock);
	fprintf(dxfF,"\
  0\nSECTION\n\
  2\nHEADER\n\
  9\n$ACADVER\n  1\nAC1009\n\
  9\n$EXTMIN\n  10\n%0.6f\n  20\n%0.6f\n\
  9\n$EXTMAX\n  10\n%0.6f\n  20\n%0.6f\n\
  9\n$TEXTSTYLE\n  7\nSTANDARD\n\
  0\nENDSEC\n\
  0\nSECTION\n\
  2\nTABLES\n\
  0\nTABLE\n\
  2\nLTYPE\n\
  0\nLTYPE\n  2\nCONTINUOUS\n  70\n0\n\
  3\nSolid line\n\
  72\n65\n  73\n0\n  40\n0\n\
  0\nLTYPE\n  2\nDASHED\n  70\n0\n\
  3\n__ __ __ __ __ __ __ __ __ __ __ __ __ __ __\n\
  72\n65\n  73\n2\n  40\n0.15\n  49\n0.1\n  49\n-0.05\n\
  0\nLTYPE\n  2\nDOT\n  70\n0\n\
  3\n...............................................\n\
  72\n65\n  73\n2\n  40\n0.1\n  49\n0\n  49\n-0.05\n\
  0\nENDTAB\n\
  0\nTABLE\n\
  2\nLAYER\n\
  70\n0\n\
  0\nLAYER\n  2\n%s1\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s2\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s3\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s4\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s5\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s6\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s7\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s8\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s9\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nLAYER\n  2\n%s10\n  6\nCONTINUOUS\n  62\n7\n  70\n0\n\
  0\nENDTAB\n\
  0\nENDSEC\n\
  0\nSECTION\n\
  2\nENTITIES\n\
",
		0.0, 0.0, mapD.size.x, mapD.size.y,
		sProdNameUpper, sProdNameUpper, sProdNameUpper, sProdNameUpper, sProdNameUpper,
		sProdNameUpper, sProdNameUpper, sProdNameUpper, sProdNameUpper, sProdNameUpper );
	DrawSelectedTracks( &dxfD );
	fprintf(dxfF,"  0\nENDSEC\n");
	fprintf(dxfF,"  0\nEOF\n");
	fclose(dxfF);
	RestoreLocale( oldLocale );
	Reset();
	wSetCursor( wCursorNormal );
	return TRUE;
}


void DoExportDXF( void )
{
	if (selectedTrackCount <= 0) {
		ErrorMessage( MSG_NO_SELECTED_TRK );
		return;
	}
	if (exportDXFFile_fs == NULL)
		exportDXFFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Export to DXF"),
				sDXFFilePattern, DoExportDXFTracks, NULL );

	wFilSelect( exportDXFFile_fs, curDirName );
}

EXPORT BOOL_T EditCopy( void )
{
	FILE * f;
	time_t clock;
	char *oldLocale = NULL;

	if (selectedTrackCount <= 0) {
		ErrorMessage( MSG_NO_SELECTED_TRK );
		return FALSE;
	}
	f = fopen( clipBoardN, "w" );
	if (f == NULL) {
		NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Clipboard"), clipBoardN, strerror(errno) );
		return FALSE;
	}

	oldLocale = SaveLocale("C");

	time(&clock);
	fprintf(f,"#%s Version: %s, Date: %s\n", sProdName, sVersion, ctime(&clock) );
	fprintf(f, "VERSION %d %s\n", iParamVersion, PARAMVERSIONVERSION );
	ExportTracks(f);
	fprintf(f, "END\n");
	RestoreLocale(oldLocale);
	fclose(f);
	return TRUE;
}


EXPORT BOOL_T EditCut( void )
{
	if (!EditCopy())
		return FALSE;
	SelectDelete();
	return TRUE;
}

/**
 * Paste clipboard content. XTrackCAD uses a disk file as clipboard replacement. This file is read and the
 * content is inserted.
 *
 * \return    TRUE if success, FALSE on error (file not found)
 */

EXPORT BOOL_T EditPaste( void )
{
	BOOL_T rc = TRUE;
	char *oldLocale = NULL;

	oldLocale = SaveLocale("C");

	wSetCursor( wCursorWait );
	Reset();
	SetAllTrackSelect( FALSE );
	ImportStart();
	UndoStart( _("Paste"), "paste" );
	useCurrentLayer = TRUE;
	if ( !ReadTrackFile( clipBoardN, sClipboardF, FALSE, TRUE, FALSE ) ) {
		NoticeMessage( MSG_CANT_PASTE, _("Continue"), NULL );
		rc = FALSE;
	}
	ImportEnd();
	/*DoRedraw();*/
	EnableCommands();
	wSetCursor( wCursorNormal );
	importMove = TRUE;
	DoCommandB( (void*)(intptr_t)selectCmdInx );
	SelectRecount();
	UpdateAllElevations();
	RestoreLocale(oldLocale);
	return rc;
}

/*****************************************************************************
 *
 * INITIALIZATION
 *
 */

EXPORT void FileInit( void )
{
	const char * pref;

	if ( (libDir = wGetAppLibDir()) == NULL ) {
		abort();
	}
	if ( (workingDir = wGetAppWorkDir()) == NULL )
		AbortProg( "wGetAppWorkDir()" );

	pref = wPrefGetString( "file", "directory" );
	if (pref != NULL) {
		strcpy( curDirName, pref );
	} else {
		sprintf( curDirName, "%s%sexamples", libDir, FILE_SEP_CHAR );
	}
}

EXPORT BOOL_T ParamFileInit( void )
{
	curParamFileIndex = PARAM_DEMO;
	log_paramFile = LogFindIndex( "paramFile" );
	if ( ReadParams( lParamKey, libDir, sParamQF ) == FALSE )
		return FALSE;

	curParamFileIndex = PARAM_CUSTOM;
	if (lParamKey == 0) {
		ReadParamFiles();
		ReadCustom();
	}

	curPathName[0] = '\0';

	clipBoardN = (char*)MyMalloc( strlen(workingDir) + 1 + strlen(sClipboardF) + 1 );
	sprintf( clipBoardN, "%s%s%s", workingDir, FILE_SEP_CHAR, sClipboardF );
	return TRUE;

}
