/* Installer.cpp: Merge7z plugin installer
 * Copyright (c) 2005 Jochen Tucht
 *
 * License:	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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Remarks:	Bundles plugins for all supported versions of 7-Zip in a single
 *			installer. Also includes essential components from latest 7-Zip
 *			stable release for optional standalone operation.
 *			Files to be installed are embedded as resources (see Files.rc2).
 *			The resulting exe must be run through UPX (upx.sourceforge.net)
 *			to reduce size.

Please mind 2. a) of the GNU General Public License, and log your changes below.

DATE:		BY:					DESCRIPTION:
==========	==================	================================================
2005-01-15	Jochen Tucht		Created
2005-02-28	Jochen Tucht		Initialize filename in Open dialog to "*.exe"
2005-04-26	Jochen Tucht		No default assumption on program directory
								Double-click option for in-place extraction
								Fix empty path issue with GetFileTitle()
								Accept extraction folder on command line
								Batch options: /standalone, /select, /commit
2005-05-30	Jochen Tucht		Standalone option now based on 7z420
2005-06-28	Jochen Tucht		Standalone option now based on 7z423
2005-12-04	Jochen Tucht		Standalone option now based on 7z431
2005-12-09	Jochen Tucht		Standalone option now based on 7z432
2006-06-28	Jochen Neubeck		Standalone option now based on 7z442
2007-12-22	Jochen Neubeck		Standalone option now based on 7z457
*/

#include <windows.h>

#define VERSION7Z 4.57

#pragma intrinsic(memset) // do not depend on CRT

#define SHARPEN(X) #X
#define SHARPEN2(X) SHARPEN(X)

// Compute dwBuild from revision.txt
static const DWORD dwBuild =
(
	sizeof""
#	define VERSION(MAJOR,MINOR)
#	include "../Merge7z/revision.txt"
#	undef VERSION
);

static SYSTEMTIME st = {0,0,0,0,0,0,0,0};

LPTSTR NTAPI ArgLower(LPTSTR lpCmdLine)
{
	while (*lpCmdLine == VK_SPACE)
		++lpCmdLine;
	return lpCmdLine;
}

LPTSTR NTAPI ArgUpper(LPTSTR lpCmdLine)
{
	TCHAR cSpace = VK_SPACE;
	while (*lpCmdLine && *lpCmdLine != cSpace)
	{
		if (*lpCmdLine == '"')
		{
			cSpace ^= VK_SPACE;
		}
		++lpCmdLine;
	}
	return lpCmdLine;
}

int NTAPI PathGetTailLength(LPCTSTR path)
{
	//GetFileTitle() returns garbage when passed in empty path...
	return *path ? GetFileTitle(path, 0, 0) : 0;
}

void InstallFile(HWND hWnd, LPTSTR lpName, LPCTSTR lpType, LPTSTR path, int cchPath)
{
	HMODULE hModule = GetModuleHandle(0);
	HRSRC hResource = FindResource(hModule, lpName, lpType);
	DWORD dwSize = SizeofResource(hModule, hResource);
	LPVOID pResource = LoadResource(hModule, hResource);
	LPTSTR name = path + cchPath;
	if (name != lpName)
	{
		lstrcpy(name, lpName);
	}
	int cchName = lstrlen(name);
	int i;
	for (i = 0 ; i < cchName ; ++i)
	{
		if (name[i] == '/')
		{
			name[i] = '\0';
			CreateDirectory(path, 0);
			name[i] = '\\';
		}
	}
	HANDLE hFile = CreateFile(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
	if (hFile != INVALID_HANDLE_VALUE)
	{
		DWORD dwNumberOfBytesWritten;
		BOOL bSuccess = WriteFile(hFile, pResource, dwSize, &dwNumberOfBytesWritten, 0);
		FILETIME ft;
		if (SystemTimeToFileTime(&st, &ft))
		{
			SetFileTime(hFile, &ft, &ft, &ft);
		}
		CloseHandle(hFile);
		if (!bSuccess || dwNumberOfBytesWritten != dwSize)
		{
			hFile = INVALID_HANDLE_VALUE;
		}
	}
	if (hFile == INVALID_HANDLE_VALUE)
	{
		LONG error = GetLastError();
		name[cchName++] = '\n';
		FormatMessage
		(
			FORMAT_MESSAGE_FROM_SYSTEM,
			NULL, error,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
			name + cchName, MAX_PATH, NULL
		);
		int response = MessageBox(hWnd, path, 0, MB_ICONSTOP|MB_OKCANCEL);
		if (response == IDCANCEL)
		{
			ExitProcess(1);
		}
	}
}

BOOL CALLBACK fnPopulateList(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam)
{
	TCHAR acName[MAX_PATH];
	if (ATOM aName = FindAtom(lpName))
	{
		GetAtomName(aName, lpName = acName, sizeof acName);
	}
	SendDlgItemMessage((HWND)lParam, 100, LB_ADDSTRING, 0, (LPARAM)lpName);
	return TRUE;
}

BOOL CALLBACK fnInstallFiles(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam)
{
	HWND hWnd = (HWND)lParam;
	TCHAR path[8 * MAX_PATH];
	int cchPath = GetDlgItemText(hWnd, 203, path, 5 * MAX_PATH);
	int cchName = PathGetTailLength(path);
	if (cchName < cchPath)
	{
		cchPath -= cchName;
	}
	if (cchPath != 3 || path[1] != ':')
	{
		path[cchPath++] = '\\';
	}
	TCHAR acName[MAX_PATH];
	if (ATOM aName = FindAtom(lpName))
	{
		GetAtomName(aName, lpName = acName, sizeof acName);
	}
	InstallFile(hWnd, lpName, lpType, path, cchPath);
	return TRUE;
}

BOOL CALLBACK DlgMain_InitDialog(HWND hWnd, LPARAM lParam)
{
	char date[] = __DATE__; // Compilation date "MMM DD YYYY"

	TCHAR path[5 * MAX_PATH];
	wsprintf(path + GetWindowText(hWnd, path, MAX_PATH),
		" (dllbuild %04lu, %s)", dwBuild, date);
	SetWindowText(hWnd, path);

	TCHAR fmt[MAX_PATH];
	GetDlgItemText(hWnd, 205, fmt, MAX_PATH);
	wsprintf(path, fmt, SHARPEN2(VERSION7Z));
	SetDlgItemText(hWnd, 205, path);

	date[6] = '#';
	st.wYear = FindAtom(&date[6]);
	date[6] = '\0';
	date[3] = '#';
	st.wDay = FindAtom(&date[3]);
	date[3] = '\0';
	st.wMonth = 1;
	const char *month = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
	while (lstrcmpi(month, date))
	{
		++st.wMonth;
		month += 4;
	} 
	st.wHour = 12;

	EnumResourceNames(GetModuleHandle(0), "Merge7z", fnPopulateList, (LONG)hWnd);
	LONG lCount = SendDlgItemMessage(hWnd, 100, LB_GETCOUNT , 0, 0);
	SendDlgItemMessage(hWnd, 100, LB_SELITEMRANGEEX, 0, lCount - 1);
	CheckRadioButton(hWnd, 201, 202, 201);
	BOOL bCommit = FALSE;
	BOOL bSelect = FALSE;
	LPTSTR lpCmdLine = GetCommandLine();
	LPTSTR lpArgLower = ArgLower(lpCmdLine);
	LPTSTR lpArgUpper = ArgUpper(lpArgLower);
	while (*(lpArgLower = ArgLower(lpArgUpper)))
	{
		TCHAR cAhead = *(lpArgUpper = ArgUpper(lpArgLower));
		*lpArgUpper = '\0';
		if (0 == lstrcmpi(lpArgLower, "/standalone"))
		{
			CheckRadioButton(hWnd, 201, 202, 202);
			SendMessage(hWnd, WM_COMMAND, 202, 0);
			CheckDlgButton(hWnd, 205, 1);
			SendMessage(hWnd, WM_COMMAND, 205, 0);
		}
		else if (0 == lstrcmpi(lpArgLower, "/select"))
		{
			int lower = -1;
			int upper = -1;
			*lpArgUpper = cAhead;
			if (*(lpArgLower = ArgLower(lpArgUpper)))
			{
				cAhead = *(lpArgUpper = ArgUpper(lpArgLower));
				*lpArgUpper = '\0';
				lower = SendDlgItemMessage(hWnd, 100, LB_FINDSTRING, -1, (LPARAM)lpArgLower);
				if (lower == -1)
				{
					MessageBox(hWnd, lpArgLower, "No match", MB_ICONSTOP);
				}
			}
			*lpArgUpper = cAhead;
			if (*(lpArgLower = ArgLower(lpArgUpper)))
			{
				cAhead = *(lpArgUpper = ArgUpper(lpArgLower));
				*lpArgUpper = '\0';
				int ahead = -1;
				while ((ahead = SendDlgItemMessage(hWnd, 100, LB_FINDSTRING, ahead, (LPARAM)lpArgLower)) > upper)
				{
					upper = ahead;
				}
				if (upper == -1)
				{
					MessageBox(hWnd, lpArgLower, "No match", MB_ICONSTOP);
				}
			}
			if (lower >= 0 && upper >= 0)
			{
				if (!bSelect)
				{
					SendDlgItemMessage(hWnd, 100, LB_SETSEL, 0, -1);
					bSelect = TRUE;
				}
				SendDlgItemMessage(hWnd, 100, LB_SELITEMRANGEEX, lower, upper);
			}
		}
		else if (0 == lstrcmpi(lpArgLower, "/commit"))
		{
			bCommit = TRUE;
		}
		/*//just for test
		else if (0 == lstrcmpi(lpArgLower, "\"ping pong\""))
		{
			MessageBox(hWnd, "", lpArgLower, 0);
		}*/
		else
		{
			DWORD dwAttributes = GetFileAttributes(lpArgLower);
			if (dwAttributes != 0xFFFFFFFF && dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				lstrcpy(path, lpArgLower);
				if (PathGetTailLength(path) > 1)
				{
					lstrcat(path, "\\");
				}
				lstrcat(path, "*.exe");
				CheckRadioButton(hWnd, 201, 202, 202);
				SendMessage(hWnd, WM_COMMAND, 202, 0);
				SetDlgItemText(hWnd, 203, path);
			}
			else
			{
				MessageBox(hWnd, lpArgLower, "Not a directory", MB_ICONSTOP);
			}
		}
		*lpArgUpper = cAhead;
	}
	if (bCommit)
	{
		SendMessage(hWnd, WM_COMMAND, IDOK, 0);
	}
	return TRUE;
}

BOOL CALLBACK DlgMain_BrowseExe(HWND hWnd)
{
	struct
	{
		OPENFILENAME ofn;
		TCHAR buffer[5 * MAX_PATH];
	} path;
	ZeroMemory(&path, sizeof path);
	path.ofn.lStructSize = sizeof path.ofn;
	path.ofn.hwndOwner = hWnd;
	path.ofn.lpstrFile = path.buffer;
	path.ofn.nMaxFile = sizeof path.buffer;
	path.ofn.lpstrFilter = "*.exe\0*.exe\0";
	path.ofn.lpstrTitle = "Browse for application ...";
	path.ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_PATHMUSTEXIST;
	int cchPath = GetDlgItemText(hWnd, 203, path.buffer, sizeof path.buffer);
	int cchName = PathGetTailLength(path.buffer);
	if (cchName < cchPath)
	{
		lstrcpy(path.buffer + cchPath - cchName, "\\*.exe");
	}
	if (GetOpenFileName(&path.ofn))
	{
		SetDlgItemText(hWnd, 203, path.buffer);
	}
	return FALSE;
}

BOOL CALLBACK DlgMain_InstallFiles(HWND hWnd)
{
	HCURSOR hCursor = SetCursor(LoadCursor(0, IDC_WAIT));
	HINSTANCE hModule = GetModuleHandle(0);
	int count = SendDlgItemMessage(hWnd, 100, LB_GETCOUNT , 0, 0);
	int index = 0;
	TCHAR path[8 * MAX_PATH];
	int cchPath;
	if (IsDlgButtonChecked(hWnd, 201))
	{
		cchPath = GetSystemDirectory(path, 4 * MAX_PATH);
	}
	else
	{
		cchPath = GetDlgItemText(hWnd, 203, path, 5 * MAX_PATH);
		int cchName = PathGetTailLength(path);
		if (cchName < cchPath)
		{
			cchPath -= cchName;
		}
	}
	if (cchPath != 3 || path[1] != ':')
	{
		path[cchPath++] = '\\';
	}
	LPTSTR name = path + cchPath;
	while (index < count)
	{
		if (SendDlgItemMessage(hWnd, 100, LB_GETSEL, index, 0))
		{
			int cchName = SendDlgItemMessage(hWnd, 100, LB_GETTEXT, index, (LPARAM)name);
			InstallFile(hWnd, name, "Merge7z", path, cchPath);
		}
		++index;
	}
	if (IsDlgButtonChecked(hWnd, 205))
	{
		EnumResourceNames(GetModuleHandle(0), "7-ZIP", fnInstallFiles, (LONG)hWnd);
	}
	SetCursor(hCursor);
	return TRUE;
}

BOOL CALLBACK DlgMain_EnableStandalone(HWND hWnd)
{
	if (IsDlgButtonChecked(hWnd, 205))
	{
		TCHAR buffer[40];
		const UINT major = UINT(VERSION7Z);
		const UINT minor = UINT(VERSION7Z * 100) % 100;
		wsprintf(buffer, "Merge7z%u%02u.dll", major, minor);
		int lower = SendDlgItemMessage(hWnd, 100, LB_FINDSTRINGEXACT, -1, (LPARAM)buffer);
		wsprintf(buffer, "Merge7z%u%02uU.dll", major, minor);
		int upper = SendDlgItemMessage(hWnd, 100, LB_FINDSTRINGEXACT, -1, (LPARAM)buffer);
		SendDlgItemMessage(hWnd, 100, LB_SELITEMRANGEEX, lower, upper);
		if (GetFocus() == GetDlgItem(hWnd, 205))
		{
			SendDlgItemMessage(hWnd, 100, LB_SETTOPINDEX, lower, 0);
		}
	}
	return TRUE;
}

BOOL CALLBACK DlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	TCHAR path[8 * MAX_PATH];
	switch (uMsg)
	{
	case WM_INITDIALOG:
		return DlgMain_InitDialog(hWnd, lParam);
	case WM_COMMAND:
		switch (wParam)
		{
		case 201:
			CheckDlgButton(hWnd, 205, 0);
			//fall through
		case 202:
			EnableWindow(GetDlgItem(hWnd, 203), wParam == 202);
			EnableWindow(GetDlgItem(hWnd, 204), wParam == 202);
			EnableWindow(GetDlgItem(hWnd, 205), wParam == 202);
			SetDlgItemText(hWnd, 203, "");
			break;
		case MAKELONG(202, BN_DOUBLECLICKED):
			GetModuleFileName(0, path, sizeof path);
			SetDlgItemText(hWnd, 203, path);
			break;
		case MAKELONG(203, EN_CHANGE):
			EnableWindow(GetDlgItem(hWnd, IDOK), GetWindowTextLength((HWND)lParam) || IsDlgButtonChecked(hWnd, 201));
			break;
		case 204:
			return DlgMain_BrowseExe(hWnd);
		case 205:
			if (IsDlgButtonChecked(hWnd, 205) && GetKeyState(VK_SHIFT) >= 0)
				SendDlgItemMessage(hWnd, 100, LB_SETSEL, 0, -1);
			//fall through
		case MAKELONG(100, LBN_SELCHANGE):
			return DlgMain_EnableStandalone(hWnd);
		case IDOK: if (DlgMain_InstallFiles(hWnd)) case IDCANCEL:
			EndDialog(hWnd, wParam);
			break;
		}
		return TRUE;
	}
	return FALSE;
}

void WinMainCRTStartup(void)
{
	InitAtomTable(0x3001);
	HINSTANCE hModule = GetModuleHandle(0);
	HRSRC hResource = FindResource(hModule, "Files.rc2", RT_RCDATA);
	char *q = (char *)LoadResource(hModule, hResource);
	DWORD n = SizeofResource(hModule, hResource);
	char directive[MAX_PATH];
	char arguments[MAX_PATH];
	char *p = 0;
	char c = '\n';
	while (n)
	{
		switch (c)
		{
		case '(':
			p = arguments;
			break;
		case ')':
			if (*directive == '\n')
				AddAtom(arguments);
			//fall through
		case '\n':
		case '#':
			p = directive;
			//fall through
		default:
			*p++ = c;
			*p = '\0';
			break;
		}
		c = *q++;
		--n;
	}
	DialogBoxParam(hModule, MAKEINTRESOURCE(100), 0, DlgMain, 0);
	ExitProcess(0);
}
