
// Append
#define AUTOEXEC_APPEND_BAD_FILENAME    0
#define AUTOEXEC_APPEND_FILE_NOT_FOUND  1
#define AUTOEXEC_APPEND_BAD_HANDLE      2
#define AUTOEXEC_APPEND_SUCCESS         3



// Find
#define AUTOEXEC_FIND_BAD_FILENAME     10
#define AUTOEXEC_FIND_FILE_NOT_FOUND   11
#define AUTOEXEC_FIND_BAD_HANDLE       12
#define AUTOEXEC_FIND_NOT_FOUND        13
#define AUTOEXEC_FIND_SUCCESS          14



// Clean
#define AUTOEXEC_CLEAN_FILE_NOT_FOUND  20
#define AUTOEXEC_CLEAN_BAD_HANDLE      21
#define AUTOEXEC_CLEAN_SUCCESS         22



// General
#define AUTOEXEC_NO_CONFIG             30



// Formatter
#define AUTOEXEC_FORMAT_BAD_FILENAME   40
#define AUTOEXEC_FORMAT_SUCCESS        41



// Global variables
static char g_sConfigFile[PLATFORM_MAX_PATH];
static char g_sRawFileName[PLATFORM_MAX_PATH];
static char g_sFolderPath[PLATFORM_MAX_PATH];

static bool g_bCreateFile = false;
static Handle g_hPluginHandle = null;



// Workaround for now
static int g_iLastFindResult;
static int g_iLastAppendResult;




/**
 * Returns the last result from the parser.
 *
 * @return			Returns one of the AUTOEXEC_FIND values or -1 if not set.
*/
stock int AutoExecConfig_GetFindResult()
{
	return g_iLastFindResult;
}





/**
 * Returns the last result from the appender.
 *
 * @return			Returns one of the AUTOEXEC_APPEND values or -1 if not set.
*/
stock int AutoExecConfig_GetAppendResult()
{
	return g_iLastAppendResult;
}


/**
 * Set if the config file should be created if it doesn't exist yet.
 *
 * @param create 	True if config file should be created, false otherwise.
 * @noreturn
 */
stock void AutoExecConfig_SetCreateFile(bool create)
{
	g_bCreateFile = create;
}


/**
 * Returns if the config file should be created if it doesn't exist.
 *
 * @return			Returns true, if the config file should be created or false if it should not.
 */
stock bool AutoExecConfig_GetCreateFile()
{
	return g_bCreateFile;
}


/**
 * Set the plugin for which the config file should be created.
 * Set to null to use the calling plugin.
 * Used to print the correct filename in the top comment when creating the file.
 *
 * @param plugin 	The plugin to create convars for or null to use the calling plugin.
 * @noreturn
 */
stock void AutoExecConfig_SetPlugin(Handle plugin)
{
	g_hPluginHandle = plugin;
}


/**
 * Returns the plugin for which the config file is created.
 *
 * @return			The plugin handle
 */
stock Handle AutoExecConfig_GetPlugin()
{
	return g_hPluginHandle;
}


/**
 * Set the global autoconfigfile used by functions of this file.
 *
 * @param file			Name of the config file, path and .cfg extension is being added if not given.
 * @param folder		Folder under cfg/ to use.  By default this is "sourcemod."
 * @return				True if formatter returned success, false otherwise.
*/
stock bool AutoExecConfig_SetFile(char[] file, char[] folder="sourcemod")
{
	Format(g_sConfigFile, sizeof(g_sConfigFile), "%s", file);
	
	// Global buffers for cfg execution
	strcopy(g_sRawFileName, sizeof(g_sRawFileName), file);
	strcopy(g_sFolderPath, sizeof(g_sFolderPath), folder);
	
	
	// Format the filename
	return AutoExecConfig_FormatFileName(g_sConfigFile, sizeof(g_sConfigFile), folder) == AUTOEXEC_FORMAT_SUCCESS;
}






/**
 * Get the formatted autoconfigfile used by functions of this file.
 *
 * @param buffer		String to format.
 * @param size			Maximum size of buffer
 * @return				True if filename was set, false otherwise.
*/
stock bool AutoExecConfig_GetFile(char[] buffer,int size)
{
	if(strlen(g_sConfigFile) > 0)
	{
		strcopy(buffer, size, g_sConfigFile);
		
		return true;
	}
	
	// Security for decl users
	buffer[0] = '\0';
	
	return false;
}






/**
 * Creates a convar and appends it to the autoconfigfile if not found.
 * FCVAR_DONTRECORD will be skipped.
 *
 * @param name			Name of new convar.
 * @param defaultValue	String containing the default value of new convar.
 * @param description	Optional description of the convar.
 * @param flags			Optional bitstring of flags determining how the convar should be handled. See FCVAR_* constants for more details.
 * @param hasMin		Optional boolean that determines if the convar has a minimum value.
 * @param min			Minimum floating point value that the convar can have if hasMin is true.
 * @param hasMax		Optional boolean that determines if the convar has a maximum value.
 * @param max			Maximum floating point value that the convar can have if hasMax is true.
 * @return				A handle to the newly created convar. If the convar already exists, a handle to it will still be returned.
 * @error				Convar name is blank or is the same as an existing console command.
*/
stock ConVar AutoExecConfig_CreateConVar(const char[] name, const char[] defaultValue, const char[] description="", int flags=0, bool hasMin=false, float min=0.0, bool hasMax=false, float max=0.0)
{
	// If configfile was set and convar has no dontrecord flag
	if(!(flags & FCVAR_DONTRECORD) && strlen(g_sConfigFile) > 0)
	{
		// Reset the results
		g_iLastFindResult = -1;
		g_iLastAppendResult = -1;
		
		
		// Add it if not found
		char buffer[64];
		
		g_iLastFindResult = AutoExecConfig_FindValue(name, buffer, sizeof(buffer), true);
		
		// We only add this convar if it doesn't exist, or the file doesn't exist and it should be auto-generated
		if(g_iLastFindResult == AUTOEXEC_FIND_NOT_FOUND || (g_iLastFindResult == AUTOEXEC_FIND_FILE_NOT_FOUND && g_bCreateFile))
		{
			g_iLastAppendResult = AutoExecConfig_AppendValue(name, defaultValue, description, flags, hasMin, min, hasMax, max);
		}
	}
	
	
	// Create the convar
	return CreateConVar(name, defaultValue, description, flags, hasMin, min, hasMax, max);
}




/**
 * Executes the autoconfigfile, and adds it to the OnConfigsExecuted forward.
 * If we didn't created it already we let SourceMod create it.
 *
 * @noreturn
*/
stock void AutoExecConfig_ExecuteFile()
{
	// Only let sourcemod create the file, if we didn't do that already. 
	AutoExecConfig(!g_bCreateFile, g_sRawFileName, g_sFolderPath);
}





/**
 * Formats a autoconfigfile, prefixes path and adds .cfg extension if missed.
 *
 * @param buffer		String to format.
 * @param size			Maximum size of buffer.
 * @return				Returns one of the AUTOEXEC_FORMAT values..
*/
stock static int AutoExecConfig_FormatFileName(char[] buffer, int size, char[] folder="sourcemod")
{
	// No config set
	if(strlen(g_sConfigFile) < 1)
	{
		return AUTOEXEC_NO_CONFIG;
	}
	
	
	// Can't be an cfgfile
	if(StrContains(g_sConfigFile, ".cfg") == -1 && strlen(g_sConfigFile) < 4)
	{
		return AUTOEXEC_FORMAT_BAD_FILENAME;
	}
	
	
	// Pathprefix
	char pathprefixbuffer[PLATFORM_MAX_PATH];
	if(strlen(folder) > 0)
	{
		Format(pathprefixbuffer, sizeof(pathprefixbuffer), "cfg/%s/", folder);
	}
	else
	{
		Format(pathprefixbuffer, sizeof(pathprefixbuffer), "cfg/");
	}
	
	
	char filebuffer[PLATFORM_MAX_PATH];
	filebuffer[0] = '\0';
	
	// Add path if file doesn't begin with it
	if(StrContains(buffer, pathprefixbuffer) != 0)
	{
		StrCat(filebuffer, sizeof(filebuffer), pathprefixbuffer);
	}
	
	StrCat(filebuffer, sizeof(filebuffer), g_sConfigFile);
	
	
	// Add .cfg extension if file doesn't end with it
	if(StrContains(filebuffer[strlen(filebuffer) - 4], ".cfg") != 0)
	{
		StrCat(filebuffer, sizeof(filebuffer), ".cfg");
	}
	
	strcopy(buffer, size, filebuffer);
	
	return AUTOEXEC_FORMAT_SUCCESS;
}






/**
 * Appends a convar to the global autoconfigfile
 *
 * @param name			Name of new convar.
 * @param defaultValue	String containing the default value of new convar.
 * @param description	Optional description of the convar.
 * @param flags			Optional bitstring of flags determining how the convar should be handled. See FCVAR_* constants for more details.
 * @param hasMin		Optional boolean that determines if the convar has a minimum value.
 * @param min			Minimum floating point value that the convar can have if hasMin is true.
 * @param hasMax		Optional boolean that determines if the convar has a maximum value.
 * @param max			Maximum floating point value that the convar can have if hasMax is true.
 * @return				Returns one of the AUTOEXEC_APPEND values
*/
stock int AutoExecConfig_AppendValue(const char[] name, const char[] defaultValue, const char[] description, int flags, bool hasMin, float min, bool hasMax, float max)
{
	// No config set
	if(strlen(g_sConfigFile) < 1)
	{
		return AUTOEXEC_NO_CONFIG;
	}
	
	
	char filebuffer[PLATFORM_MAX_PATH];
	strcopy(filebuffer, sizeof(filebuffer), g_sConfigFile);
	
	
	//PrintToServer("pathbuffer: %s", filebuffer);
	
	bool bFileExists = FileExists(filebuffer);
	
	if(g_bCreateFile || bFileExists)
	{
		// If the file already exists we open it in append mode, otherwise we use a write mode which creates the file
		File fFile = OpenFile(filebuffer, (bFileExists ? "a" : "w"));
		char writebuffer[2048];
		
		
		if(fFile == null)
		{
			return AUTOEXEC_APPEND_BAD_HANDLE;
		}
		
		// We just created the file, so add some header about version and stuff
		if(g_bCreateFile && !bFileExists)
		{
			fFile.WriteLine( "// This file was auto-generated by AutoExecConfig read and append beta");
			
			GetPluginFilename(g_hPluginHandle, writebuffer, sizeof(writebuffer));
			Format(writebuffer, sizeof(writebuffer), "// ConVars for plugin \"%s\"", writebuffer);
			fFile.WriteLine(writebuffer);
		}
		
		// Spacer
		fFile.WriteLine("\n");
		

		// This is used for multiline comments
		int newlines = GetCharCountInStr('\n', description);
		if(newlines == 0)
		{
			// We have no newlines, we can write the description to the file as is
			Format(writebuffer, sizeof(writebuffer), "// %s", description);
			fFile.WriteLine(writebuffer);
		}
		else
		{
			char[][] newlineBuf = new char[newlines +1][2048];
			ExplodeString(description, "\n", newlineBuf, newlines +1, 2048, false);
			
			// Each newline gets a commented newline
			for(int i; i <= newlines; i++)
			{
				if(strlen(newlineBuf[i]) > 0)
				{
					fFile.WriteLine("// %s", newlineBuf[i]);
				}
			}
		}
		
		
		// Descspacer
		fFile.WriteLine("// -");
		
		
		// Default
		Format(writebuffer, sizeof(writebuffer), "// Default: \"%s\"", defaultValue);
		fFile.WriteLine(writebuffer);
		
		
		// Minimum
		if(hasMin)
		{		
			Format(writebuffer, sizeof(writebuffer), "// Minimum: \"%f\"", min);
			fFile.WriteLine(writebuffer);
		}
		
		
		// Maximum
		if(hasMax)
		{
			Format(writebuffer, sizeof(writebuffer), "// Maximum: \"%f\"", max);
			fFile.WriteLine(writebuffer);
		}
		
		
		// Write end and defaultvalue
		Format(writebuffer, sizeof(writebuffer), "%s \"%s\"", name, defaultValue);
		fFile.WriteLine(writebuffer);
		
		
		fFile.Close();
		
		
		// Clean up the file
		//AutoExecConfig_CleanFile(filebuffer, false);
		
		
		return AUTOEXEC_APPEND_SUCCESS;
	}
	
	return AUTOEXEC_APPEND_FILE_NOT_FOUND;
}






/**
 * Returns a convars value from the global autoconfigfile
 *
 * @param cvar				Cvar to search for.
 * @param value				Buffer to store result into.
 * @param size				Maximum size of buffer.
 * @param caseSensitive		Whether or not the search should be case sensitive.
 * @return					Returns one of the AUTOEXEC_FIND values
*/
stock int AutoExecConfig_FindValue(const char[] cvar, char[] value, int size, bool caseSensitive=false)
{
	// Security for decl users
	value[0] = '\0';
	
	
	// No config set
	if(strlen(g_sConfigFile) < 1)
	{
		return AUTOEXEC_NO_CONFIG;
	}
	
	
	char filebuffer[PLATFORM_MAX_PATH];
	strcopy(filebuffer, sizeof(filebuffer), g_sConfigFile);
	
	
	
	//PrintToServer("pathbuffer: %s", filebuffer);
	
	bool bFileExists = FileExists(filebuffer);
	
	// We want to create the config file and it doesn't exist yet.
	if(g_bCreateFile && !bFileExists)
	{
		return AUTOEXEC_FIND_FILE_NOT_FOUND;
	}
	
	
	if(bFileExists)
	{
		File fFile = OpenFile(filebuffer, "r");
		int valuestart;
		int valueend;
		int cvarend;
		
		// Just an reminder to self, leave the values that high
		char sConvar[64];
		char sValue[64];
		char readbuffer[2048];
		char copybuffer[2048];
		
		if(fFile == null)
		{
			return AUTOEXEC_FIND_BAD_HANDLE;
		}
		
		
		while(!fFile.EndOfFile() && fFile.ReadLine(readbuffer, sizeof(readbuffer)))
		{
			// Is a comment or not valid
			if(IsCharSpace(readbuffer[0]) || readbuffer[0] == '/' || !IsCharAlpha(readbuffer[0]))
			{
				continue;
			}
			
			
			// Has not enough spaces, must have at least 1
			if(GetCharCountInStr(' ', readbuffer) < 1)
			{
				continue;
			}

			
			// Ignore cvars which aren't quoted
			if(GetCharCountInStr('"', readbuffer) != 2)
			{
				continue;
			}
			
			
			
			// Get the start of the value
			if( (valuestart = StrContains(readbuffer, "\"")) == -1 )
			{
				continue;
			}
			
			
			// Get the end of the value
			if( (valueend = StrContains(readbuffer[valuestart+1], "\"")) == -1 )
			{
				continue;
			}
			
			
			// Get the start of the cvar, 
			if( (cvarend = StrContains(readbuffer, " ")) == -1 || cvarend >= valuestart)
			{
				continue;
			}
			
			
			// Skip if cvarendindex is before valuestartindex
			if(cvarend >= valuestart)
			{
				continue;
			}
			
			
			// Convar
			// Tempcopy for security
			strcopy(copybuffer, sizeof(copybuffer), readbuffer);
			copybuffer[cvarend] = '\0';
			
			strcopy(sConvar, sizeof(sConvar), copybuffer);
			

			// Value
			// Tempcopy for security
			strcopy(copybuffer, sizeof(copybuffer), readbuffer[valuestart+1]);
			copybuffer[valueend] = '\0';
			
			strcopy(sValue, sizeof(sValue), copybuffer);
			
			
			//PrintToServer("Cvar %s has a value of %s", sConvar, sValue);
			
			if(StrEqual(sConvar, cvar, caseSensitive))
			{
				Format(value, size, "%s", sConvar);
				
				fFile.Close();
				return AUTOEXEC_FIND_SUCCESS;
			}					
		}
		
		fFile.Close();	
		return AUTOEXEC_FIND_NOT_FOUND;
	}
	
	
	return AUTOEXEC_FIND_FILE_NOT_FOUND;
}






/**
 * Cleans the global autoconfigfile from too much spaces
 *
 * @return					One of the AUTOEXEC_CLEAN values.
*/
stock int AutoExecConfig_CleanFile()
{
	// No config set
	if(strlen(g_sConfigFile) < 1)
	{
		return AUTOEXEC_NO_CONFIG;
	}
	
	
	char sfile[PLATFORM_MAX_PATH];
	strcopy(sfile, sizeof(sfile), g_sConfigFile);
	
	
	// Security
	if(!FileExists(sfile))
	{
		return AUTOEXEC_CLEAN_FILE_NOT_FOUND;
	}
	
	
	
	char sfile2[PLATFORM_MAX_PATH];
	Format(sfile2, sizeof(sfile2), "%s_tempcopy", sfile);
	
	
	char readbuffer[2048];
	int count;
	bool firstreached;
	
	
	// Open files
	File fFile1 = OpenFile(sfile, "r");
	File fFile2 = OpenFile(sfile2, "w");
	
	
	
	// Check filehandles
	if(fFile1 == null || fFile2 == null)
	{
		if(fFile1 != null)
		{
			//PrintToServer("Handle1 invalid");
			fFile1.Close();
		}
		
		if(fFile2 != null)
		{
			//PrintToServer("Handle2 invalid");
			fFile2.Close();
		}
		
		return AUTOEXEC_CLEAN_BAD_HANDLE;
	}
	
	
	
	while(!fFile1.EndOfFile() && fFile1.ReadLine(readbuffer, sizeof(readbuffer)))
	{
		// Is space
		if(IsCharSpace(readbuffer[0]))
		{
			count++;
		}
		// No space, count from start
		else
		{
			count = 0;
		}
		
		
		// Don't write more than 1 space if seperation after informations have been reached
		if(count < 2 || !firstreached)
		{
			ReplaceString(readbuffer, sizeof(readbuffer), "\n", "");
			fFile2.WriteLine(readbuffer);
		}
		
		
		// First bigger seperation after informations has been reached
		if(count == 2)
		{
			firstreached = true;
		}
	}
	
	
	fFile1.Close();
	fFile2.Close();
	
	
	// This might be a risk, for now it works
	DeleteFile(sfile);
	RenameFile(sfile, sfile2);
	
	return AUTOEXEC_CLEAN_SUCCESS;
}






/**
 * Returns how many times the given char occures in the given string.
 *
 * @param str		String to search for in.
 * @return			Occurences of the given char found in string.
*/
stock static int GetCharCountInStr(int character, const char[] str)
{
	int len = strlen(str);
	int count;
	
	for(int i; i < len; i++)
	{
		if(str[i] == character)
		{
			count++;
		}
	}
	
	return count;
}