/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2003 Chris Schoeneman
 * 
 * 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.
 */

#include "CArchTaskBarOS2.h"
#include "IArchTaskBarReceiver.h"
#include "CArch.h"
#include "XArch.h"
#include <string.h>
/** @todo xwp */

static const ULONG		kAddReceiver     = WM_USER + 10;
static const ULONG		kRemoveReceiver  = WM_USER + 11;
static const ULONG		kUpdateReceiver  = WM_USER + 12;
static const ULONG		kNotifyReceiver  = WM_USER + 13;
static const ULONG		kFirstReceiverID = WM_USER + 14;

//
// CArchTaskBarWindows
//

CArchTaskBarOS2*	CArchTaskBarOS2::s_instance    = NULL;

CArchTaskBarOS2::CArchTaskBarOS2(void* appInstance) :
	m_nextID(kFirstReceiverID)
{
	// we need a mutex
	m_mutex       = ARCH->newMutex();

	// and a condition variable which uses the above mutex
	m_ready       = false;
	m_condVar     = ARCH->newCondVar();

	// we're going to want to get a result from the thread we're
	// about to create to know if it initialized successfully.
	// so we lock the condition variable.
	ARCH->lockMutex(m_mutex);

#if 0 /* later */
	// open a window and run an event loop in a separate thread.
	// this has to happen in a separate thread because if we
	// create a window on the current desktop with the current
	// thread then the current thread won't be able to switch
	// desktops if it needs to.
	m_thread      = ARCH->newThread(&CArchTaskBarOS2::threadEntry, this);

	// wait for child thread
	while (!m_ready) {
		ARCH->waitCondVar(m_condVar, m_mutex, -1.0);
	}
#else
        m_thread = NULL;
#endif /* later */

	// ready
	ARCH->unlockMutex(m_mutex);
}

CArchTaskBarOS2::~CArchTaskBarOS2()
{
	if (m_thread != NULL) {
		WinPostMsg(m_hwnd, WM_QUIT, 0, 0);
		ARCH->wait(m_thread, -1.0);
		ARCH->closeThread(m_thread);
	}
	ARCH->closeCondVar(m_condVar);
	ARCH->closeMutex(m_mutex);
}

void
CArchTaskBarOS2::addDialog(HWND hwnd)
{
//later	CArchMiscWindows::addDialog(hwnd);
}

void
CArchTaskBarOS2::removeDialog(HWND hwnd)
{
//later	CArchMiscWindows::removeDialog(hwnd);
}

void
CArchTaskBarOS2::addReceiver(IArchTaskBarReceiver* receiver)
{
	// ignore bogus receiver
	if (receiver == NULL) {
		return;
	}

	// add receiver if necessary
	CReceiverToInfoMap::iterator index = m_receivers.find(receiver);
	if (index == m_receivers.end()) {
		// add it, creating a new message ID for it
		CReceiverInfo info;
		info.m_id = getNextID();
		index = m_receivers.insert(std::make_pair(receiver, info)).first;

		// add ID to receiver mapping
		m_idTable.insert(std::make_pair(info.m_id, index));
	}

	// add receiver
	WinPostMsg(m_hwnd, kAddReceiver, (MPARAM)index->second.m_id, 0);
}

void
CArchTaskBarOS2::removeReceiver(IArchTaskBarReceiver* receiver)
{
	// find receiver
	CReceiverToInfoMap::iterator index = m_receivers.find(receiver);
	if (index == m_receivers.end()) {
		return;
	}

	// remove icon.  wait for this to finish before returning.
	WinSendMsg(m_hwnd, kRemoveReceiver, (MPARAM)index->second.m_id, 0);

	// recycle the ID
	recycleID(index->second.m_id);

	// discard
	m_idTable.erase(index->second.m_id);
	m_receivers.erase(index);
}

void
CArchTaskBarOS2::updateReceiver(IArchTaskBarReceiver* receiver)
{
	// find receiver
	CReceiverToInfoMap::const_iterator index = m_receivers.find(receiver);
	if (index == m_receivers.end()) {
		return;
	}

	// update icon and tool tip
	WinPostMsg(m_hwnd, kUpdateReceiver, (MPARAM)index->second.m_id, 0);
}

ULONG
CArchTaskBarOS2::getNextID()
{
	if (m_oldIDs.empty()) {
		return m_nextID++;
	}
	ULONG id = m_oldIDs.back();
	m_oldIDs.pop_back();
	return id;
}

void
CArchTaskBarOS2::recycleID(ULONG id)
{
	m_oldIDs.push_back(id);
}

void
CArchTaskBarOS2::addIcon(ULONG id)
{
	ARCH->lockMutex(m_mutex);
	CIDToReceiverMap::const_iterator index = m_idTable.find(id);
	if (index != m_idTable.end()) {
//later		modifyIconNoLock(index->second, NIM_ADD);
	}
	ARCH->unlockMutex(m_mutex);
}

void
CArchTaskBarOS2::removeIcon(ULONG id)
{
	ARCH->lockMutex(m_mutex);
	removeIconNoLock(id);
	ARCH->unlockMutex(m_mutex);
}

void
CArchTaskBarOS2::updateIcon(ULONG id)
{
	ARCH->lockMutex(m_mutex);
	CIDToReceiverMap::const_iterator index = m_idTable.find(id);
	if (index != m_idTable.end()) {
//later		modifyIconNoLock(index->second, NIM_MODIFY);
	}
	ARCH->unlockMutex(m_mutex);
}

void
CArchTaskBarOS2::addAllIcons()
{
	ARCH->lockMutex(m_mutex);
	for (CReceiverToInfoMap::const_iterator index = m_receivers.begin();
									index != m_receivers.end(); ++index) {
//later		modifyIconNoLock(index, NIM_ADD);
	}
	ARCH->unlockMutex(m_mutex);
}

void
CArchTaskBarOS2::removeAllIcons()
{
	ARCH->lockMutex(m_mutex);
	for (CReceiverToInfoMap::const_iterator index = m_receivers.begin();
									index != m_receivers.end(); ++index) {
		removeIconNoLock(index->second.m_id);
	}
	ARCH->unlockMutex(m_mutex);
}

void
CArchTaskBarOS2::modifyIconNoLock(
				CReceiverToInfoMap::const_iterator index, ULONG taskBarMessage)
{
#if 0
	// get receiver
	ULONG id                        = index->second.m_id;
	IArchTaskBarReceiver* receiver = index->first;

	// lock receiver so icon and tool tip are guaranteed to be consistent
	receiver->lock();

	// get icon data
	HICON icon = reinterpret_cast<HICON>(
				const_cast<IArchTaskBarReceiver::Icon>(receiver->getIcon()));

	// get tool tip
	std::string toolTip = receiver->getToolTip();

	// done querying
	receiver->unlock();

	// prepare to add icon
	NOTIFYICONDATA data;
	data.cbSize           = sizeof(NOTIFYICONDATA);
	data.hWnd             = m_hwnd;
	data.uID              = id;
	data.uFlags           = NIF_MESSAGE;
	data.uCallbackMessage = kNotifyReceiver;
	data.hIcon            = icon;
	if (icon != NULL) {
		data.uFlags |= NIF_ICON;
	}
	if (!toolTip.empty()) {
		strncpy(data.szTip, toolTip.c_str(), sizeof(data.szTip));
		data.szTip[sizeof(data.szTip) - 1] = '\0';
		data.uFlags                       |= NIF_TIP;
	}
	else {
		data.szTip[0] = '\0';
	}

	// add icon
	if (Shell_NotifyIcon(taskBarMessage, &data) == 0) {
		// failed
	}
#endif 
}

void
CArchTaskBarOS2::removeIconNoLock(ULONG id)
{
#if 0 /*later*/
	NOTIFYICONDATA data;
	data.cbSize = sizeof(NOTIFYICONDATA);
	data.hWnd   = m_hwnd;
	data.uID    = id;
	if (Shell_NotifyIcon(NIM_DELETE, &data) == 0) {
		// failed
	}
#endif 
}

void
CArchTaskBarOS2::handleIconMessage(
				IArchTaskBarReceiver* receiver, MPARAM lParam)
{
	// process message
	switch ((ULONG)lParam) {
	case WM_BUTTON2DOWN:
		receiver->showStatus();
		break;

	case WM_BUTTON2DBLCLK:
		receiver->primaryAction();
		break;

	case WM_BUTTON2UP: {
		CURSORINFO info;
		if (!WinQueryCursorInfo(HWND_DESKTOP, &info)) {
                    info.x = 10;
                    info.y = 10;
                }
		receiver->runMenu(info.x, info.y);
		break;
	}

	case WM_MOUSEMOVE:
		// currently unused
		break;

	default:
		// unused
		break;
	}
}

bool
CArchTaskBarOS2::processDialogs(QMSG * msg)
{
#if 0 /* later */
	// only one thread can be in this method on any particular object
	// at any given time.  that's not a problem since only our event
	// loop calls this method and there's just one of those.

	ARCH->lockMutex(m_mutex);

	// remove removed dialogs
	m_dialogs.erase(false);

	// merge added dialogs into the dialog list
	for (CDialogs::const_iterator index = m_addedDialogs.begin();
							index != m_addedDialogs.end(); ++index) {
		m_dialogs.insert(std::make_pair(index->first, index->second));
	}
	m_addedDialogs.clear();

	ARCH->unlockMutex(m_mutex);

	// check message against all dialogs until one handles it.
	// note that we don't hold a lock while checking because
	// the message is processed and may make calls to this
	// object.  that's okay because addDialog() and
	// removeDialog() don't change the map itself (just the
	// values of some elements).
	ARCH->lockMutex(m_mutex);
	for (CDialogs::const_iterator index = m_dialogs.begin();
							index != m_dialogs.end(); ++index) {
		if (index->second) {
			ARCH->unlockMutex(m_mutex);
			if (IsDialogMessage(index->first, msg)) {
				return true;
			}
			ARCH->lockMutex(m_mutex);
		}
	}
	ARCH->unlockMutex(m_mutex);
#endif /* later */
	return false;
}

MRESULT
CArchTaskBarOS2::wndProc(HWND hwnd,
				ULONG msg, MPARAM wParam, MPARAM lParam)
{
	switch (msg) {
	case kNotifyReceiver: {
		// lookup receiver
		CIDToReceiverMap::const_iterator index = m_idTable.find((ULONG)wParam);
		if (index != m_idTable.end()) {
			IArchTaskBarReceiver* receiver = index->second->first;
			handleIconMessage(receiver, lParam);
			return 0;
		}
		break;
	}

	case kAddReceiver:
		addIcon((ULONG)wParam);
		break;

	case kRemoveReceiver:
		removeIcon((ULONG)wParam);
		break;

	case kUpdateReceiver:
		updateIcon((ULONG)wParam);
		break;

	default:
		if (msg == m_taskBarRestart) {
			// task bar was recreated so re-add our icons
			addAllIcons();
		}
		break;
	}

	return WinDefWindowProc(hwnd, msg, wParam, lParam);
}

MRESULT EXPENTRY
CArchTaskBarOS2::staticWndProc(HWND hwnd, ULONG msg,
				MPARAM wParam, MPARAM lParam)
{
#if 0 /*later*/
	// if msg is WM_NCCREATE, extract the CArchTaskBarOS2* and put
	// it in the extra window data then forward the call.
	CArchTaskBarOS2* self = NULL;
	if (msg == WM_CREATE) {
		CREATESTRUCT* createInfo;
		createInfo = reinterpret_cast<CREATESTRUCT*>(lParam);
		self       = reinterpret_cast<CArchTaskBarOS2*>(
												createInfo->lpCreateParams);
		SetWindowLong(hwnd, 0, reinterpret_cast<LONG>(self));
	}
	else {
		// get the extra window data and forward the call
		LONG data = GetWindowLong(hwnd, 0);
		if (data != 0) {
			self = reinterpret_cast<CArchTaskBarOS2*>(
							reinterpret_cast<void*>(data));
		}
	}

	// forward the message
	if (self != NULL) {
		return self->wndProc(hwnd, msg, wParam, lParam);
	}
	else {
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
#endif 
}

void
CArchTaskBarOS2::threadMainLoop()
{
#if 0 /*later*/
	// register the task bar restart message
	m_taskBarRestart        = RegisterWindowMessage(TEXT("TaskbarCreated"));

	// register a window class
	WNDCLASSEX classInfo;
	classInfo.cbSize        = sizeof(classInfo);
	classInfo.style         = CS_NOCLOSE;
	classInfo.lpfnWndProc   = &CArchTaskBarOS2::staticWndProc;
	classInfo.cbClsExtra    = 0;
	classInfo.cbWndExtra    = sizeof(CArchTaskBarOS2*);
	classInfo.hInstance     = s_appInstance;
	classInfo.hIcon         = NULL;
	classInfo.hCursor       = NULL;
	classInfo.hbrBackground = NULL;
	classInfo.lpszMenuName  = NULL;
	classInfo.lpszClassName = TEXT("SynergyTaskBar");
	classInfo.hIconSm       = NULL;
	ATOM windowClass        = RegisterClassEx(&classInfo);

	// create window
	m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW,
							reinterpret_cast<LPCTSTR>(windowClass),
							TEXT("Synergy Task Bar"),
							WS_POPUP,
							0, 0, 1, 1,
							NULL,
							NULL,
							s_appInstance,
							reinterpret_cast<void*>(this));

	// signal ready
	ARCH->lockMutex(m_mutex);
	m_ready = true;
	ARCH->broadcastCondVar(m_condVar);
	ARCH->unlockMutex(m_mutex);

	// handle failure
	if (m_hwnd == NULL) {
		UnregisterClass(reinterpret_cast<LPCTSTR>(windowClass), s_appInstance);
		return;
	}

	// main loop
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0)) {
		if (!processDialogs(&msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	// clean up
	removeAllIcons();
	DestroyWindow(m_hwnd);
	UnregisterClass(reinterpret_cast<LPCTSTR>(windowClass), s_appInstance);
#endif /* later */
}

void*
CArchTaskBarOS2::threadEntry(void* self)
{
	reinterpret_cast<CArchTaskBarOS2*>(self)->threadMainLoop();
	return NULL;
}
