/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2002 Chris Schoeneman, Nick Bolton, Sorin Sbarnea
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file COPYING that should have accompanied this file.
 * 
 * This package 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, see <http://www.gnu.org/licenses/>.
 */

#include "CArchAppUtilWindows.h"
#include "Version.h"
#include "CLog.h"
#include "XArchWindows.h"
#include "CArchMiscWindows.h"
#include "CApp.h"
#include "LogOutputters.h"
#include "CMSWindowsScreen.h"
#include "XSynergy.h"
#include "IArchTaskBarReceiver.h"
#include "CMSWindowsRelauncher.h"
#include "CScreen.h"

#include <sstream>
#include <iostream>
#include <conio.h>

CArchAppUtilWindows::CArchAppUtilWindows() :
m_exitMode(kExitModeNormal)
{
	if (SetConsoleCtrlHandler((PHANDLER_ROUTINE)consoleHandler, TRUE) == FALSE)
    {
		throw XArchEvalWindows();
    }
}

CArchAppUtilWindows::~CArchAppUtilWindows()
{
}

BOOL WINAPI CArchAppUtilWindows::consoleHandler(DWORD CEvent)
{
	if (instance().app().m_taskBarReceiver)
	{
		// HACK: it would be nice to delete the s_taskBarReceiver object, but 
		// this is best done by the CApp destructor; however i don't feel like
		// opening up that can of worms today... i need sleep.
		instance().app().m_taskBarReceiver->cleanup();
	}

	ExitProcess(kExitTerminated);
    return TRUE;
}

bool 
CArchAppUtilWindows::parseArg(const int& argc, const char* const* argv, int& i)
{
	if (app().isArg(i, argc, argv, NULL, "--service")) {

		const char* action = argv[++i];

		if (_stricmp(action, "install") == 0) {
			installService();
		}
		else if (_stricmp(action, "uninstall") == 0) {
			uninstallService();
		}
		else if (_stricmp(action, "start") == 0) {
			startService();
		}
		else if (_stricmp(action, "stop") == 0) {
			stopService();
		}
		else {
			LOG((CLOG_ERR "unknown service action: %s", action));
			app().m_bye(kExitArgs);
		}
		app().m_bye(kExitSuccess);
	}
	else if (app().isArg(i, argc, argv, NULL, "--debug-service-wait")) {

		app().argsBase().m_debugServiceWait = true;
	}
	else if (app().isArg(i, argc, argv, NULL, "--relaunch")) {

		app().argsBase().m_relaunchMode = true;
	}
	else if (app().isArg(i, argc, argv, NULL, "--exit-pause")) {

		app().argsBase().m_pauseOnExit = true;
	}
	else if (app().isArg(i, argc, argv, NULL, "--no-tray")) {

		app().argsBase().m_disableTray = true;
	}
	else {
		// option not supported here
		return false;
	}

	return true;
}

CString
CArchAppUtilWindows::getServiceArgs() const
{
	std::stringstream argBuf;
	for (int i = 1; i < __argc; i++) {
		const char* arg = __argv[i];

		// ignore service setup args
		if (_stricmp(arg, "--service") == 0) {
			// ignore and skip the next arg also (service action)
			i++;
		}
		else {
			if (strchr(arg, ' ') != NULL) {
				// surround argument with quotes if it contains a space
				argBuf << " \"" << arg << "\"";
			} else {
				argBuf << " " << arg;
			}
		}
	}
	return argBuf.str();
}

void
CArchAppUtilWindows::installService()
{
	CString args = getServiceArgs();

	// get the path of this program
	char thisPath[MAX_PATH];
	GetModuleFileName(CArchMiscWindows::instanceWin32(), thisPath, MAX_PATH);

	ARCH->installDaemon(
		app().daemonName(), app().daemonInfo(), 
		thisPath, args.c_str(), NULL, true);

	LOG((CLOG_INFO "service '%s' installed with args: %s",
		app().daemonName(), args != "" ? args.c_str() : "none" ));
}

void
CArchAppUtilWindows::uninstallService()
{
	ARCH->uninstallDaemon(app().daemonName(), true);
	LOG((CLOG_INFO "service '%s' uninstalled", app().daemonName()));
}

void
CArchAppUtilWindows::startService()
{
	// open service manager
	SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ);
	if (mgr == NULL) {
		throw XArchDaemonFailed(new XArchEvalWindows());
	}

	// open the service
	SC_HANDLE service = OpenService(
		mgr, app().daemonName(), SERVICE_START);

	if (service == NULL) {
		CloseServiceHandle(mgr);
		throw XArchDaemonFailed(new XArchEvalWindows());
	}

	// start the service
	if (StartService(service, 0, NULL)) {
		LOG((CLOG_INFO "service '%s' started", app().daemonName()));
	}
	else {
		throw XArchDaemonFailed(new XArchEvalWindows());
	}
}

void
CArchAppUtilWindows::stopService()
{
	// open service manager
	SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ);
	if (mgr == NULL) {
		throw XArchDaemonFailed(new XArchEvalWindows());
	}

	// open the service
	SC_HANDLE service = OpenService(
		mgr, app().daemonName(),
		SERVICE_STOP | SERVICE_QUERY_STATUS);

	if (service == NULL) {
		CloseServiceHandle(mgr);
		throw XArchDaemonFailed(new XArchEvalWindows());
	}

	// ask the service to stop, asynchronously
	SERVICE_STATUS ss;
	if (!ControlService(service, SERVICE_CONTROL_STOP, &ss)) {
		DWORD dwErrCode = GetLastError(); 
		if (dwErrCode != ERROR_SERVICE_NOT_ACTIVE) {
			LOG((CLOG_ERR "cannot stop service '%s'", app().daemonName()));
			throw XArchDaemonFailed(new XArchEvalWindows());
		}
	}

	LOG((CLOG_INFO "service '%s' stopping asynchronously", app().daemonName()));
}

static
int 
mainLoopStatic() 
{
	return CArchAppUtil::instance().app().mainLoop();
}

int 
CArchAppUtilWindows::daemonNTMainLoop(int argc, const char** argv)
{
	app().initApp(argc, argv);
	debugServiceWait();

	// NB: what the hell does this do?!
	app().argsBase().m_backend = false;
	
	return CArchMiscWindows::runDaemon(mainLoopStatic);
}

void 
CArchAppUtilWindows::exitApp(int code)
{
	switch (m_exitMode) {

		case kExitModeDaemon:
			CArchMiscWindows::daemonFailed(code);
			break;

		default:
			throw XExitApp(code);
	}
}

int daemonNTMainLoopStatic(int argc, const char** argv)
{
	return CArchAppUtilWindows::instance().daemonNTMainLoop(argc, argv);
}

int 
CArchAppUtilWindows::daemonNTStartup(int, char**)
{
	CSystemLogger sysLogger(app().daemonName(), false);
	m_exitMode = kExitModeDaemon;
	return ARCH->daemonize(app().daemonName(), daemonNTMainLoopStatic);
}

static
int
daemonNTStartupStatic(int argc, char** argv)
{
	return CArchAppUtilWindows::instance().daemonNTStartup(argc, argv);
}

static
int
foregroundStartupStatic(int argc, char** argv)
{
	return CArchAppUtil::instance().app().foregroundStartup(argc, argv);
}

void
CArchAppUtilWindows::beforeAppExit()
{
	// this can be handy for debugging, since the application is launched in
	// a new console window, and will normally close on exit (making it so
	// that we can't see error messages).
	if (app().argsBase().m_pauseOnExit) {
		std::cout << std::endl << "Press any key to exit..." << std::endl;
		int c = _getch();
	}
}

int
CArchAppUtilWindows::run(int argc, char** argv)
{
	// record window instance for tray icon, etc
	CArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL));

	CMSWindowsScreen::init(CArchMiscWindows::instanceWin32());
	CThread::getCurrentThread().setPriority(-14);

	StartupFunc startup;
	if (CArchMiscWindows::wasLaunchedAsService()) {
		startup = &daemonNTStartupStatic;
	} else {
		startup = &foregroundStartupStatic;
		app().argsBase().m_daemon = false;
	}

	return app().runInner(argc, argv, NULL, startup);
}

CArchAppUtilWindows& 
CArchAppUtilWindows::instance()
{
	return (CArchAppUtilWindows&)CArchAppUtil::instance();
}

void 
CArchAppUtilWindows::debugServiceWait()
{
	if (app().argsBase().m_debugServiceWait)
	{
		while(true)
		{
			// this code is only executed when the process is launched via the
			// windows service controller (and --debug-service-wait arg is 
			// used). to debug, set a breakpoint on this line so that 
			// execution is delayed until the debugger is attached.
			ARCH->sleep(1);
			LOG((CLOG_INFO "waiting for debugger to attach"));
		}
	}
}

void 
CArchAppUtilWindows::startNode()
{
	if (app().argsBase().m_relaunchMode) {

		LOG((CLOG_DEBUG1 "entering relaunch mode"));
		CMSWindowsRelauncher relauncher;
		relauncher.startAsync();

		// HACK: create a dummy screen, which can handle system events 
		// (such as a stop request from the service controller).
		CScreen* dummyScreen = app().createScreen();
	}
	else {
		app().startNode();
	}
}
