/** \file paramfile.c
 * Handling of parameter files
 */

/*  XTrackkCad - Model Railroad CAD
 *  Copyright (C) 2019 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 "common.h"
#include "compound.h"
#include "ctrain.h"
#include "custom.h"
#include "fileio.h"
#include "layout.h"
#include "paths.h"
#include "include/paramfile.h"
#include "include/paramfilelist.h"
#include "include/utf8convert.h"
#include "common-ui.h"

static long paramCheckSum;

typedef enum paramFileState(*GetCompatibilityFunction)(int index,
                SCALEINX_T scale);

GetCompatibilityFunction GetCompatibility[] = {
	GetTrackCompatibility,
	GetStructureCompatibility,
	GetCarProtoCompatibility,
	GetCarPartCompatibility
};

#define COMPATIBILITYCHECKSCOUNT COUNT(GetCompatibility)

/**
 * Check whether parameter file is still loaded
 *
 * \param fileInx
 * \return TRUE if loaded, FALSE otherwise
 */

wBool_t IsParamValid(
        int fileInx)
{
	if (fileInx == PARAM_DEMO) {
		return (curDemo >= 0);
	} else if (fileInx == PARAM_CUSTOM) {
		return TRUE;
	} else if (fileInx == PARAM_LAYOUT) {
		return TRUE;
	} else if (fileInx >= 0 && fileInx < paramFileInfo_da.cnt) {
		return (!paramFileInfo(fileInx).deleted);
	} else {
		return FALSE;
	}
}

char *GetParamFileDir(void)
{
	return (GetCurrentPath(PARAMETERPATHKEY));
}

void
SetParamFileDir(char *fullPath)
{
	SetCurrentPath(PARAMETERPATHKEY, fullPath);
}

char * GetParamFileName(
        int fileInx)
{
	return paramFileInfo(fileInx).name;
}

char * GetParamFileContents(
        int fileInx)
{
	return paramFileInfo(fileInx).contents;
}

bool IsParamFileDeleted(int inx)
{
	return paramFileInfo(inx).deleted;
}

bool IsParamFileFavorite(int inx)
{
	return paramFileInfo(inx).favorite;
}

void SetParamFileDeleted(int fileInx, bool deleted)
{
	paramFileInfo(fileInx).deleted = deleted;
}

void SetParamFileFavorite(int fileInx, bool favorite)
{
	paramFileInfo(fileInx).favorite = favorite;
}

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

/**
 * Set the compatibility state of a parameter file
 *
 * \param index parameter file number in list
 * \return
 */

void SetParamFileState(int index)
{
	enum paramFileState state = PARAMFILE_NOTUSABLE;
	enum paramFileState newState;
	SCALEINX_T scale = GetLayoutCurScale();

	//Set yet?
	if (scale>=0) {
		for (int i = 0; i < COMPATIBILITYCHECKSCOUNT && state < PARAMFILE_FIT &&
		     state != PARAMFILE_UNLOADED; i++) {
			newState = (*GetCompatibility[i])(index, scale);
			if (newState > state || newState == PARAMFILE_UNLOADED) {
				state = newState;
			}
		}
	}

	paramFileInfo(index).trackState = state;
}

/**
 * Check whether file exists and is readable
 *
 * \param  file The file.
 *
 * \returns True if it succeeds, false if it fails.
 */

static bool
CheckFileReadable(const char *file)
{
	if(!access( file, R_OK )) {
		return TRUE;
	} else {
		return FALSE;
	}
}

/**
 * Read a single parameter file and update the parameter file list
 *
 * \param fileName full path for parameter file
 * \return
 */

int
ReadParamFile(const char *fileName)
{
	if (!CheckFileReadable(fileName)) {
		return(-1);
	} else {
		DYNARR_APPEND(paramFileInfo_t, paramFileInfo_da, 10);
		curParamFileIndex = paramFileInfo_da.cnt - 1;
		paramFileInfo(curParamFileIndex).name = MyStrdup(fileName);
		paramFileInfo(curParamFileIndex).valid = TRUE;
		paramFileInfo(curParamFileIndex).deleted = !ReadParams(0, NULL, fileName);
		paramFileInfo(curParamFileIndex).contents = MyStrdup(curContents);

		SetParamFileState(curParamFileIndex);
	}
	return (curParamFileIndex);
}

/**
 * Reload a single parameter file that had been unloaded before.
 *
 * \param  fileindex index of previously created paramFileInfo
 *
 * \returns
 */

int
ReloadDeletedParamFile(int fileindex)
{
	curParamFileIndex = fileindex;
	paramFileInfo(curParamFileIndex).valid = TRUE;
	paramFileInfo(curParamFileIndex).deleted = !ReadParams(0, NULL,
	                paramFileInfo(curParamFileIndex).name);
	paramFileInfo(curParamFileIndex).contents = MyStrdup(curContents);

	SetParamFileState(curParamFileIndex);

	return (curParamFileIndex);
}

/**
 * Parameter file reader and interpreter
 *
 * \param key unused
 * \param dirName prefix for parameter file path
 * \param fileName name of parameter file
 * \return
 */

bool 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;
	BOOL_T bFoundContents = FALSE;
	paramVersion = -1;

	if (dirName) {
		MakeFullpath(&paramFileName, dirName, fileName, NULL);
	} else {
		MakeFullpath(&paramFileName, fileName, NULL);
	}
	paramLineNum = 0;
	curBarScale = -1;
	curContents = MyStrdup(fileName);
	curSubContents = curContents;

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

	SetCLocale();

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

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

		return FALSE;
	}
	paramCheckSum = key;
	paramLineNum = 0;
	checkSummed = FALSE;
	BOOL_T skip = false;
	int skipLines = 0;
	while (paramFile && (fgets(paramLine, 1024, 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);
					paramFile = NULL;
				}
				SetUserLocale();
				return FALSE;
			}
			oldFile = paramFile;
			oldLineNum = paramLineNum;
			oldCheckSum = paramCheckSum;
			if (!ReadParams(key, dirName, cp)) {
				SetUserLocale();
				return FALSE;
			}
			paramFile = oldFile;
			paramLineNum = oldLineNum;
			paramCheckSum = oldCheckSum;
			if (dirName) {
				MakeFullpath(&paramFileName, dirName, fileName, NULL);
			} else {
				MakeFullpath(&paramFileName, fileName);
			}
			skip = FALSE;
		} else if (strncmp(paramLine, "CONTENTS ", 9) == 0) {
#ifdef UTFCONVERT
			ConvertUTF8ToSystem(paramLine + 9);
#endif
			if ( bFoundContents == FALSE ) {
				// Only use the first CONTENTS
				curContents = MyStrdup(paramLine + 9);
				curSubContents = curContents;
				bFoundContents = TRUE;
			}
			skip = FALSE;
		} else if (strncmp(paramLine, "SUBCONTENTS ", 12) == 0) {
#ifdef UTFCONVERT
			ConvertUTF8ToSystem(paramLine + 12);
#endif // UTFCONVERT
			curSubContents = MyStrdup(paramLine + 12);
			skip = FALSE;
		} else if (strncmp(paramLine, "PARAM ", 6) == 0) {
			paramVersion = strtol(paramLine + 6, &cp, 10);
			if (cp)
				while (*cp && isspace((unsigned char)*cp)) { cp++; }
			if (paramVersion > iParamVersion) {
				if (cp && *cp) {
					NoticeMessage(MSG_PARAM_UPGRADE_VERSION1, _("Ok"), NULL, paramVersion,
					              iParamVersion, sProdName, cp);
				} else {
					NoticeMessage(MSG_PARAM_UPGRADE_VERSION2, _("Ok"), NULL, paramVersion,
					              iParamVersion, sProdName);
				}
				break;
			}
			if (paramVersion < iMinParamVersion) {
				NoticeMessage(MSG_PARAM_BAD_FILE_VERSION, _("Ok"), NULL, paramVersion,
				              iMinParamVersion, sProdName);
				break;
			}
		} else if (skip && (strncmp(paramLine, "  ", 1) == 0)) {
			//Always skip to next line starting in LeftHandColumn
			skipLines++;
			goto nextLine;
		} else {
			for (pc = 0; pc < paramProc_da.cnt; pc++) {
				if (strncmp(paramLine, paramProc(pc).name,
				            strlen(paramProc(pc).name)) == 0) {
					skip = FALSE;					//Stop skip so we re-message
					paramProc(pc).proc(paramLine);
					goto nextLine;
				}
			}
			if (!skip) {
				if (InputError(_("Unknown param file line - skip until next good object?"),
				               TRUE)) {   //OK to carry on
					/* SKIP until next main line we recognize */
					skip = TRUE;
					skipLines++;
					goto nextLine;
				} else {
					if (skipLines > 0) {
						NoticeMessage(MSG_PARAM_LINES_SKIPPED, _("Ok"), NULL, paramFileName,
						              skipLines);
					}
					if (paramFile) {
						fclose(paramFile);
						paramFile = NULL;
					}
					if (paramFileName) {
						free(paramFileName);
						paramFileName = NULL;
					}
					SetUserLocale();
					return FALSE;
				}
			}
			skipLines++;
		}
nextLine:
		;
	}
	if (key) {
		if (!checkSummed || checkSum != paramCheckSum) {
			/* Close file and reset the locale settings */
			if (paramFile) {
				fclose(paramFile);
				paramFile = NULL;
			}
			SetUserLocale();

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

			return FALSE;
		}
	}
	if (skipLines > 0) {
		NoticeMessage(MSG_PARAM_LINES_SKIPPED, _("Ok"), NULL, paramFileName,
		              skipLines);
	}
	if (paramFile) {
		fclose(paramFile);
		paramFile = NULL;
	}
	free(paramFileName);
	paramFileName = NULL;
	SetUserLocale();

	return TRUE;
}