// nshell.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <psapi.h>
#include <shellapi.h>
#include <time.h>
#include <atlbase.h>

#define ID_MENUITEM_quit                40001


BOOL ExitNodoka(LPWSTR szFilePath, LPWSTR szFileName);
BOOL RunProgram(LPWSTR lpCmdLine, BOOL bWait);
BOOL Waitting(LPWSTR lpCmdLine);
BOOL IsHere(LPWSTR szWindowTitle);
BOOL CALLBACK EnumWndProc( HWND hWnd, LPARAM lParam );
void ShowErrorMessage(DWORD dwErr, LPWSTR lpCmdLine);


BOOL gbDetect = FALSE;
HANDLE hConsole = NULL;
DWORD dwConsole = 0;



int APIENTRY _tWinMain(HINSTANCE hInstance,
											 HINSTANCE hPrevInstance,
											 LPTSTR    lpCmdLine,
											 int       nCmdShow)
	{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);


	TCHAR	szFilePath[MAX_PATH * 4 + 1];
	TCHAR	szFileName[MAX_PATH * 4 + 1];	// nodokaのフルパスを受け取るバッファ

	TCHAR szAppFileName[MAX_PATH *4 + 1];	// 指定されたアプリのフルパスを受け取るバッファ
	TCHAR szAppParm[MAX_PATH * 4 + 1];			// 指定されたアプリの引数を受け取るバッファ
	BOOL	bNodoka = FALSE;							// nodokaを終了させた?

	TCHAR szTempText[MAX_PATH];

	BOOL useConsole = FALSE;

	//if(AllocConsole()){
	//	useConsole = TRUE;
	//	hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	//}

	int argc = 0;
	LPWSTR *argv;

	// 初期化
	szFilePath[0] = _T('\0');
	szFileName[0] = _T('\0');

	szAppFileName[0] = _T('\0');
	szAppParm[0] = _T('\0');

	szTempText[0] = _T('\0');

	int	case_number;
	case_number = 0;			// test mode use case 9

	if(case_number >= 9)
		goto test_mode;


	// 引数の解釈　「nshell 実行ファイルフルパス 引数」もしくは、「nshell 実行ファイルフルパス」を想定。
	// ただし、nshell の後ろに書いた引数は、CreateProcess に全て渡す。
	argv = CommandLineToArgvW(GetCommandLineW(), &argc);

	if(argc >= 2)		// nshellの引数が 2未満なら、Warningを出すだけで、何もしない。
		{
		// 引数のチェック
		//MessageBoxW(NULL, argv[1], L"nshell.exe 引数1個目", MB_OK);
		//MessageBoxW(NULL, argv[2], L"nshell.exe 引数2個目", MB_OK);

		// 引数ごとに挙動を変える。
		if(wcscmp(argv[1], L"0") == 0)
			case_number = 4;

		if(wcscmp(argv[1], L"1") == 0)
			case_number = 1;

		if(wcscmp(argv[1], L"2") == 0)
			case_number = 2;

		if(wcscmp(argv[1], L"3") == 0)
			case_number = 3;

        if(wcscmp(argv[1], L"9") == 0)		// for debug
			case_number = 9;

test_mode:
		// nodokaかmayuを探して、終了させる。戻り値はTRUE:　居たので終了させた, FALSE: 居なかった。それと実行ファイルパス
		if(case_number <= 9)
			bNodoka = ExitNodoka((LPWSTR)&szFilePath, (LPWSTR)&szFileName);


		//		wsprintfW(szTempText, TEXT("case: %d, %lS\n"), case_number, lpCmdLine); 
		//		MessageBoxW(NULL, szTempText, L"debug", MB_OK | MB_ICONEXCLAMATION);

		switch(case_number)
			{
			case 1:									// 指定されたプログラムを起動するだけ。待たない。のどかは再起動させない。
				if(argc >= 3)
					{
					RunProgram(argv[2], FALSE);
					}
				else
					{
					wsprintfW(szTempText, TEXT("引数の個数が正しくありません。\n\n1 \"実行ファイル名\"\n\nである必要があります。\n\n%s"), lpCmdLine); 
					MessageBoxW(NULL, szTempText, L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
					}
				bNodoka = FALSE;
				break;

			case 2:									// 指定したプログラムを起動し、指定されたウィンドウタイトルを持つプログラムが終了するまで待つ。
				if(argc >= 4)
					{
					// Check
					if(IsHere(argv[2]))
						{
						wsprintfW(szTempText, TEXT("既に %s のプログラムが検出されたので、「のどか」の再起動は実施しません。"), argv[2]); 
						MessageBoxW(NULL, szTempText, L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
						RunProgram(argv[3], FALSE);
						bNodoka = FALSE;
						}
					else 
						{
						if(RunProgram(argv[3], FALSE))
							if(!Waitting(argv[2]))					// Programの起動に成功したので待つことにする。
								bNodoka = FALSE;							// Waitingにて、1分以内に検出できなかった。
						}
					}
				else
					{
					wsprintfW(szTempText, TEXT("引数の個数が正しくありません。\n\n2 \"ウィンドウタイトル名\" \"実行ファイル名\"\n\nである必要があります。\n\n%s"), lpCmdLine); 
					MessageBoxW(NULL, szTempText, L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
					}
				break;

			case 3:									// 既に「のどか」は終了済みのため、何もしない。
				bNodoka = FALSE;
				break;

			case 4:									// same case 0
				if(argc >= 3)
					RunProgram(argv[2], TRUE);		// 指定されたプログラムを起動し、終了を待つ。
				else
					{
					wsprintfW(szTempText, TEXT("引数の個数が正しくありません。\n\n0 \"実行ファイル名\"\n\nである必要があります。\n\n%s"), lpCmdLine); 
					MessageBoxW(NULL, szTempText, L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
					}
				break;

			case 9:									// debug用
				{
					bNodoka = TRUE;
				}
				break;
			default:								// 指定されたプログラムを起動し、終了するまで待つ。
				RunProgram(lpCmdLine, TRUE);
				break;
			}			

		// nodokaの起動
		if(bNodoka == TRUE)								// nodokaを終了させたのなら
			{
			Sleep(3000);										// 3秒待つ。起動されたアプリがすぐ終了すると、Nodoka終了させていても、まだ居ると判断してエラーになるため。
			
			wsprintfW(szTempText, TEXT("nodokaを起動します。\n%s %s"), szFilePath, szFileName); 
			MessageBoxW(NULL, szTempText, L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);

			ShellExecute(NULL, NULL, szFileName, NULL, szFilePath, SW_SHOWNOACTIVATE);
			}

		// 終了
		}
	else
		{
			MessageBoxW(NULL, L"nshell/nshell64.exe Ver.1.05\n Program Launcher for Nodoka. \n                    Copyright(c) 2008,2009 applet All Rights Reserved.\n\n  Exit Nodoka, Run Your Program, Run Nodoka.\n\n ex.   nshell.exe notepad.exe test.txt[Enter]", L"nshell.exe", MB_OK | MB_ICONINFORMATION);
		}

	if(useConsole){
		FreeConsole();
	}
	return TRUE;
	}


// のどかを終了させる。居なかった場合には、FALSEを返す。
BOOL ExitNodoka(LPWSTR FilePath, LPWSTR FileName)
	{
	HANDLE	hProcess = NULL;					// nodokaプロセスのハンドル
	HMODULE hModule	= NULL;						// nodokaモジュールのハンドル
	DWORD   dwSize2 = 256 * sizeof(DWORD);

	HWND	hwnd;										// nodokaのウィンドウハンドル
	BOOL	bMayu = FALSE;								// nodokaではなくmayuが居た?
	BOOL	bNodoka = FALSE;							// nodokaを終了させた?

	TCHAR	 *szFilePath = (TCHAR *)FilePath;
	TCHAR	 *szFileName = (TCHAR *)FileName;

	// nodokaを探す
	hwnd = FindWindow(_T("nodokaTasktray"), NULL);

	// nodokaが居なければ、mayuを探す。
	if(hwnd == NULL)
		{
		hwnd = FindWindow(_T("mayuTasktray"), NULL);
		bMayu = TRUE;
		}

	// nodokaの実行ファイルパスを取得しておく
	if (hwnd != NULL)								// nodokaが居たら、正常終了させる。
		{
			CRegKey Key;
			ULONG size;
			TCHAR tempFilePath[_MAX_FNAME];
			TCHAR tempFileName[_MAX_FNAME];

			if(Key.Open(HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\appletkan\\nodoka") != ERROR_SUCCESS)
				if(Key.Open(HKEY_LOCAL_MACHINE, L"Software\\appletkan\\nodoka") != ERROR_SUCCESS)
					if(Key.Open(HKEY_LOCAL_MACHINE, L"Software\\GANAware\\mayu") != ERROR_SUCCESS){
						// レジストリが存在しない。あるいは権限不足でレジストリアクセスがリダイレクトされ存在しないとき強制的に設定する。
						if(bMayu){
							wsprintfW(tempFilePath, L"c:\\Program Files\\mayu");
							wsprintfW(tempFileName, L"mayu.exe");
						} else {
#ifdef _WIN64
							wsprintfW(tempFilePath, L"c:\\Program Files\\nodoka");
							wsprintfW(tempFileName, L"nodoka64.exe");
#else
							wsprintfW(tempFilePath, L"c:\\Program Files (x86)\\nodoka");
							wsprintfW(tempFileName, L"nodoka.exe");
#endif
						}
						goto exit_nodoka;
					}

			size = sizeof(tempFilePath);
			Key.QueryStringValue( L"dir", tempFilePath, &size );

			if(!bMayu){
				size = sizeof(tempFileName);
				Key.QueryStringValue( L"FileName", tempFileName, &size );
			} else {
				wsprintfW(tempFileName, L"mayu.exe");
			}
exit_nodoka:

			wsprintfW(szFilePath, L"%s", tempFilePath);
			wsprintfW(szFileName, L"%s\\%s", tempFilePath, tempFileName);

			// nodokaの終了
			// 
			// mayu に、WM_CLOSEを送ると、DefProcの処理によりWM_DESTROYを受け取り、処理しようとするがPostQuitMessage(0);の行が無いため正常終了しない。
			// その代わりにmayuで定義されているID_MENUITEM_quitを送る。
			if(bMayu != TRUE)
				SendMessage(hwnd, WM_CLOSE, 0, 0);
			else
				{
				SendMessage(hwnd, WM_COMMAND, MAKELONG(ID_MENUITEM_quit, 0), 0);
				}
			Sleep(3000);										// 3秒待つ。
			bNodoka = TRUE;

		}
	return bNodoka;
	}


// 指定されたプログラムを起動する。待つ/待たない
BOOL RunProgram(LPWSTR lpCmdLine, BOOL bWait)
	{
	BOOL bOK = TRUE;
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	DWORD     dwErr;

	ZeroMemory(&si, sizeof(STARTUPINFO));
	si.cb						= sizeof( STARTUPINFO );
	si.dwFlags			= STARTF_USESHOWWINDOW;
	si.wShowWindow	= SW_SHOWNORMAL;

	if(CreateProcessW(NULL, lpCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
		{
		if(bWait)
			WaitForSingleObject(pi.hProcess, INFINITE);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		}
	else
		{
		dwErr = GetLastError();
		bOK = FALSE;
		ShowErrorMessage(dwErr, lpCmdLine);
		}
	return bOK;
	}

// 指定されたウィンドウタイトルを持つプログラムの終了を待つ。ただし、1分経っても、該当プログラムを検出できない場合は
// FALSEを返す。
BOOL Waitting(LPWSTR szWindowTitle)
	{
	BOOL bOK = FALSE;
	BOOL bHere = FALSE;
	time_t		StartTime, CurrentTime;
	TCHAR			szErrorMessage[1000]; 

	szErrorMessage[0] = _T('\0');

	//debug
	//MessageBoxW(NULL, szWindowTitle, L"debug Waitting", MB_OK);

	// 現在時刻取得
	time(&StartTime);

	// 指定されたプログラムを探す。
	//debug
	//MessageBoxW(NULL, L"1. 存在検出開始", L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
	while(1)
		{
		if(IsHere(szWindowTitle))
			{
			bHere = TRUE;
			//debug
			//MessageBoxW(NULL, L"1-1. 検出しました。", L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
			break;
			}
		Sleep(5000);																			// 5秒ごと

		time(&CurrentTime);
		if(difftime(CurrentTime, StartTime) > 60)				// 1分以内に検出できたか
			{
			wsprintfW(szErrorMessage, TEXT("1分待ちましたが、指定された %s というタイトルのプログラムを確認できませんでした。\n\nプログラムを終了します。"), szWindowTitle); 
			MessageBoxW(NULL, szErrorMessage, L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
			break;
			}
		}
	if(!bHere)
		return bOK;

	//MessageBoxW(NULL, L"detect", L"debug", MB_OK);

	// 終了を監視する。
	//debug
	//MessageBoxW(NULL, L"2. 終了監視開始", L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
	while(1)
		{
		if(!IsHere(szWindowTitle))
			{
			bOK = TRUE;
			//debug
			//MessageBoxW(NULL, L"2-1. 検出しました。", L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
			break;
			}
		Sleep(5000);															// 5秒ごと
		}

	//MessageBoxW(NULL, L"exit", L"debug", MB_OK);
	// 居なくなったら終了
	return bOK;
	}

// 指定されたウィンドウタイトルを持つプログラムの存在をBOOLで返す	
BOOL IsHere(LPWSTR szWindowTitle)
	{
	DWORD dwErr;
	gbDetect = FALSE;
	//debug
	//MessageBoxW(NULL, szWindowTitle, L"debug", MB_OK);
	//printf("start detect %s\n", szWindowTitle);
	if(0 == EnumWindows( EnumWndProc, (LPARAM)szWindowTitle ))
		{
		dwErr = GetLastError();
		if(dwErr !=0 || dwErr !=18)		// 0:検出したので終了, 18(0x12) 全て列挙したけど見つからなかった。
			{
				//debug
				//WriteConsole(hConsole, _T("not detect\n"), lstrlen(_T("not detect\n")), &dwConsole, NULL);
				//ShowErrorMessage(dwErr, L"Error EnumWindows");
			}
		}
	return gbDetect;
	}

// EnumWndProc
BOOL CALLBACK  EnumWndProc( HWND hWnd, LPARAM lparam )
	{
	TCHAR  szWindowName[1024];
	TCHAR	 *szFindWindowTitle = (TCHAR *)lparam;

	GetWindowTextW( hWnd, szWindowName, sizeof(szWindowName) );
	//debug
	//MessageBoxW(NULL, szFindWindowTitle, szWindowName, MB_OK);
	//WriteConsole(hConsole, szFindWindowTitle, lstrlen(szFindWindowTitle) , &dwConsole, NULL);
	//WriteConsole(hConsole,szWindowName, lstrlen(szWindowName) , &dwConsole, NULL);


	if(lstrcmpW(szWindowName, szFindWindowTitle) == 0)
		{
		gbDetect = TRUE;
		//debug
		//WriteConsole(hConsole, _T("detect\n"), lstrlen(_T("detect\n")), &dwConsole, NULL);
		return FALSE;				//EnumWindowsを終了させる。
		}
	gbDetect = FALSE;
	return TRUE;	//EnumWindowsを続けさせる。
	}

// Show ErrorMessage
void ShowErrorMessage(DWORD dwErr, LPWSTR lpCmdLine)
	{
	TCHAR			szErrorTemp[1000], szErrorMessage[1000]; 
	szErrorTemp[0] = _T('\0');
	szErrorMessage[0] = _T('\0');

	FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
		NULL, 
		dwErr, 
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
		(LPWSTR)szErrorTemp, 
		sizeof(szErrorTemp), 
		NULL); 

	wsprintfW(szErrorMessage, TEXT("%s \n%s (0x%X)"), lpCmdLine, szErrorTemp, dwErr ); 
	MessageBoxW(NULL, szErrorMessage, L"nshell.exe", MB_OK | MB_ICONEXCLAMATION);
	}

//[EOF]
