/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * 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 Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */


#include "Service.h"
#include "platform.h"
#include "daemon/DaemonEngine.h"

#include "DaemonDM/IPCDaemonEngine.h"

#include "DataStorage/IDataStorage.h"
#include "Errors.h"

#include <string>
#include <iostream>
#include <Windows.h>
#include <string>
#include "../branding.h"

#include <boost/program_options.hpp>
namespace po = boost::program_options;

namespace NS_DM_Client
{
	const std::string S_serviceName("OMA_DM_Client");

	void PrintUsage(FILE* stream, const char* progName)
	{
		fprintf(stream, "Usage: %s options \n", progName);
		fprintf(stream,
			"   -h  --help              Display this usage information\n"
			"   -i  --install           Install service\n"
			"   -u  --uninstall         Uninstall service\n"
			"   -s  --start				Start service\n"
			"   -p  --stop				Stop service\n"
			"   -a  --alive             Check if service is alive\n"
			"   -r  --restart           Restart service\n"
			"   -v  --version           Display service version\n"
			"   -c  --compiler          Display compiler version\n"
			"   -t  --time              Display compilation time\n"
			);
	}
	//------------------------------------------------------------------------------------------------------

	Service Service::m_handle;

	//-----------------------------------------------------------------------------
	Service::Service(): m_name(S_serviceName), m_ipcEngine(0), m_engine(0)
	{
	}
	//-----------------------------------------------------------------------------

	void WINAPI Service::ServiceDispatch( DWORD numArgs, char **args )
	{
		Service *service = &m_handle;
		service->m_name = args[0];

		// we have to initialize the service-specific stuff
		memset( &service->m_status, 0, sizeof(SERVICE_STATUS) );
		service->m_status.dwServiceType = SERVICE_WIN32;
		service->m_status.dwCurrentState = SERVICE_START_PENDING;
		service->m_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;

		service->m_statusHandle = ::RegisterServiceCtrlHandlerA( service->m_name.c_str(), &ServiceCtrlHandler );
		if( service->m_statusHandle == (SERVICE_STATUS_HANDLE)0 )
		{
			fprintf(stdout, "Failed to register service control handler");
			return;
		}

		// copy the arguments to an ArmsString array
		std::string *newArgs = new std::string[numArgs];
		for( DWORD i = 0; i < numArgs; i++ )
			newArgs[i] = args[i];

		if( !service->Run())
		{
			service->m_status.dwCurrentState = SERVICE_STOPPED;
			service->m_status.dwCheckPoint = 0;
			service->m_status.dwWaitHint = 0;
		}
		else
		{
			service->m_status.dwCurrentState = SERVICE_RUNNING;
			service->m_status.dwCheckPoint = 0;
			service->m_status.dwWaitHint = 0;
		}

		::SetServiceStatus( service->m_statusHandle, &service->m_status );
	}

	//-----------------------------------------------------------------------------
	void WINAPI Service::ServiceCtrlHandler( DWORD control )
	{
		Service *service = &m_handle;

		switch( control )
		{
		case SERVICE_CONTROL_SHUTDOWN:
		case SERVICE_CONTROL_STOP:
			service->Shutdown();

			service->m_status.dwCurrentState = SERVICE_STOPPED;
			service->m_status.dwWin32ExitCode = 0;
			service->m_status.dwCheckPoint = 0;
			service->m_status.dwWaitHint = 0;
			break;
		case SERVICE_CONTROL_INTERROGATE:
			// just set the current state to whatever it is...
			break;
		}

		::SetServiceStatus( service->m_statusHandle, &service->m_status );
	}

	//-----------------------------------------------------------------------------

	bool Service::Execute(int numArgs, char** args)
	{
		po::options_description desc("Allowed options");
		desc.add_options()
			("help,h", "help")
			("install,i", "install service")
			("uninstall,u", "uninstall service")
			("start,s", "start service")
			("stop,p", "stop service")
			("restart,r", "restart service")
			("alive,a", "is service running")
			("version,v", "version")
			("compiler,c", "compiler")
			("time,t", "time")
			("execute,e", "Run as executable")
			;

		po::variables_map vm;        
		try
		{
			po::store(po::parse_command_line(numArgs, args, desc), vm);
			po::notify(vm);
		}
		catch (po::error&)
		{
			//PrintUsage(stdout, "OMA-DMClient");
			// no description for such parameter, skip it
		}

		if (vm.count("install")) 
		{
			Install();
			return 0;
		}

		if (vm.count("uninstall")) 
		{
			Uninstall();
			return 0;
		}

		if (vm.count("start")) 
		{
			Start();
			return 0;
		}

		if (vm.count("stop")) 
		{
			Stop();
			return 0;
		}

		if (vm.count("restart")) 
		{
			Restart();
			return 0;
		}

		if (vm.count("alive")) 
		{
			bool res = IsRunning();
			return 0;
		}

		if (vm.count("help")) 
		{
			PrintUsage(stdout, args[0]);
			return 0;
		}
		if (vm.count("version"))
		{
			fprintf(stdout, "Service version is: %s\n", cApplicationVersion);
			return 0;
		}
		if (vm.count("compiler"))
		{
			fprintf(stdout, "Compiler version is: %d\n", _MSC_VER);
			return 0;
		}					
		if (vm.count("time"))
		{
			fprintf(stdout, "Compilation date and time is: %s %s\n", __DATE__, __TIME__);
			return 0;
		}

		SERVICE_TABLE_ENTRYA dispatchTable[] = 
		{
			{ const_cast<char*>(m_name.c_str()), &ServiceDispatch },
			{ NULL, NULL }
		};

		if( ::StartServiceCtrlDispatcherA( dispatchTable ) == 0 )
		{
			// if this fails, it's probably because someone started us from
			// the command line.  Print a message telling them the "usage"

			PrintUsage(stdout, "OMA-DMClient");
		}
		return 0;
	}
	//-------------------------------------------------------------------------------------------

	bool Service::Run()
	{
		// specific daemon initialization
		m_engine = NS_DM_Client::IDaemonEngine::GetInstance();
		if (m_engine)
		{
			if (m_engine->Start())
			{
				// start interprocess communication engine
				m_ipcEngine = NS_DM_Client::CreateDaemonIPCEngine(m_engine);
				if (!m_ipcEngine)
				{
					fprintf(stdout, "Failed to get IPC Engine instance\n");
				}
				else
				{
					m_ipcEngine->Start();
				}
			}
			else
			{
				fprintf(stdout, "Can't start daemon engine\n");
			}
		}
		else
		{
			fprintf(stdout, "Can't get daemon engine instance\n");
		}
		return true;
	}

	//-----------------------------------------------------------------------------

	void Service::Shutdown()
	{
 		if (m_ipcEngine && (!m_ipcEngine->Stop()))
 		{
			fprintf(stdout, "ERROR: Failed to stop ipc engine\n");
 		}
	 	if (!m_engine->Stop())
	 	{
			fprintf(stdout, "ERROR: Failed to stop daemon engine\n");
	 	}
		//TODO: uncomment this line when IgorK fixes the problem
	 	delete m_engine;
	}
	//-------------------------------------------------------------------------------------------

	void Service::Install()
	{
		// get a handle to the service control manager
		SC_HANDLE handle = ::OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
		if (handle == NULL)
		{
			fprintf(stdout, "Could not open SCM");
			return;
		}

		const int maxSize = 256;
		char buffer[maxSize];
		char filePath[maxSize];

		// find out the filename of ourselves so we can install it to the
		// service control manager.
		::GetModuleFileNameA(NULL, buffer, maxSize);
		sprintf(filePath, "\"%s\"", buffer);
		SC_HANDLE service = ::CreateServiceA(handle, m_name.c_str(), m_name.c_str(), GENERIC_READ | GENERIC_EXECUTE,
			SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, filePath, NULL, NULL,
			NULL, NULL, NULL);

		if (service == NULL)
		{
			fprintf(stdout, "Failed to create new service. Error = %d\n", ::GetLastError());
			::CloseServiceHandle(handle);
			return;
		}
		else
		{
			fprintf(stdout, "Service installed. Executable path = \"%s\"\n", filePath);
		}

		::CloseServiceHandle(service);
		::CloseServiceHandle(handle);
	}
	//-----------------------------------------------------------------------------
	void Service::Uninstall()
	{
		// get a handle to the SCM
		SC_HANDLE handle = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
		if( handle == NULL )
		{
			fprintf(stdout, "Failed to open SCM\n");
			return;
		}

		// open a handle to the service
		SC_HANDLE service = ::OpenServiceA( handle, m_name.c_str(), DELETE );
		if( service == NULL )
		{
			::CloseServiceHandle( handle );
			return;
		}

		// remove the service!
		if( !::DeleteService( service ) )
		{
			fprintf(stdout, "DeleteService failed\n");
		}
		else
		{
			fprintf(stdout, "Service uninstalled\n");
		}

		::CloseServiceHandle( service );
		::CloseServiceHandle( handle );
	}

	//-----------------------------------------------------------------------------

	bool Service::IsRunning()
	{
		SC_HANDLE service = NULL;
		bool res = openService(service);
		if (res && service)
		{
			SERVICE_STATUS_PROCESS status;
			DWORD bytesNeeded;
			if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO,  (LPBYTE)&status, sizeof(SERVICE_STATUS_PROCESS), 
				&bytesNeeded))
			{
				fprintf(stdout, "Failed to query service status, error = %d\n", ::GetLastError());
				return false; 
			}

			if (status.dwCurrentState == SERVICE_RUNNING) 
			{
				fprintf(stdout, "Service is running\n");
				res = true;
			}
			else 
			{ 
				fprintf(stdout, "Service isn't running\n");
				res = false;
			} 
			CloseServiceHandle(service); 
			return res;
		}
		return false;
	}
	//-------------------------------------------------------------------------------------------

	void Service::Restart()
	{
		SC_HANDLE service = NULL;
		bool res = openService(service);
		if (res && service)
		{
			// send the STOP control request to the service
			SERVICE_STATUS status;
			::ControlService(service, SERVICE_CONTROL_STOP, &status);
			if(status.dwCurrentState != SERVICE_STOPPED)
			{
				fprintf(stdout, "ControlService failed\n");
			}
			else
			{
				fprintf(stdout, "Service stopped!\n");
			}

			if(!::StartService(service, 0, NULL))
			{
				fprintf(stdout, "StartService failed\n");
			}
			else
			{
				fprintf(stdout, "Service started!\n");
			}

			::CloseServiceHandle(service);
		}
	}
	//-------------------------------------------------------------------------------------------

	bool Service::openService(SC_HANDLE& service)
	{
		// open a handle to the SCM
		SC_HANDLE handle = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if(handle == NULL)
		{
			fprintf(stdout, "Could not connect to SCM database\n");
			return false;
		}

		// open a handle to the service
		service = ::OpenServiceA(handle, m_name.c_str(), SERVICE_ALL_ACCESS);
		if(service == NULL)
		{
			fprintf(stdout, "Could not get handle to service\n");
			::CloseServiceHandle(handle);
			return false;
		}
		::CloseServiceHandle(handle);
		return true;
	}
	//-------------------------------------------------------------------------------------------

	void Service::Start()
	{
		SC_HANDLE service = NULL;
		bool res = openService(service);
		if (res && service)
		{
			if(!::StartService(service, 0, NULL))
			{
				fprintf(stdout, "StartService failed\n");
			}
			else
			{
				fprintf(stdout, "Service started!\n");
				fprintf(stdout, "Service version is: %s\n", cApplicationVersion);
			}
			::CloseServiceHandle(service);
		}
	}
	//-----------------------------------------------------------------------------

	void Service::Stop()
	{
		SC_HANDLE service = NULL;
		bool res = openService(service);
		if (res && service)
		{
			// send the STOP control request to the service
			SERVICE_STATUS status;
			::ControlService(service, SERVICE_CONTROL_STOP, &status);
			if(status.dwCurrentState != SERVICE_STOPPED)
			{
				fprintf(stdout, "ControlService failed\n");
			}
			else
			{
				fprintf(stdout, "Service stopped!\n");
			}

			::CloseServiceHandle(service);
		}
	}
}