// zapdlg.cpp : implementation file
//
// Copyright (c) 1985-1995, Microsoft Corporation. All rights reserved.
//

#include "stdafx.h"
#include "customwz.h"
#include "zapdlg.h"
#include "sampleaw.h"
#include "zap.h"
#include "paint.h"

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

//static CTypedPtrMap<CMapStringToPtr, CString, int> g_TemplateNameCollisions;
static CMapStringToPtr g_TemplateNameCollisions;
CStringList g_TemplateNames;

static BOOL FindDuplicateFileNames(LPTSTR pstrPath, CMapStringToPtr &rmapFileNamesFound);

void MakeTemplateName(CString& strTemplateName, LPCTSTR szFileName)
{
	// Make template name upper-case
	strTemplateName = szFileName;
	strTemplateName.MakeUpper();

	// See if this template name collides with others we're using
	int nNextDigit;
	if (g_TemplateNameCollisions.Lookup(strTemplateName, (void *&)nNextDigit))
	{
		// There is a collision, so append a digit to the name & update
		//  collisions map
		CString strOldTemplateName = strTemplateName;
		strTemplateName.Format("%s%d", (LPCTSTR) strOldTemplateName, nNextDigit);
		g_TemplateNameCollisions[strOldTemplateName] = (void *) ++nNextDigit;

		// Call self recusively to make sure the new name doesn't collide
		MakeTemplateName(strTemplateName, strTemplateName);
	}
	else
	{
		// No collision, so update collision map for the future.
		g_TemplateNameCollisions[strTemplateName] = (void *)2;
	}
}

// This determines what type of project we're zapping.  The heuristic used:
// if no TARGTYPE line in the project makefile (or it doesn't fit criteria below), type is unknown
// TARGTYPE line has 1 as low byte of the value, type is projExe
// TARGTYPE line has 2 as low byte of the value, type is projDLL
// TARGTYPE line has 8 as high byte of the value, type is projJava
enProjType GetProjectType(LPCTSTR lpszProjName)
{
	#define SIZEBUF 256
	char szBuf[SIZEBUF];

	DWORD dwTargType = 0;
	CStdioFile file(lpszProjName, CFile::modeRead | CFile::typeText);

	// Read through makefile line by line
	while (file.ReadString(szBuf, SIZEBUF-1) != NULL)
	{
		// Is this line a "# TARGTYPE" line?  If so, grab the TARGTYPE value
		if (sscanf(szBuf, _T("# TARGTYPE \"%*[^\"]\" 0x%lx"), &dwTargType) == 1)
			break;
	}

	// Check the TARGTYPE value to see what it is
	if ((dwTargType & 0xFF00) == 0x800)
		return projJava;
	else if ((dwTargType & 0x00FF) == 1)
		return projExe;
	else if ((dwTargType & 0x00FF) == 2)
		return projDLL;
	else
		return projUnknown;
}


// This helper (used in TraverseDirectory) doubles occurrences of the backslash
//  character in szPath, so the path can be enclosed in double-quotes in the
//  resource file.
void DoubleSlashes(CString& strPathDblSlashes, LPCTSTR szPath)
{
	strPathDblSlashes.Empty();
	while (*szPath != '\0')
	{
		if (*szPath == '\\')
			strPathDblSlashes += "\\\\";
		else
			strPathDblSlashes += *szPath;
		szPath = _tcsinc(szPath);
	}
}

// This helper (used in TraverseDirectory) determines whether we should skip
//  zapping files in the given directory based on its name.
inline BOOL ShouldSkipDirectory(LPCTSTR szDir)
{
	return (!_tcscmp(szDir, _T(".")) || !_tcscmp(szDir, _T(".."))
		|| !_tcsicmp(szDir, _T("debug")) || !_tcsicmp(szDir, _T("release"))
		|| !_tcsicmp(szDir, _T("windebug")) || !_tcsicmp(szDir, _T("winrel"))
		|| !_tcsicmp(szDir, _T("pmcdebug")) || !_tcsicmp(szDir, _T("pmcrel"))
		|| !_tcsicmp(szDir, _T("macdebug")) || !_tcsicmp(szDir, _T("macrel")));
}

// This helper (used in TraverseDirectory) determines whether we should skip
//  zapping a file based on its extension.
inline BOOL ShouldSkipFile(LPCTSTR szExt)
{
	return (szExt != NULL &&
		(!_tcsicmp(szExt, _T(".mak")) || !_tcsicmp(szExt, _T(".vcp")) || !_tcsicmp(szExt, _T(".aps"))
			|| !_tcsicmp(szExt, _T(".sbr")) || !_tcsicmp(szExt, _T(".plg")) || !_tcsicmp(szExt, _T(".obj"))
			|| !_tcsicmp(szExt, _T(".pch")) || !_tcsicmp(szExt, _T(".res")) || !_tcsicmp(szExt, _T(".ilk"))
			|| !_tcsicmp(szExt, _T(".rsc")) || !_tcsicmp(szExt, _T(".bsc")) || !_tcsicmp(szExt, _T(".exe"))
			|| !_tcsicmp(szExt, _T(".bld")) || !_tcsicmp(szExt, _T(".opt")) || !_tcsicmp(szExt, _T(".pjx"))
			|| !_tcsicmp(szExt, _T(".dll")) || !_tcsicmp(szExt, _T(".pdb")) || !_tcsicmp(szExt, _T(".ncb"))
			|| !_tcsicmp(szExt, _T(".mdp")) || !_tcsicmp(szExt, _T(".dsw")) || !_tcsicmp(szExt, _T(".dsp"))));
}

// This recursive function is called from SetProjectFilesMacros with references
//  to CStrings that correspond to template macros we're setting.  It
//  traverses the given directory and updates these strings with names of
//  files it will zap into templates for the generated custom AppWizard.
void TraverseDirectory(const CString& strProjDir, const CString& strSubdir, CString& strProjFiles,
	CString& strTemplateSubdirs, CString& strTemplateRsc, CString& strGeneratedNewProjInfoDirs)
{
	WIN32_FIND_DATA ffd;
	HANDLE hSearch = ::FindFirstFile(strProjDir + strSubdir + _T("*.*"), &ffd);
	if (hSearch == INVALID_HANDLE_VALUE)	// Bad search handle
	{
		if (strSubdir.IsEmpty())
			// We've hit a bad search handle immediately after being called--
			//  the project directory is invalid.  Throw exception.
			ReportAndThrow(IDP_BAD_PROJDIR, strProjDir + strSubdir);
		else
			// We've hit a bad search handle in a recursive call--
			//  empty project subdirectory.  Just ignore it.
			return;
	}

	do
	{
		if (ffd.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
		{
			// If it's a directory, recursively call self with the directory,
			//  and note the directory structure in the custom AppWizard's newproj.inf
			if (ShouldSkipDirectory(ffd.cFileName))
				continue;

			CString strNewSubdir = (strSubdir + ffd.cFileName) + _T('\n');
			strGeneratedNewProjInfoDirs += _T('/') + strNewSubdir;
			strTemplateSubdirs += _T('/') + (_T("template\\") + strNewSubdir);
			CString strNewDir = strSubdir;
			strNewDir += ffd.cFileName;
			strNewDir += _T('\\');
			TraverseDirectory(strProjDir, strNewDir, strProjFiles, strTemplateSubdirs,
				strTemplateRsc, strGeneratedNewProjInfoDirs);
			continue;
		}
	
		// Skip if it's a file we know we shouldn't copy (e.g., the makefile, .vcp file,
		//  .aps file, etc.).
		LPCTSTR lpch = _tcsrchr(ffd.cFileName, _T('.'));
		if (ShouldSkipFile(lpch))
			continue;

		// Skip if it has no length
		if (ffd.nFileSizeLow == 0 && ffd.nFileSizeHigh == 0)
			continue;

		// Zap the file's name
		CString strRootFile;
		sampleaw.m_Zap.ZapFileName(ffd.cFileName, strRootFile, 1);

		// Now that we have the info we need, update the strings we were passed

		// First, the string used in CUSTMWZ.DLL's newproj.inf:
						// The original project file...
		strProjFiles += (_T("#") + strProjDir + strSubdir + ffd.cFileName)
							// File to emit as custom AppWizard's template...
		 					+ (_T("\ttemplate\\") + strSubdir + strRootFile + _T('\n'));

		// Next, the string used in CUSTMWZ.DLL's root.rc (i.e., the
		//  generated custom AppWizard's resource file).
		CString strSubdirDblSlashes;
		DoubleSlashes(strSubdirDblSlashes, strSubdir);
		CString strTemplateName;
		MakeTemplateName(strTemplateName, strRootFile);
		strTemplateRsc += strTemplateName + 
			_T("\tTEMPLATE DISCARDABLE\t\"template\\\\") + strSubdirDblSlashes + strRootFile +
			_T("\"\n");
		// Remember the template name for later, when we're generating lines in
		//  CUSTMWZ.DLL's newnwprj.inf (i.e., the generated custom AppWizard's
		//  newproj.inf).
		g_TemplateNames.AddTail(strTemplateName);

	}
	while (::FindNextFile(hSearch,  &ffd));
	::FindClose(hSearch);
}


// This function drives it all.  It's called just before the zap dialog is
//  dismissed, and coordinates setting the template macros that correspond
//  with the set of files to be zapped.
void SetProjectFilesMacros(LPCTSTR lpszSrcProjName)
{
	// In order to allow the user to click "Finish" from the first step,
	//  we should gracefully handle the case that no project was named to zap.
	if (lpszSrcProjName == NULL || *lpszSrcProjName == _T('\0'))
	{
		// If project name not specified, empty out the zapper macros
		DefineBoolMacro(_T("ZAP_FULL_PATH"), FALSE);
		DefineBoolMacro(_T("PROJECT_FILES"), FALSE);
		DefineBoolMacro(_T("GENERATED_NEWPROJ_INF_FILES"), FALSE);
		DefineBoolMacro(_T("GENERATED_NEWPROJ_INF_DIRS"), FALSE);
		DefineBoolMacro(_T("TEMPLATE_RSC"), FALSE);
		return;
	}

	// Here, we have a valid, non-NULL project name to zap.  First, get its
	//  full path name
	CString strFullPath;
	_tfullpath(strFullPath.GetBuffer(_MAX_PATH), lpszSrcProjName, _MAX_PATH);
	strFullPath.ReleaseBuffer();
	DefineStringMacro(_T("ZAP_FULL_PATH"), strFullPath);

	// Divide the path name into the various components.
	CString strDrive, strDir, strName, strExt;
	_tsplitpath(strFullPath, strDrive.GetBuffer(_MAX_DRIVE), strDir.GetBuffer(_MAX_DIR),
		strName.GetBuffer(_MAX_FNAME), strExt.GetBuffer(_MAX_EXT));
	strDrive.ReleaseBuffer();
	strDir.ReleaseBuffer();
	strName.ReleaseBuffer();
	strExt.ReleaseBuffer();
	sampleaw.m_Zap.SetRoot(strDir, strName);
	DefineStringMacro(_T("ZAP_SRC_PROJ"), strName + strExt);
	strDir = strDrive + strDir;

	// Now, declare the strings that will be passed to TraverseDirectory, and
	//  filled with lists of the files we'll zap.  These strings will later
	//  be used as template macros.
	CString strProjFiles;				// Will be $$PROJECT_FILES$$ in newproj.inf
	CString strTemplateSubdirs;				// Will be $$PROJECT_DIRS$$ in newproj.inf
	CString strGeneratedNewProjInfoDirs;// Will be $$GENERATED_NEWPROJ_INF_DIRS$$
	CString strTemplateRsc;				// Will be $$TEMPLATE_RSC$$ in root.rc

	// Set their values
	g_TemplateNameCollisions.RemoveAll();				// Initialize g_TemplateNameCollisions
	g_TemplateNameCollisions[_T("NEWPROJ.INF")] = (void *)2;	//  w/ the two canned template names
	g_TemplateNameCollisions[_T("CONFIRM.INF")] = (void *)2;
	g_TemplateNames.RemoveAll();
	TraverseDirectory(strDir, _T(""), strProjFiles, strTemplateSubdirs, strTemplateRsc, strGeneratedNewProjInfoDirs);

	// Set the template macros to their values
	DefineStringMacro(_T("PROJECT_FILES"), strProjFiles);
	DefineStringMacro(_T("TEMPLATE_SUBDIRS"), strTemplateSubdirs);
	DefineStringMacro(_T("GENERATED_NEWPROJ_INF_DIRS"), strGeneratedNewProjInfoDirs);
	DefineStringMacro(_T("TEMPLATE_RSC"), strTemplateRsc);
}


/////////////////////////////////////////////////////////////////////////////
// CZapDlg dialog


CZapDlg::CZapDlg()
	: CAppWizStepDlg(CZapDlg::IDD)
{
	//{{AFX_DATA_INIT(CZapDlg)
	m_strProjName = _T("");
	//}}AFX_DATA_INIT
}


void CZapDlg::DoDataExchange(CDataExchange* pDX)
{
	CAppWizStepDlg::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CZapDlg)
	DDX_Text(pDX, IDC_PROJ_NAME, m_strProjName);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CZapDlg, CAppWizStepDlg)
	//{{AFX_MSG_MAP(CZapDlg)
	ON_BN_CLICKED(IDC_BROWSE, OnBrowse)
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CZapDlg message handlers

// Validate the name of the project to zap, and set zap-related template
//  macros before dismissing
BOOL CZapDlg::OnDismiss()
{
	UpdateData(TRUE);

	// If the user specifies a nonempty, nonexistent path, balk
	if (!m_strProjName.IsEmpty()
		&& GetFileAttributes(m_strProjName) == 0xffffffff)
		return ReportError(IDP_ZAP_CANT_OPEN_FILE, m_strProjName);
	else if (m_strProjName.IsEmpty())	// no project name, no checking we can do
		return TRUE;

	// WARNING: continuing past this point with an empty project name could yield 
	// unpredictable actions as _splitpath doesn't check for a non-NULL path
	// before trying to break it up into drive and directory (Orion 7985). - dianeme 8/20/96

	CHAR rgchPathBuffer[_MAX_PATH];
	CHAR rgchDrive[_MAX_DRIVE];
	CHAR rgchDirectory[_MAX_DIR];

	_splitpath(m_strProjName, rgchDrive, rgchDirectory, NULL, NULL);
	_makepath(rgchPathBuffer, rgchDrive, rgchDirectory, NULL, NULL);

	CMapStringToPtr map;

	BOOL fFoundDuplicate = FindDuplicateFileNames(rgchPathBuffer, map);

	if (fFoundDuplicate)
		return ReportError(IDP_ZAP_BAD_PROJECT, m_strProjName);
	
	if (m_strProjName.IsEmpty())
		return ReportError(IDP_ZAP_CANT_OPEN_FILE, m_strProjName);

	// Now set the macros corresponding to the files we zap into templates.
	//  This may throw an exception if it runs into an error.  In that
	//  case, we'll return FALSE.
	TRY
	{
		enProjType projType = GetProjectType(m_strProjName);
		if (projType != projExe && projType != projDLL)
			return ReportError(IDP_ZAP_NOT_CPP, m_strProjName);

		DefineBoolMacro(_T("CREATE_DLL_PROJECT"), projType == projDLL);
		SetProjectFilesMacros(m_strProjName);
	}
	CATCH(CFileException, e)
	{
		return ReportError(IDP_ZAP_CANT_OPEN_FILE, m_strProjName);
	}
	AND_CATCH(CException, e)
	{
		return FALSE;
	}
	END_CATCH

	// If we got this far, we've successfully built our list of files
	//  to zap.  We're ready to continue.
	return TRUE;
}

#define BROWSE_DLG_HELP_ID 17304

// Handle the "Browse..." button by popping up a file navigator.
void CZapDlg::OnBrowse() 
{
	CString strBrowseTitle, strFilterString;
	strBrowseTitle.LoadString(IDS_BROWSE_TITLE);
	strFilterString.LoadString(IDS_BROWSE_FILTER);

	CFileDialog dlg(
		TRUE,										// Open File Dialog
		_T("dsp"),									// Default extension
		NULL,										// No default filename
		OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,		// OPENFILENAME flags
		strFilterString);	// Filter strings
	dlg.m_ofn.lpstrTitle = strBrowseTitle;
	dlg.SetHelpID(BROWSE_DLG_HELP_ID);
	if (dlg.DoModal() == IDOK)
		m_strProjName = dlg.GetPathName();

	UpdateData(FALSE);
}

#define STEP2_LEFT			8
#define STEP2_TOP			40
#define STEP2_WIDTH			179
#define STEP2_HEIGHT		224

// Override OnPaint to draw the one static picture on the left side
void CZapDlg::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	PaintBackground(&dc, this);
	
	CDC dcMem;
	if (!dcMem.CreateCompatibleDC(&dc))
		return;

	// Picture
	PaintBitmap(IDB_STEP2, STEP2_LEFT, STEP2_TOP, STEP2_WIDTH, STEP2_HEIGHT, &dc, &dcMem);
}

BOOL FindDuplicateFileNames(LPTSTR ptstrPath, CMapStringToPtr &rmap)
{
	WIN32_FIND_DATA wfd;

	BOOL fResult = FALSE; // Assume no duplicates

	size_t cchOldLength;

#if defined(_UNICODE)
	cchOldLength = wcslen(ptstrPath);
#else
	cchOldLength = strlen(ptstrPath);
#endif

	TCHAR *ptchOldNull = &ptstrPath[cchOldLength];

	_tcscpy(ptchOldNull, TEXT("*.*"));

	HANDLE hFind = ::FindFirstFile(ptstrPath, &wfd);

	if (hFind != INVALID_HANDLE_VALUE)
	{
		// We started a good search.  Let's handle each file now.
		for (;;)
		{
			if ((_tcscmp(wfd.cFileName, TEXT(".")) != 0) &&
				(_tcscmp(wfd.cFileName, TEXT("..")) != 0))
			{
				if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
				{
					// We've got a subdirectory.  Recurse.
					_tcscpy(ptchOldNull, wfd.cFileName);
					_tcscat(ptchOldNull, TEXT("\\"));

					if (FindDuplicateFileNames(ptstrPath, rmap))
					{
						fResult = TRUE;
						break;
					}
				}
				else
				{
					// First, canonicalize the case of the file...
					_tcsupr(wfd.cFileName);

					VOID *pvTemp;

					// If it's in the map, look out!
					if (rmap.Lookup(wfd.cFileName, pvTemp))
					{
						fResult = TRUE;
						break;
					}

					// It wasn't there; put a NULL in the list.
					rmap.SetAt(wfd.cFileName, NULL);
				}
			}

			// I hate to lose status if something more erroneous occurred
			// here, but it seems harmless.  -mgrier 6/12/96
			if (!::FindNextFile(hFind, &wfd))
				break;
		}

		::FindClose(hFind);
	}

	ptchOldNull = TEXT('\0');

	return fResult;
}

