/*
 *(C) 2006 Roku LLC
 *
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License Version 2 as published 
 * by the Free Software Foundation.
 *
 * 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.
 *
 * Please read README.txt in the same directory as this source file for 
 * further license information.
 */

#ifndef SERVICECONTROL_H
#define SERVICECONTROL_H

class Service
{
	SC_HANDLE m_sc_manager;
	SC_HANDLE m_sc_service;
	bool m_can_control;

public:
	
	class Status : public SERVICE_STATUS
	{
		bool WaitPending();

	public:
		Status()
		{
			SERVICE_STATUS *clearable_this = this;
			ZeroMemory(clearable_this, sizeof(SERVICE_STATUS));
		}

		bool operator==(const Status &r) const
		{
			const SERVICE_STATUS *lhs = this;
			const SERVICE_STATUS *rhs = &r;
			return memcmp(lhs, rhs, sizeof(SERVICE_STATUS)) == 0;
		}

		bool operator!=(const Status &r) const
		{
			const SERVICE_STATUS *lhs = this;
			const SERVICE_STATUS *rhs = &r;
			return memcmp(lhs, rhs, sizeof(SERVICE_STATUS)) != 0;
		}

		void AssertValid() const
		{
			// If we've been written then this shouldn't be zero.
			ATLASSERT(dwCurrentState != 0);
		}

		bool IsValid() const
		{
			return dwCurrentState != 0;
		}

		bool IsRunning() const
		{
			AssertValid();
			return dwCurrentState == SERVICE_RUNNING;
		}

		bool IsStopped() const
		{
			AssertValid();
			return dwCurrentState == SERVICE_STOPPED;
		}

		bool IsPaused() const
		{
			AssertValid();
			return dwCurrentState == SERVICE_PAUSED;
		}

		bool IsPending() const
		{
			AssertValid();
			switch (dwCurrentState)
			{
			case SERVICE_CONTINUE_PENDING:
			case SERVICE_PAUSE_PENDING:
			case SERVICE_START_PENDING:
			case SERVICE_STOP_PENDING:
				return true;
			default:
				return false;
			}
		}
	};

	Service() : m_sc_manager(NULL), m_sc_service(NULL), m_can_control(false) {}
	~Service() { Close(); }

	bool IsOpen() const
	{
		return m_sc_service != NULL;
	}
	bool Open(const TCHAR *name);
	void Close();

	bool GetStatus(Status *status) const;

	bool Start();
	bool StartAndWait();
	bool Stop();
	bool StopAndWait();
	bool WaitPending(Status *status, DWORD existing_state);
	bool PollPending(Status *status, DWORD existing_state);

	bool CanControl() const
	{
		// For the time being - need to deal with running as a user that can't control.
		return IsOpen() && m_can_control;
	}

	CString GetBinaryPath() const;

	/// Pass SERVICE_AUTO_START, SERVICE_BOOT_START, SERVICE_DEMAND_START,
	/// SERVICE_DISABLED or SERVICE_SYSTEM_START.
	bool ConfigureStartup(DWORD dwStartup);

	DWORD GetStartup() const;
};

class ServiceStatusObserver
{
public:
	virtual void OnServiceStatus(Service::Status old_status, Service::Status new_status) = 0;
};

class ServiceStatusMonitor
{
	Service::Status m_service_status;
	std::vector<ServiceStatusObserver *> m_service_observers;

	void FireServiceStatus(Service::Status old_status, Service::Status new_status)
	{
		std::vector<ServiceStatusObserver *>::iterator i = m_service_observers.begin();
		while (i != m_service_observers.end())
		{
			(*i)->OnServiceStatus(old_status, new_status);
			++i;
		}
	}

public:
	void Poll(Service *service);
	void Subscribe(ServiceStatusObserver *obs)
	{
		m_service_observers.push_back(obs);
	}

	void Unsubscribe(ServiceStatusObserver *obs)
	{
		std::vector<ServiceStatusObserver *>::iterator i = std::find(m_service_observers.begin(), m_service_observers.end(), obs);
		ATLASSERT(i != m_service_observers.end());
		if (i != m_service_observers.end())
		{
			m_service_observers.erase(i);
		}
	}

};
#endif // SERVICECONTROL_H