/**********************************************************************
 
	Copyright (C) 2003 Tomohito Nakajima <nakajima@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/


#include <windows.h>
#include <winsvc.h>
#include <TCHAR.H>


void WinError(){
	LPVOID lpMsgBuf;
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR) &lpMsgBuf,
		0,
		NULL
	);
	MessageBox(NULL, (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION);
	LocalFree(lpMsgBuf);
}

void WinErrorLog(){
	LPVOID lpMsgBuf;
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR) &lpMsgBuf,
		0,
		NULL
	);
	LocalFree(lpMsgBuf);
	int *debug=0;
	*debug=1;
}

void ExecAndWait(TCHAR* cmdLine, int cmdShow){
	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	ZeroMemory(&si,sizeof(si));
	ZeroMemory(&pi,sizeof(pi));
	si.cb=sizeof(si);
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = cmdShow;
	
	if(CreateProcess(NULL,cmdLine,NULL,NULL,FALSE,
		CREATE_NEW_CONSOLE|
		CREATE_NEW_PROCESS_GROUP|
		NORMAL_PRIORITY_CLASS,NULL,NULL,&si,&pi)){
		CloseHandle(pi.hThread);
		WaitForSingleObject(pi.hProcess,INFINITE);
		CloseHandle(pi.hProcess);
	}
	else{
		WinError();
	}
}

#ifndef ASSERT
#define ASSERT(exp) {if(!(exp)){int *i=0;*i=0;}}
#endif

class ServiceApp{
	static ServiceApp *this_;
	
public :
	ServiceApp(const TCHAR *service_name){
		init(service_name);
	}
	/*
	static ServiceApp *getInstance(const TCHAR *service_name){
		if(service_name==NULL)
			return this_;
		
		if(!this_){
			this_ = new ServiceApp(service_name);
		}
		else{
			if(_tcscmp(this_->service_name_, service_name) != 0){
				return NULL;
			}
		}
		return this_;
	}
	*/

	virtual bool doService(){

		SERVICE_TABLE_ENTRY srv[] = {
			{ const_cast<TCHAR*>(service_name_), serviceMainProc },
			{ NULL, NULL }
		};
		
		if(!StartServiceCtrlDispatcher(srv)){
			WinErrorLog();
			return false;
		}
		return true;
	}
	
protected:
	virtual void start()=0;
	virtual void stop()=0;
	
	HANDLE hServEvent;
	SERVICE_STATUS status;
	SERVICE_STATUS_HANDLE hstate;
	TCHAR service_name_[256];
	SC_HANDLE service_handle_;

	// initialize service app
	void init(const TCHAR *service_name){
		
		// only one ServiceApp instance available 
		ASSERT(this_==NULL);
		
		this_ = this;
		_tcsncpy(service_name_, service_name, sizeof(service_name_));
		
		this_->status.dwServiceType = SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS;
		this_->status.dwCurrentState = SERVICE_START_PENDING;
		this_->status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
		this_->status.dwWin32ExitCode = NO_ERROR;
		this_->status.dwServiceSpecificExitCode = NO_ERROR;
		this_->status.dwCheckPoint = 0;
		this_->status.dwWaitHint = 0; 
	}

	static VOID WINAPI serviceCtrlHandler(
				DWORD dwControl)
	{
		switch(dwControl){
			case SERVICE_CONTROL_STOP:
				this_->status.dwCurrentState = SERVICE_STOP_PENDING;
				SetServiceStatus (this_->hstate, &this_->status);
				SetEvent(this_->hServEvent);
				// return NO_ERROR;
				return;
			
			case SERVICE_CONTROL_SHUTDOWN:
				SetEvent(this_->hServEvent);
				// return NO_ERROR;
				return;
			
			default:
				// return ERROR_CALL_NOT_IMPLEMENTED;
				return;
		}
	}
	

	static VOID WINAPI serviceMainProc(DWORD argc, LPTSTR *argv)
	{
		this_->hServEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
		this_->hstate = RegisterServiceCtrlHandler(this_->service_name_, serviceCtrlHandler);
		if(this_->hstate==0){
			WinErrorLog();
		}
		
		this_->status.dwCurrentState = SERVICE_START_PENDING;
		this_->status.dwCheckPoint = 0;
		this_->status.dwWaitHint = 5000;

		SetServiceStatus(this_->hstate, &this_->status);
		
		/* start service */
		this_->start();
		
		this_->status.dwCurrentState = SERVICE_RUNNING;
		this_->status.dwCheckPoint = 0;
		this_->status.dwWaitHint = 0;
		
		SetServiceStatus (this_->hstate, &this_->status);
		
		WaitForSingleObject(this_->hServEvent, INFINITE);
		
		/* stop service */
		this_->stop();
		
		this_->status.dwCurrentState = SERVICE_STOPPED;
		SetServiceStatus(this_->hstate, &this_->status);

		CloseHandle(this_->hServEvent);
	}
};

ServiceApp *ServiceApp::this_=0;

class ServiceManager{
	SC_HANDLE handle_;
public :
	ServiceManager(DWORD desiredAccess = SC_MANAGER_ALL_ACCESS){
		handle_ = OpenSCManager(NULL, NULL, desiredAccess);
		if(!handle_)
			WinError();
	}

	~ServiceManager(){
		CloseServiceHandle(handle_);
	}
	
	// ref. CreateService
	bool createService(
		LPCTSTR service_name, 
		LPCTSTR path_to_exe, 
		DWORD service_type=SERVICE_INTERACTIVE_PROCESS|SERVICE_WIN32_OWN_PROCESS,
		DWORD start_type=SERVICE_AUTO_START,
		LPCTSTR account=NULL, 
		LPCTSTR password=NULL){
		
		if(handle_==NULL){
			WinError();
			return false;
		}
		
		TCHAR path[MAX_PATH+1];
		/*_tcscpy(path, path_to_exe);*/
		if(path_to_exe[0] != '\"'){
			path[0]='\0';
			_tcscat(path,_T("\""));
			_tcscat(path,path_to_exe);
			_tcscat(path,_T("\""));
		}
		
		SC_HANDLE hServ = OpenService(handle_, service_name, SERVICE_QUERY_STATUS);

		if(hServ){
			CloseServiceHandle(hServ);
			hServ = OpenService(handle_, service_name, DELETE|SERVICE_STOP);
			if(hServ == NULL){
				WinError();
				return false;
			}
			else{
				CloseServiceHandle(hServ);
				TCHAR buf[1024];
				buf[0]='\0';
				_tcscat(buf, service_name);
				_tcscat(buf, _T(" already exists. do you wish to delete this service before install new one ?"));
				
				int btn = MessageBox(NULL, buf, _T("confirm"), MB_YESNO);
				if(btn == IDYES){
					deleteService(service_name);
				}
				else{
					return false;
				}
			}
		}
		
		hServ = CreateService(handle_,
							service_name,
							service_name,
							SERVICE_ALL_ACCESS,
							service_type,
							start_type,
							SERVICE_ERROR_NORMAL,
							path,
							NULL,
							NULL,
							NULL,
							account,
							password);
		
		if(hServ==NULL){
			WinError();
			return false;
		}
		else{
			CloseServiceHandle(hServ);
			return true;
		}
	}

	bool startService(LPCTSTR service_name, DWORD dwNumServiceArgs=0, LPCTSTR *lpServiceArgVectors=NULL){
		SC_HANDLE hServ = OpenService(handle_, service_name, SERVICE_START);
		if(hServ){
			bool ret = StartService(hServ, dwNumServiceArgs, lpServiceArgVectors)!=0;
			CloseServiceHandle(hServ);
			return ret;
		}
		return false;
	}
	
	bool stopService(LPCTSTR service_name){
		SC_HANDLE hServ = OpenService(handle_, service_name, SERVICE_STOP);
		if(hServ){
			SERVICE_STATUS st;
			ControlService(hServ, SERVICE_CONTROL_STOP, &st);
			CloseServiceHandle(hServ);
			return true;
		}
		else{
			return false;
		}
	}
	
	bool deleteService(LPCTSTR service_name){
		if(handle_==NULL){
			WinError();
			return false;
		}
		stopService(service_name);
		
		SC_HANDLE hServ = OpenService(handle_, service_name, DELETE);
		if(hServ==NULL){
			WinError();
			return false;
		}
		else{
			if(!DeleteService(hServ))
				WinError();
			CloseServiceHandle(hServ);
			return true;
		}
	}
};





static const TCHAR *GBS_SERVICE_NAME=_T("GLOBALBASE Landscape Server");
static const TCHAR *GBS_SERVER_ROOT=_T("XLSERVERROOT");

class GBServiceApp : public ServiceApp{
public:
	GBServiceApp() : ServiceApp(GBS_SERVICE_NAME){

	}
private:
	virtual void start(){
		if(!SetCurrentDirectory(_tgetenv(GBS_SERVER_ROOT))){
			WinError();
		}
		ExecAndWait(_T("xl gbserver.xl - - / start"), SW_SHOWDEFAULT);
	}

	virtual void stop(){
		if(!SetCurrentDirectory(_tgetenv(GBS_SERVER_ROOT))){
			WinError();
		}
		ExecAndWait(_T("xl gbserver.xl - - / stop"), SW_SHOWDEFAULT);
	}
};

bool set_system_env(LPCTSTR name, LPCTSTR value){
	HKEY key;
	if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
		_T("System\\CurrentControlSet\\Control\\Session Manager\\Environment"), 
		NULL, KEY_ALL_ACCESS, &key)==ERROR_SUCCESS){
 		RegSetValueEx(key, name, NULL, REG_EXPAND_SZ, (UCHAR*)value, (_tcslen(value)+1) * sizeof(TCHAR));
		RegCloseKey(key);
		return true;
	}
	else{
		MessageBox(NULL, _T("Can not open registry key for write environment."), NULL, MB_OK);
		return false;
	}
}

bool add_system_path(LPCTSTR path){
	HKEY key;
	if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
		_T("System\\CurrentControlSet\\Control\\Session Manager\\Environment"), 
		NULL, KEY_ALL_ACCESS, &key)==ERROR_SUCCESS){
		TCHAR path_buff[8192];
		DWORD size = sizeof(path_buff)-1;
		DWORD data_type=REG_EXPAND_SZ;
		if(RegQueryValueEx(key, _T("Path"), NULL, &data_type, (_TUCHAR*)path_buff, &size) == ERROR_SUCCESS){
			/* if path env exists, find path and if not found the path, append to Path env */ 
			path_buff[size] = '\0';
			LPCTSTR p = _tcsstr(path_buff, path);
			if(p == 0 || ((p[_tcslen(path)]!=';') && (p[_tcslen(path)]!='\0')) ){
				_tcscat(path_buff, _T(";"));
				_tcscat(path_buff, path);
				RegSetValueEx(key, _T("Path"), NULL, REG_EXPAND_SZ, (_TUCHAR*)path_buff, (_tcslen(path_buff)+1) * sizeof(TCHAR));
			}
		}
		else{
			RegSetValueEx(key, _T("Path"), NULL, REG_EXPAND_SZ, (UCHAR*)path, (_tcslen(path)+1) * sizeof(TCHAR));
		}
		
		RegCloseKey(key);
		return true;
	}
	else{
		MessageBox(NULL, _T("Can not open registry key for write environment."), NULL, MB_OK);
		return false;
	}
}

bool setup_env(LPCTSTR server_root){
	TCHAR buff[1024];
	buff[0]='\0';
	
	// add path
	GetShortPathName(server_root, buff, sizeof(buff));
	_tcscat(buff, _T("bin"));
	add_system_path(buff);
	
	buff[0]='\0';
	_tcscat(buff, server_root);
	_tcscat(buff, _T("xlscript\\sbin\\;"));
	_tcscat(buff, server_root);
	_tcscat(buff, _T("xlscript\\gbs\\;"));
	_tcscat(buff, server_root);
	_tcscat(buff, _T("xlscript\\std\\"));
	
	return set_system_env(_T("XLPATH"), buff) && set_system_env(GBS_SERVER_ROOT, server_root);
}

void ensure_path_name(TCHAR* path, TCHAR delim)
{
	int len;

	if(path==0)
		return;
	len = strlen(path);
	if(path[len-1] != delim){
		path[len] = delim;
		path[len+1]='\0';
	}
}


int __cdecl _tmain(int argc, _TCHAR **argv, _TCHAR **envp)
{
	ServiceManager manager;

	int result = -1;
	if(argc>=2){
		if(_tcscmp(argv[1],_T("regist"))==0){
			TCHAR app_path[MAX_PATH+1];
			
			GetModuleFileName(GetModuleHandle(NULL), app_path, MAX_PATH);
			
			/* regist service */
			if(manager.createService(GBS_SERVICE_NAME, app_path)){
				
				TCHAR path[MAX_PATH+1];
				if(argc==3){
					/* get server root path from startup arg */
					_tcsncpy(path, argv[2], sizeof(path)-(sizeof(TCHAR)*2));
				}
				else{
					/* if there is not second arg, make server root path from app_path */
					TCHAR dir[MAX_PATH+1];
					TCHAR drive[MAX_PATH+1];
					path[0]='\0';
					_splitpath(app_path, drive, dir, NULL, NULL);
					_tcscat(path, drive);
					_tcscat(path, dir);
					path[_tcslen(path)-1] = '\0';
					_splitpath(path, drive, dir, NULL, NULL);
					path[0]='\0';
					_tcscat(path, drive);
					_tcscat(path, dir);
				}
				ensure_path_name(path, '\\');
				
				if(setup_env(path))
					result = 0;
			}
		}
		else if(_tcscmp(argv[1],_T("unregist"))==0){
			if(manager.deleteService(GBS_SERVICE_NAME))
				result = 0;
		}
		else if(_tcscmp(argv[1],_T("start"))==0){
			if(manager.startService(GBS_SERVICE_NAME))
				result = 0;
		}
		else if(_tcscmp(argv[1],_T("stop"))==0){
			if(manager.stopService(GBS_SERVICE_NAME))
				result = 0;
		}
	}
	else{
		if(GBServiceApp().doService()) 
			result = 0;
	}
	
	return result;
}
