// valvelibaw.cpp : implementation file
//

#include "stdafx.h"
#include "valvelib.h"
#include "valvelibaw.h"
#include "chooser.h"

#include <assert.h>

#include<atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <initguid.h>
#include<comdef.h>


#ifdef _PSEUDO_DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define STUPID_MS_BUG 1

enum Config_t
{
	CONFIG_DEBUG = 0,
	CONFIG_RELEASEWITHSYMBOLS,
	CONFIG_RELEASE
};

enum Project_t
{
	PROJECT_LIB = 0,
	PROJECT_DLL,
	PROJECT_EXE
};

struct ProjectInfo_t
{
	DSProjectSystem::IConfigurationPtr	m_pConfig;
	long		m_ConfigIndex;
	Config_t	m_Config;
	Project_t	m_Project;
	CString		m_RelativeTargetPath;
	CString		m_RelativeImplibPath;
	CString		m_RelativeRootPath;
	CString		m_RelativeSrcPath;
	CString		m_TargetPath;
	CString		m_RootName;
	CString		m_BuildName;
	CString		m_Ext;
	bool		m_Tool;
	bool		m_Console;
	bool		m_Public;
	bool		m_PublishImportLib;
};


// This is called immediately after the custom AppWizard is loaded.  Initialize
//  the state of the custom AppWizard here.
void CValvelibAppWiz::InitCustomAppWiz()
{
	// Create a new dialog chooser; CDialogChooser's constructor initializes
	//  its internal array with pointers to the steps.
	m_pChooser = new CDialogChooser;

	// Set the maximum number of steps.
	SetNumberOfSteps(LAST_DLG);

	// TODO: Add any other custom AppWizard-wide initialization here.
}

// This is called just before the custom AppWizard is unloaded.
void CValvelibAppWiz::ExitCustomAppWiz()
{
	// Deallocate memory used for the dialog chooser
	ASSERT(m_pChooser != NULL);
	delete m_pChooser;
	m_pChooser = NULL;

	// TODO: Add code here to deallocate resources used by the custom AppWizard
}

// This is called when the user clicks "Create..." on the New Project dialog
//  or "Next" on one of the custom AppWizard's steps.
CAppWizStepDlg* CValvelibAppWiz::Next(CAppWizStepDlg* pDlg)
{
	// Delegate to the dialog chooser
	return m_pChooser->Next(pDlg);
}

// This is called when the user clicks "Back" on one of the custom
//  AppWizard's steps.
CAppWizStepDlg* CValvelibAppWiz::Back(CAppWizStepDlg* pDlg)
{
	// Delegate to the dialog chooser
	return m_pChooser->Back(pDlg);
}


//-----------------------------------------------------------------------------
// Deals with compiler settings
//-----------------------------------------------------------------------------

static void SetupCompilerSettings(ProjectInfo_t& info)
{
	_bstr_t varTool;
	_bstr_t varSwitch;
	_variant_t varj = info.m_ConfigIndex;

	// We're going to modify settings associated with cl.exe
	varTool   = "cl.exe";

	// Standard include directories
	CString includePath;
	if (info.m_Public)
	{
		includePath.Format("/I%scommon /I%spublic", info.m_RelativeSrcPath, 
			info.m_RelativeSrcPath );
	}
	else
	{
		includePath.Format("/I%scommon", info.m_RelativeSrcPath );
	}
	info.m_pConfig->AddToolSettings(varTool, (char const*)includePath, varj);

	// Control which version of the debug libraries to use (static single thread
	// for normal projects, multithread for tools)
	if (info.m_Tool)
	{
		if (info.m_Config == CONFIG_DEBUG)
			varSwitch = "/MTd";
		else
			varSwitch = "/MT";
	}
	else
	{
		// Suppress use of non-filesystem stuff in new non-tool projects...
		if (info.m_Config == CONFIG_DEBUG)
			varSwitch = "/D \"fopen=dont_use_fopen\" /MLd";
		else
			varSwitch = "/D \"fopen=dont_use_fopen\" /ML";
	}
	info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);

	// If it's a DLL, define a standard exports macro...
	if (info.m_Project == PROJECT_DLL)
	{
		CString dllExport;
		dllExport.Format("/D \"%s_DLL_EXPORT\"", info.m_RootName ); 
		dllExport.MakeUpper();
		info.m_pConfig->AddToolSettings(varTool, (char const*)dllExport, varj);
	}

	// /W4 - Warning level 4
	// /FD - Generates file dependencies
	// /G6 - optimize for pentium pro
	// Add _WIN32, as that's what all our other code uses
	varSwitch = "/W4 /FD /G6 /D \"_WIN32\"";
	info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);

	// Remove Preprocessor def for MFC DLL specifier, _AFXDLL
	// /GX - No exceptions, 
	// /YX - no precompiled headers
	// Remove WIN32, it's put there by default
	varSwitch = "/D \"_AFXDLL\" /GX /YX /D \"WIN32\"";
	info.m_pConfig->RemoveToolSettings(varTool, varSwitch, varj);

	switch (info.m_Config)
	{
	case CONFIG_DEBUG:
		// Remove the following switches (avoid duplicates, in addition to other things)
		varSwitch = "/D \"NDEBUG\" /Zi /ZI /GZ";
		info.m_pConfig->RemoveToolSettings(varTool, varSwitch, varj);

		// Add the following switches:
		// /ZI - Includes debug information in a program database compatible with Edit and Continue. 
		// /Zi - Includes debug information in a program database (no Edit and Continue) 
		// /Od - Disable optimization
		// /GZ - Catch release build problems (uninitialized variables, fp stack problems)
		// /Op - Improves floating-point consistency
		// /Gm - Minimal rebuild
		// /FR - Generate Browse Info
		varSwitch = "/D \"_DEBUG\" /Od /GZ /Op /Gm /FR\"Debug/\"";
		info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);

		if (info.m_Project == PROJECT_LIB)
		{
			// Static libraries cannot use edit-and-continue, why compile it in?
			varSwitch = "/Zi";
			info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);
		}
		else
		{
			varSwitch = "/ZI";
			info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);
		}
		break;

	case CONFIG_RELEASE:
	case CONFIG_RELEASEWITHSYMBOLS:
		// Remove the following switches:
		// /Zi - Generates complete debugging information 
		// /ZI - Includes debug information in a program database compatible with Edit and Continue. 
		// /Gm - Minimal rebuild
		// /GZ - Catch release build problems (uninitialized variables, fp stack problems)
		varSwitch = "/D \"_DEBUG\" /Gm /Zi /ZI /GZ";
		info.m_pConfig->RemoveToolSettings(varTool, varSwitch, varj);

		// Add the following switches:
		// /Ox - Uses maximum optimization (/Ob1gity /Gs)
		// /Ow - Assumes aliasing across function calls 
		// /Op - Improves floating-point consistency 
		// /Gf - String pooling
		// /Oi - Generates intrinsic functions
		// /Ot - Favors fast code 
		// /Og - Uses global optimizations
		// /Gy - Enable function level linking
		varSwitch = "/D \"NDEBUG\" /Ox /Ow /Op /Oi /Ot /Og /Gf /Gy";
		info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);
		break;
	}

	// A little fixup for release with symbols
	if (info.m_Config == CONFIG_RELEASEWITHSYMBOLS)
	{
#ifndef STUPID_MS_BUG
		CString oldPath;
		oldPath.Format("/Fo\"%s___Win32_ReleaseWithSymbols/\" /Fd\"%s___Win32_ReleaseWithSymbols/\" /FR\"%s___Win32_ReleaseWithSymbols/\"", info.m_RootName, info.m_RootName, info.m_RootName  ); 
		info.m_pConfig->RemoveToolSettings(varTool, (char const*)oldPath, varj);

		varSwitch = "/Zi /Fo\"ReleaseWithSymbols/\" /Fd\"ReleaseWithSymbols/\" /FR\"ReleaseWithSymbols/\"";
		info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);
#else
		CString releaseStuff;
		releaseStuff.Format("/D \"_MBCS\" /Zi /FR\"%s___Win32_ReleaseWithSymbols/\"", info.m_RootName ); 
		info.m_pConfig->AddToolSettings(varTool, (char const*)releaseStuff, varj);
#endif
	}

}

//-----------------------------------------------------------------------------
// Deals with resource compiler
//-----------------------------------------------------------------------------

static void SetupResourceCompilerSettings(ProjectInfo_t& info)
{
	_bstr_t varTool;
	_bstr_t varSwitch;
	_variant_t varj = info.m_ConfigIndex;

	// Remove Preprocessor def for MFC DLL specifier, _AFXDLL
	varTool   = "rc.exe";
	varSwitch = "/d \"_AFXDLL\"";
	info.m_pConfig->RemoveToolSettings(varTool, varSwitch, varj);

	if (info.m_Config == CONFIG_RELEASEWITHSYMBOLS)
	{
		varSwitch = "/d \"_DEBUG\"";
		info.m_pConfig->RemoveToolSettings(varTool, varSwitch, varj);

		varSwitch = "/d \"NDEBUG\"";
		info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);
	}
}


//-----------------------------------------------------------------------------
// Deals with linker settings
//-----------------------------------------------------------------------------

static void SetupLinkerSettings(ProjectInfo_t& info)
{
	_bstr_t varTool;
	_bstr_t varSwitch;
	_variant_t varj = info.m_ConfigIndex;

	// We're going to modify settings associated with cl.exe
	varTool   = "link.exe";

	// Remove windows libraries by default... hopefully we don't have windows dependencies
	varSwitch = "kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib";
	info.m_pConfig->RemoveToolSettings(varTool, varSwitch, varj);

	// Add libraries we always want to use
	varSwitch = "tier0.lib";
	info.m_pConfig->AddToolSettings(varTool, varSwitch, varj);
	
#ifndef STUPID_MS_BUG
	if (info.m_Config == CONFIG_RELEASEWITHSYMBOLS)
	{
		// Fixup the pdb file name
		CString pdbFileName;
		pdbFileName.Format( "/pdb:\"%s___Win32_ReleaseWithSymbols/%s.pdb\"", info.m_RootName, info.m_RootName );
		info.m_pConfig->RemoveToolSettings(varTool, (char const*)pdbFileName, varj);

		pdbFileName.Format( "/pdb:\"ReleaseWithSymbols/%s.pdb\"", info.m_RootName );
		info.m_pConfig->AddToolSettings(varTool, (char const*)pdbFileName, varj);

		// Fixup the importlib file name
		if (info.m_Project == PROJECT_DLL)
		{
			CString implibFileName;
			implibFileName.Format( "/implib:\"%s___Win32_ReleaseWithSymbols/%s.lib\"", info.m_RootName, info.m_RootName );
			info.m_pConfig->RemoveToolSettings(varTool, (char const*)implibFileName, varj);

			implibFileName.Format( "/implib:\"ReleaseWithSymbols/%s.lib\"", info.m_RootName );
			info.m_pConfig->AddToolSettings(varTool, (char const*)implibFileName, varj);
		}
	}
#endif

	// Hook in the static library path
	CString libPath;

	// FIXME: Still haven't decided on build-specific publish dir

	if (info.m_Public)
	{
		libPath.Format( "/libpath:\"%slib\\common\\\" /libpath:\"%slib\\public\\\"",
			info.m_RelativeSrcPath, info.m_RelativeSrcPath );
	}
	else
	{
		libPath.Format( "/libpath:\"%slib\\common\\\"", info.m_RelativeSrcPath );
	}

	info.m_pConfig->AddToolSettings(varTool, (char const*)libPath, varj);
}

//-----------------------------------------------------------------------------
// Deals with lib settings
//-----------------------------------------------------------------------------

static void SetupLibSettings(ProjectInfo_t& info)
{
	// ARGHRHGHGHH!!!
	// My beautiful plan of getting everything in a subdirectory that 
	// doesn't has Foo___Win32_ReleaseWithSymbols is toast becasue of this
	// one small bug that causes AddToolSettings to barf when you pass it lib.exe
	// It works with obscure things like midl and bscmake. Why can't
	// they get it right?  WAAAHH!!!

#ifndef STUPID_MS_BUG
	_variant_t varj = info.m_ConfigIndex;

	// Browse settings...
	if (info.m_Config == CONFIG_RELEASEWITHSYMBOLS)
	{
		CString libFile;
		libFile.Format("/out:\"%s___Win32_ReleaseWithSymbols\\%s.lib\"", info.m_RootName, info.m_RootName );
		info.m_pConfig->RemoveToolSettings("lib.exe", (char const*)libFile, varj );

		libFile.Format("/out:\"ReleaseWithSymbols\\%s.lib\"", info.m_RootName );
		info.m_pConfig->AddToolSettings("lib.exe", (char const*)libFile, varj );
	}
#endif
}

//-----------------------------------------------------------------------------
// Deals with custom build steps
//-----------------------------------------------------------------------------

static void SetupCustomBuildSteps(ProjectInfo_t& info)
{
	// Create the custom build steps
	CString copySteps;
	CString targets;

	if (info.m_Project == PROJECT_LIB)
	{
		CString targetPath;

		targetPath.Format( "%s%s.%s", info.m_RelativeTargetPath, info.m_RootName, info.m_Ext );

		// NOTE: The second attrib is commented out because I'm fairly certain it causes
		// a bug in VSS to make it overwrite the target on a get
		copySteps.Format( 
			"if exist %s attrib -r %s\n"
			"copy $(TargetPath) %s\n"
			"if exist $(TargetDir)\\%s.map copy $(TargetDir)\\%s.map %s%s.map", 
			targetPath, targetPath,
			targetPath,
			info.m_RootName, info.m_RootName, info.m_RelativeTargetPath, info.m_RootName );

		targets.Format( "%s\n", targetPath );
	}
	else
	{
		CString targetPath;
		targetPath.Format( "%s%s.%s", info.m_RelativeTargetPath, info.m_RootName, info.m_Ext );

		// NOTE: The second attrib is commented out because I'm fairly certain it causes
		// a bug in VSS to make it overwrite the target on a get
		copySteps.Format( 
			"if exist %s attrib -r %s\n"
			"copy $(TargetPath) %s\n"
			"if exist $(TargetDir)\\%s.map copy $(TargetDir)\\%s.map %s%s.map", 
			targetPath, targetPath,
			targetPath,
			info.m_RootName, info.m_RootName, info.m_RelativeTargetPath, info.m_RootName );

		targets.Format( "%s", targetPath );

		if ((info.m_Project == PROJECT_DLL) && info.m_PublishImportLib)
		{
			CString targetPath;

 			targetPath.Format( "%s%s.lib", info.m_RelativeImplibPath, info.m_RootName );

			CString impLibCopy;
			impLibCopy.Format( 
				"\nif exist %s attrib -r %s\n"
				"if exist $(TargetDir)\\%s.lib copy $(TargetDir)\\%s.lib %s\n",
				targetPath, targetPath,
				info.m_RootName, info.m_RootName, targetPath
				);
			copySteps += impLibCopy;

			CString implibTarget;
			implibTarget.Format( "\n%s", targetPath );
			targets += implibTarget;
		}
	}

	CString publishDir;
	publishDir.Format( "Publishing to target directory (%s)...", info.m_RelativeTargetPath );
	info.m_pConfig->AddCustomBuildStep( (char const*)copySteps, (char const*)targets, (char const*)publishDir );
}

//-----------------------------------------------------------------------------
// Deals with MIDL build steps
//-----------------------------------------------------------------------------

static void SetupMIDLSettings(ProjectInfo_t& info)
{
	_bstr_t varSwitch;
	_variant_t varj = info.m_ConfigIndex;

	// MIDL settings...
	if (info.m_Config == CONFIG_RELEASEWITHSYMBOLS)
	{
		varSwitch = "/D \"_DEBUG\"";
		info.m_pConfig->RemoveToolSettings("midl", varSwitch, varj);

		varSwitch = "/D \"NDEBUG\"";
		info.m_pConfig->AddToolSettings("midl", varSwitch, varj);
	}
}

//-----------------------------------------------------------------------------
// Deals with browse build steps
//-----------------------------------------------------------------------------

static void SetupBrowseSettings(ProjectInfo_t& info)
{
#ifndef STUPID_MS_BUG
	_variant_t varj = info.m_ConfigIndex;

	// Browse settings...
	if (info.m_Config == CONFIG_RELEASEWITHSYMBOLS)
	{
		CString browseFile;
		browseFile.Format("/o\"%s___Win32_ReleaseWithSymbols/%s.bsc\"", info.m_RootName, info.m_RootName );
		info.m_pConfig->RemoveToolSettings("bscmake", (char const*)browseFile, varj );

		browseFile.Format("/o\"ReleaseWithSymbols/%s.bsc\"", info.m_RootName );
		info.m_pConfig->AddToolSettings("bscmake", (char const*)browseFile, varj );
	}
#endif
}

void CValvelibAppWiz::CustomizeProject(IBuildProject* pProject)
{
	// This is called immediately after the default Debug and Release
	//  configurations have been created for each platform.  You may customize
	//  existing configurations on this project by using the methods
	//  of IBuildProject and IConfiguration such as AddToolSettings,
	//  RemoveToolSettings, and AddCustomBuildStep. These are documented in
	//  the Developer Studio object model documentation.

	// WARNING!!  IBuildProject and all interfaces you can get from it are OLE
	//  COM interfaces.  You must be careful to release all new interfaces
	//  you acquire.  In accordance with the standard rules of COM, you must
	//  NOT release pProject, unless you explicitly AddRef it, since pProject
	//  is passed as an "in" parameter to this function.  See the documentation
	//  on CCustomAppWiz::CustomizeProject for more information.

	using namespace DSProjectSystem;

	long lNumConfigs;
	IConfigurationsPtr pConfigs;
	IBuildProjectPtr pProj;

	// Needed to convert IBuildProject to the DSProjectSystem namespace
	pProj.Attach((DSProjectSystem::IBuildProject*)pProject, true);

	// Add a release with symbols configuration
	pProj->AddConfiguration( "ReleaseWithSymbols" );

	pProj->get_Configurations(&pConfigs);
	pConfigs->get_Count(&lNumConfigs);

	// Needed for OLE2T below
	USES_CONVERSION;
	CComBSTR configName;

	ProjectInfo_t info;
	if (!Valvelibaw.m_Dictionary.Lookup("VALVE_RELATIVE_PATH", info.m_RelativeTargetPath))
		return;
	if (!Valvelibaw.m_Dictionary.Lookup("root", info.m_RootName))
		return;
	if (!Valvelibaw.m_Dictionary.Lookup("VALVE_TARGET_TYPE", info.m_Ext))
		return;
	if (!Valvelibaw.m_Dictionary.Lookup("VALVE_ROOT_RELATIVE_PATH", info.m_RelativeRootPath))
		return;
	if (!Valvelibaw.m_Dictionary.Lookup("VALVE_SRC_RELATIVE_PATH", info.m_RelativeSrcPath))
		return;
	if (!Valvelibaw.m_Dictionary.Lookup("VALVE_TARGET_PATH", info.m_TargetPath))
		return;

	CString tmp;
	info.m_Tool = (Valvelibaw.m_Dictionary.Lookup("VALVE_TOOL", tmp) != 0);
	info.m_Console = (Valvelibaw.m_Dictionary.Lookup("PROJTYPE_CON", tmp) != 0);
	info.m_Public = (Valvelibaw.m_Dictionary.Lookup("VALVE_PUBLIC_PROJECT", tmp) != 0);
	info.m_PublishImportLib = (Valvelibaw.m_Dictionary.Lookup("VALVE_PUBLISH_IMPORT_LIB", tmp) != 0);
	info.m_Project = PROJECT_LIB;
	if ( strstr( info.m_Ext, "dll" ) != 0 )
	{
		info.m_Project = PROJECT_DLL;
		if (!Valvelibaw.m_Dictionary.Lookup("VALVE_IMPLIB_RELATIVE_PATH", info.m_RelativeImplibPath))
			return;
	}
	else if ( strstr( info.m_Ext, "exe" ) != 0 )
	{
		info.m_Project = PROJECT_EXE;
	}

	//Get each individual configuration
	for (long j = 1 ; j <= lNumConfigs ; j++)
	{
		_bstr_t varTool;
		_bstr_t varSwitch;
		_variant_t varj = j;
		info.m_ConfigIndex = j;

		info.m_pConfig = pConfigs->Item(varj);
		
		// Figure if we're debug or release
		info.m_pConfig->get_Name( &configName );
		info.m_Config = CONFIG_RELEASE;
		info.m_BuildName = "Release";
		if ( strstr( OLE2T(configName), "Debug" ) != 0 )
		{
			info.m_Config = CONFIG_DEBUG;
			info.m_BuildName = "Debug";
		}
		else if ( strstr( OLE2T(configName), "ReleaseWithSymbols" ) != 0 )
		{
			info.m_Config = CONFIG_RELEASEWITHSYMBOLS;
			info.m_BuildName = "ReleaseWithSymbols";
		}

		// Not using MFC...
		info.m_pConfig->AddToolSettings("mfc", "0", varj);

		SetupCompilerSettings(info);
		SetupResourceCompilerSettings(info);
		if (info.m_Project != PROJECT_LIB)
		{
			SetupLinkerSettings(info);

			if (!info.m_Console)
				SetupMIDLSettings(info);
		}
		else
		{
			SetupLibSettings(info);
		}
		SetupCustomBuildSteps(info);
		SetupBrowseSettings(info);

		info.m_pConfig->MakeCurrentSettingsDefault();
	}
}
	

// Here we define one instance of the CValvelibAppWiz class.  You can access
//  m_Dictionary and any other public members of this class through the
//  global Valvelibaw.
CValvelibAppWiz Valvelibaw;

