/*
** gameconfigfile.cpp
** An .ini parser specifically for zdoom.ini
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#include <stdio.h>
#include <time.h>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <lmcons.h>
#include <shlobj.h>
extern HWND Window;
#endif

#include "doomdef.h"
#include "gameconfigfile.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "c_bind.h"
#include "gstrings.h"
#include "m_argv.h"
#include "cmdlib.h"
#include "version.h"
#include "m_misc.h"
#include "v_font.h"
#include "a_pickups.h"
#include "doomstat.h"
#include "i_system.h"

EXTERN_CVAR (Bool, con_centernotify)
EXTERN_CVAR (Int, msg0color)
EXTERN_CVAR (Color, dimcolor)
EXTERN_CVAR (Color, color)
EXTERN_CVAR (Float, dimamount)
EXTERN_CVAR (Int, msgmidcolor)
EXTERN_CVAR (Int, msgmidcolor2)
EXTERN_CVAR (Bool, snd_pitched)
EXTERN_CVAR (Color, am_wallcolor)
EXTERN_CVAR (Color, am_fdwallcolor)
EXTERN_CVAR (Color, am_cdwallcolor)

char *WeaponSection;

FGameConfigFile::FGameConfigFile ()
{
	FString pathname;
	
	bMigrating = false;
	pathname = GetConfigPath (true);
	ChangePathName (pathname);
	LoadConfigFile (MigrateStub, NULL);

	if (!HaveSections ())
	{ // Config file not found; try the old one
		MigrateOldConfig ();
	}

	// If zdoom.ini was read from the program directory, switch
	// to the user directory now. If it was read from the user
	// directory, this effectively does nothing.
	pathname = GetConfigPath (false);
	ChangePathName (pathname);

	// Set default IWAD search paths if none present
	if (!SetSection ("IWADSearch.Directories"))
	{
		SetSection ("IWADSearch.Directories", true);
		SetValueForKey ("Path", ".", true);
		SetValueForKey ("Path", "$DOOMWADDIR", true);
#ifndef unix
		SetValueForKey ("Path", "$HOME", true);
		SetValueForKey ("Path", "$PROGDIR", true);
#else
		SetValueForKey ("Path", HOME_DIR, true);
		SetValueForKey ("Path", SHARE_DIR, true);
#endif
	}

	// Set default search paths if none present
	if (!SetSection ("FileSearch.Directories"))
	{
		SetSection ("FileSearch.Directories", true);
#ifndef unix
		SetValueForKey ("Path", "$PROGDIR", true);
#else
		SetValueForKey ("Path", SHARE_DIR, true);
#endif
		SetValueForKey ("Path", "$DOOMWADDIR", true);
	}
}

FGameConfigFile::~FGameConfigFile ()
{
}

void FGameConfigFile::WriteCommentHeader (FILE *file) const
{
	fprintf (file, "# This file was generated by ZDOOM " DOTVERSIONSTR " on %s"
				   "# It is not really meant to be modified outside of ZDoom, nyo.\n\n", myasctime ());
}

void FGameConfigFile::MigrateStub (const char *pathname, FConfigFile *config, void *userdata)
{
	static_cast<FGameConfigFile *>(config)->bMigrating = true;
}

void FGameConfigFile::MigrateOldConfig ()
{
	// Set default key bindings. These will be overridden
	// by the bindings in the config file if it exists.
	C_SetDefaultBindings ();

#if 0	// Disabled for now, maybe forever.
	int i;
	char *execcommand;

	i = strlen (GetPathName ()) + 8;
	execcommand = new char[i];
	sprintf (execcommand, "exec \"%s\"", GetPathName ());
	execcommand[i-5] = 'c';
	execcommand[i-4] = 'f';
	execcommand[i-3] = 'g';
	cvar_defflags = CVAR_ARCHIVE;
	C_DoCommand (execcommand);
	cvar_defflags = 0;
	delete[] execcommand;

	FBaseCVar *configver = FindCVar ("configver", NULL);
	if (configver != NULL)
	{
		UCVarValue oldver = configver->GetGenericRep (CVAR_Float);

		if (oldver.Float < 118.f)
		{
			C_DoCommand ("alias idclip noclip");
			C_DoCommand ("alias idspispopd noclip");

			if (oldver.Float < 117.2f)
			{
				dimamount = *dimamount * 0.25f;
				if (oldver.Float <= 113.f)
				{
					C_DoCommand ("bind t messagemode; bind \\ +showscores;"
								 "bind f12 spynext; bind sysrq screenshot");
					if (C_GetBinding (KEY_F5) && !stricmp (C_GetBinding (KEY_F5), "menu_video"))
					{
						C_ChangeBinding ("menu_display", KEY_F5);
					}
				}
			}
		}
		delete configver;
	}
	// Change all impulses to slot commands
	for (i = 0; i < NUM_KEYS; i++)
	{
		char slotcmd[8] = "slot ";
		char *bind, *numpart;

		bind = C_GetBinding (i);
		if (bind != NULL && strnicmp (bind, "impulse ", 8) == 0)
		{
			numpart = strchr (bind, ' ');
			if (numpart != NULL && strlen (numpart) < 4)
			{
				strcpy (slotcmd + 5, numpart);
				C_ChangeBinding (slotcmd, i);
			}
		}
	}

	// Migrate and delete some obsolete cvars
	FBaseCVar *oldvar;
	UCVarValue oldval;

	oldvar = FindCVar ("autoexec", NULL);
	if (oldvar != NULL)
	{
		oldval = oldvar->GetGenericRep (CVAR_String);
		if (oldval.String[0])
		{
			SetSection ("Doom.AutoExec", true);
			SetValueForKey ("Path", oldval.String, true);
		}
		delete oldvar;
	}

	oldvar = FindCVar ("def_patch", NULL);
	if (oldvar != NULL)
	{
		oldval = oldvar->GetGenericRep (CVAR_String);
		if (oldval.String[0])
		{
			SetSection ("Doom.DefaultDehacked", true);
			SetValueForKey ("Path", oldval.String, true);
		}
		delete oldvar;
	}

	oldvar = FindCVar ("vid_noptc", NULL);
	if (oldvar != NULL)
	{
		delete oldvar;
	}
#endif
}

void FGameConfigFile::DoGlobalSetup ()
{
	if (SetSection ("GlobalSettings.Unknown"))
	{
		ReadCVars (CVAR_GLOBALCONFIG);
	}
	if (SetSection ("GlobalSettings"))
	{
		ReadCVars (CVAR_GLOBALCONFIG);
	}
	if (SetSection ("LastRun"))
	{
		const char *lastver = GetValueForKey ("Version");
		if (lastver != NULL)
		{
			double last = atof (lastver);
			if (last < 123.1)
			{
				FBaseCVar *noblitter = FindCVar ("vid_noblitter", NULL);
				if (noblitter != NULL)
				{
					noblitter->ResetToDefault ();
				}
			}
			if (last < 201)
			{
				// Be sure the Hexen fourth weapons are assigned to slot 4
				// If this section does not already exist, then they will be
				// assigned by SetupWeaponList().
				if (SetSection ("Hexen.WeaponSlots"))
				{
					SetValueForKey ("Slot[4]", "FWeapQuietus CWeapWraithverge MWeapBloodscourge");
				}
			}
			if (last < 202)
			{
				// Make sure the Hexen hotkeys are accessible by default.
				if (SetSection ("Hexen.Bindings"))
				{
					SetValueForKey ("\\", "use ArtiHealth");
					SetValueForKey ("scroll", "+showscores");
					SetValueForKey ("0", "useflechette");
					SetValueForKey ("9", "use ArtiBlastRadius");
					SetValueForKey ("8", "use ArtiTeleport");
					SetValueForKey ("7", "use ArtiTeleportOther");
					SetValueForKey ("6", "use ArtiEgg");
					SetValueForKey ("5", "use ArtiInvulnerability");
				}
			}
			if (last < 204)
			{ // The old default for vsync was true, but with an unlimited framerate
			  // now, false is a better default.
				FBaseCVar *vsync = FindCVar ("vid_vsync", NULL);
				if (vsync != NULL)
				{
					vsync->ResetToDefault ();
				}
			}
		}
	}
}

void FGameConfigFile::DoGameSetup (const char *gamename)
{
	const char *key;
	const char *value;
	enum { Doom, Heretic, Hexen, Strife } game;

	if (strcmp (gamename, "Heretic") == 0)
		game = Heretic;
	else if (strcmp (gamename, "Hexen") == 0)
		game = Hexen;
	else if (strcmp (gamename, "Strife") == 0)
		game = Strife;
	else
		game = Doom;

	if (bMigrating)
	{
		MigrateOldConfig ();
	}
	subsection = section + sprintf (section, "%s.", gamename);
	
	strcpy (subsection, "UnknownConsoleVariables");
	if (SetSection (section))
	{
		ReadCVars (0);
	}

	strcpy (subsection, "ConsoleVariables");
	if (SetSection (section))
	{
		ReadCVars (0);
	}

	if (game != Doom && game != Strife)
	{
		SetRavenDefaults (game == Hexen);
	}

	// The NetServerInfo section will be read when it's determined that
	// a netgame is being played.
	strcpy (subsection, "LocalServerInfo");
	if (SetSection (section))
	{
		ReadCVars (0);
	}

	strcpy (subsection, "Player");
	if (SetSection (section))
	{
		ReadCVars (0);
	}

	strcpy (subsection, "Bindings");
	if (!SetSection (section))
	{ // Config has no bindings for the given game
		if (!bMigrating)
		{
			C_SetDefaultBindings ();
		}
	}
	else
	{
		C_UnbindAll ();
		while (NextInSection (key, value))
		{
			C_DoBind (key, value, false);
		}
	}

	strcpy (subsection, "DoubleBindings");
	if (SetSection (section))
	{
		while (NextInSection (key, value))
		{
			C_DoBind (key, value, true);
		}
	}

	strcpy (subsection, "ConsoleAliases");
	if (SetSection (section))
	{
		const char *name = NULL;
		while (NextInSection (key, value))
		{
			if (stricmp (key, "Name") == 0)
			{
				name = value;
			}
			else if (stricmp (key, "Command") == 0 && name != NULL)
			{
				C_SetAlias (name, value);
				name = NULL;
			}
		}
	}

	strcpy (subsection, "WeaponSlots");
	if (!SetSection (section) || !LocalWeapons.RestoreSlots (*this))
	{
		SetupWeaponList (gamename);
	}
}

void FGameConfigFile::ReadNetVars ()
{
	strcpy (subsection, "NetServerInfo");
	if (SetSection (section))
	{
		ReadCVars (0);
	}
}

void FGameConfigFile::ReadCVars (DWORD flags)
{
	const char *key, *value;
	FBaseCVar *cvar;
	UCVarValue val;

	while (NextInSection (key, value))
	{
		cvar = FindCVar (key, NULL);
		if (cvar == NULL)
		{
			cvar = new FStringCVar (key, NULL,
				CVAR_AUTO|CVAR_UNSETTABLE|CVAR_ARCHIVE|flags);
		}
		val.String = const_cast<char *>(value);
		cvar->SetGenericRep (val, CVAR_String);
	}
}

void FGameConfigFile::ArchiveGameData (const char *gamename)
{
	char section[32*3], *subsection;

	subsection = section + sprintf (section, "%s.", gamename);

	strcpy (subsection, "Player");
	SetSection (section, true);
	ClearCurrentSection ();
	C_ArchiveCVars (this, 4);

	strcpy (subsection, "ConsoleVariables");
	SetSection (section, true);
	ClearCurrentSection ();
	C_ArchiveCVars (this, 0);

	strcpy (subsection, netgame ? "NetServerInfo" : "LocalServerInfo");
	if (!netgame || consoleplayer == 0)
	{ // Do not overwrite this section if playing a netgame, and
	  // this machine was not the initial host.
		SetSection (section, true);
		ClearCurrentSection ();
		C_ArchiveCVars (this, 5);
	}

	strcpy (subsection, "UnknownConsoleVariables");
	SetSection (section, true);
	ClearCurrentSection ();
	C_ArchiveCVars (this, 2);

	strcpy (subsection, "ConsoleAliases");
	SetSection (section, true);
	ClearCurrentSection ();
	C_ArchiveAliases (this);

	M_SaveCustomKeys (this, section, subsection);

	strcpy (subsection, "Bindings");
	SetSection (section, true);
	ClearCurrentSection ();
	C_ArchiveBindings (this, false);

	strcpy (subsection, "DoubleBindings");
	SetSection (section, true);
	ClearCurrentSection ();
	C_ArchiveBindings (this, true);

	if (WeaponSection == NULL)
	{
		strcpy (subsection, "WeaponSlots");
	}
	else
	{
		sprintf (subsection, "%s.WeaponSlots", WeaponSection);
	}
	SetSection (section, true);
	ClearCurrentSection ();
	LocalWeapons.SaveSlots (*this);
}

void FGameConfigFile::ArchiveGlobalData ()
{
	SetSection ("LastRun", true);
	ClearCurrentSection ();
	SetValueForKey ("Version", LASTRUNVERSION);

	SetSection ("GlobalSettings", true);
	ClearCurrentSection ();
	C_ArchiveCVars (this, 1);

	SetSection ("GlobalSettings.Unknown", true);
	ClearCurrentSection ();
	C_ArchiveCVars (this, 3);
}

FString FGameConfigFile::GetConfigPath (bool tryProg)
{
	char *pathval;
	FString path;

	pathval = Args.CheckValue ("-config");
	if (pathval != NULL)
		return FString(pathval);

#ifndef unix
	path = NULL;
	HRESULT hr;

	TCHAR uname[UNLEN+1];
	DWORD unamelen = sizeof(uname);

	// Because people complained, try for a user-specific .ini in the program directory first.
	// If that is not writeable, use the one in the home directory instead.
	hr = GetUserName (uname, &unamelen);
	if (SUCCEEDED(hr) && uname[0] != 0)
	{
		// Is it valid for a user name to have slashes?
		// Check for them and substitute just in case.
		char *probe = uname;
		while (*probe != 0)
		{
			if (*probe == '\\' || *probe == '/')
				*probe = '_';
			++probe;
		}

		path = progdir;
		path += "zdoom-";
		path += uname;
		path += ".ini";
		if (tryProg)
		{
			if (!FileExists (path.GetChars()))
			{
				path = "";
			}
		}
		else
		{ // check if writeable
			FILE *checker = fopen (path.GetChars(), "a");
			if (checker == NULL)
			{
				path = "";
			}
			else
			{
				fclose (checker);
			}
		}
	}

	if (path.IsEmpty())
	{
		if (Args.CheckParm ("-cdrom"))
			return "c:\\zdoomdat\\zdoom.ini";

		path = progdir;
		path += "zdoom.ini";
	}
	return path;
#else
	return GetUserFile ("zdoom.ini");
#endif
}

void FGameConfigFile::AddAutoexec (DArgs *list, const char *game)
{
	char section[64];
	const char *key;
	const char *value;

	sprintf (section, "%s.AutoExec", game);

	if (bMigrating)
	{
		FBaseCVar *autoexec = FindCVar ("autoexec", NULL);

		if (autoexec != NULL)
		{
			UCVarValue val;
			char *path;

			val = autoexec->GetGenericRep (CVAR_String);
			path = copystring (val.String);
			delete autoexec;
			SetSection (section, true);
			SetValueForKey ("Path", path);
			list->AppendArg (path);
			delete[] path;
		}
	}
	else
	{
		// If <game>.AutoExec section does not exist, create it
		// with a default autoexec.cfg file present.
		if (!SetSection (section))
		{
			FString path;
			
#ifndef unix
			if (Args.CheckParm ("-cdrom"))
			{
				path = "c:\\zdoomdat\\autoexec.cfg";
			}
			else
			{
				path = progdir;
				path += "autoexec.cfg";
			}
#else
			path = GetUserFile ("autoexec.cfg");
#endif
			SetSection (section, true);
			SetValueForKey ("Path", path.GetChars());
		}
		// Run any files listed in the <game>.AutoExec section
		if (SetSection (section))
		{
			while (NextInSection (key, value))
			{
				if (stricmp (key, "Path") == 0 && FileExists (value))
				{
					list->AppendArg (value);
				}
			}
		}
	}
}

void FGameConfigFile::SetRavenDefaults (bool isHexen)
{
	UCVarValue val;

	if (bMigrating)
	{
		con_centernotify.ResetToDefault ();
		msg0color.ResetToDefault ();
		dimcolor.ResetToDefault ();
		color.ResetToDefault ();
	}

	val.Bool = true;
	con_centernotify.SetGenericRepDefault (val, CVAR_Bool);
	snd_pitched.SetGenericRepDefault (val, CVAR_Bool);
	val.Int = 9;
	msg0color.SetGenericRepDefault (val, CVAR_Int);
	val.Int = 0x0000ff;
	dimcolor.SetGenericRepDefault (val, CVAR_Int);
	val.Int = CR_WHITE;
	msgmidcolor.SetGenericRepDefault (val, CVAR_Int);
	val.Int = CR_YELLOW;
	msgmidcolor2.SetGenericRepDefault (val, CVAR_Int);

	val.Int = 0x543b17;
	am_wallcolor.SetGenericRepDefault (val, CVAR_Int);
	val.Int = 0xd0b085;
	am_fdwallcolor.SetGenericRepDefault (val, CVAR_Int);
	val.Int = 0x734323;
	am_cdwallcolor.SetGenericRepDefault (val, CVAR_Int);

	// Fix the Heretic/Hexen automap colors so they are correct.
	// (They were wrong on older versions.)
	if (*am_wallcolor == 0x2c1808 && *am_fdwallcolor == 0x887058 && *am_cdwallcolor == 0x4c3820)
	{
		am_wallcolor.ResetToDefault ();
		am_fdwallcolor.ResetToDefault ();
		am_cdwallcolor.ResetToDefault ();
	}

	if (!isHexen)
	{
		val.Int = 0x3f6040;
		color.SetGenericRepDefault (val, CVAR_Int);
	}
}

void FGameConfigFile::SetupWeaponList (const char *gamename)
{
	for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
	{
		LocalWeapons.Slots[i].Clear ();
	}

	if (strcmp (gamename, "Heretic") == 0)
	{
		LocalWeapons.Slots[1].AddWeapon ("Staff");
		LocalWeapons.Slots[1].AddWeapon ("Gauntlets");
		LocalWeapons.Slots[2].AddWeapon ("GoldWand");
		LocalWeapons.Slots[3].AddWeapon ("Crossbow");
		LocalWeapons.Slots[4].AddWeapon ("Blaster");
		LocalWeapons.Slots[5].AddWeapon ("SkullRod");
		LocalWeapons.Slots[6].AddWeapon ("PhoenixRod");
		LocalWeapons.Slots[7].AddWeapon ("Mace");
	}
	else if (strcmp (gamename, "Hexen") == 0)
	{
		LocalWeapons.Slots[1].AddWeapon ("FWeapFist");
		LocalWeapons.Slots[2].AddWeapon ("FWeapAxe");
		LocalWeapons.Slots[3].AddWeapon ("FWeapHammer");
		LocalWeapons.Slots[4].AddWeapon ("FWeapQuietus");
		LocalWeapons.Slots[1].AddWeapon ("CWeapMace");
		LocalWeapons.Slots[2].AddWeapon ("CWeapStaff");
		LocalWeapons.Slots[3].AddWeapon ("CWeapFlame");
		LocalWeapons.Slots[4].AddWeapon ("CWeapWraithverge");
		LocalWeapons.Slots[1].AddWeapon ("MWeapWand");
		LocalWeapons.Slots[2].AddWeapon ("MWeapFrost");
		LocalWeapons.Slots[3].AddWeapon ("MWeapLightning");
		LocalWeapons.Slots[4].AddWeapon ("MWeapBloodscourge");
	}
	else if (strcmp (gamename, "Strife") == 0)
	{
		LocalWeapons.Slots[1].AddWeapon ("PunchDagger");
		LocalWeapons.Slots[2].AddWeapon ("StrifeCrossbow2");
		LocalWeapons.Slots[2].AddWeapon ("StrifeCrossbow");
		LocalWeapons.Slots[3].AddWeapon ("AssaultGun");
		LocalWeapons.Slots[4].AddWeapon ("MiniMissileLauncher");
		LocalWeapons.Slots[5].AddWeapon ("StrifeGrenadeLauncher2");
		LocalWeapons.Slots[5].AddWeapon ("StrifeGrenadeLauncher");
		LocalWeapons.Slots[6].AddWeapon ("FlameThrower");
		LocalWeapons.Slots[7].AddWeapon ("Mauler2");
		LocalWeapons.Slots[7].AddWeapon ("Mauler");
		LocalWeapons.Slots[8].AddWeapon ("Sigil");
	}
	else // Doom
	{
		LocalWeapons.Slots[1].AddWeapon ("Fist");
		LocalWeapons.Slots[1].AddWeapon ("Chainsaw");
		LocalWeapons.Slots[2].AddWeapon ("Pistol");
		LocalWeapons.Slots[3].AddWeapon ("Shotgun");
		LocalWeapons.Slots[3].AddWeapon ("SuperShotgun");
		LocalWeapons.Slots[4].AddWeapon ("Chaingun");
		LocalWeapons.Slots[5].AddWeapon ("RocketLauncher");
		LocalWeapons.Slots[6].AddWeapon ("PlasmaRifle");
		LocalWeapons.Slots[7].AddWeapon ("BFG9000");
	}
}

CCMD (whereisini)
{
	FString path = GameConfig->GetConfigPath (false);
	Printf ("%s\n", path.GetChars());
}
