/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2004 Chris Schoeneman
 * Copyright (C) 2006 Knut St. Osmundsen
 *
 * 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 "CPMEventQueueBuffer.h"
#include "CThread.h"
#include "CLog.h"
#include "IEventQueue.h"
#include "CPMUtil.h"
#include <unistd.h>

//
// CEventQueueTimer
//

class CEventQueueTimer { };


//
// CPMEventQueueBuffer
//

CPMEventQueueBuffer::CPMEventQueueBuffer()
{
    // the anchor block. Remeber it for use when waiting, peeking, and getting events.
    m_hab = NULLHANDLE;
    PTIB pTib;
    PPIB pPib;
    DosGetInfoBlocks(&pTib, &pPib);
    LOG((CLOG_DEBUG2 "CPMEventQueueBuffer: ultype=%lx tid=%lx", pPib->pib_ultype, pTib->tib_ptib2->tib2_ultid));
    if (pPib->pib_ultype != 3) {
        pPib->pib_ultype = 3;
        m_shallTerminate = true;
        m_hab = WinInitialize(0);
    }
    if (m_hab == NULLHANDLE) {
        m_hab = CPMUtil::getHAB();
        if (m_hab == NULLHANDLE) {
            m_shallTerminate = true;
            m_hab = WinInitialize(0);
        }
    }
     
    // the message queue. remember it so we can post and get messages.
    m_hmq = CPMUtil::getHMQ(m_hab);
    if (m_hmq == NULLHANDLE) {
        m_shallDestroyMsgQueue = true;
        m_hmq = WinCreateMsgQueue(m_hab, 0);
    }

    // check that we succeeded the two previous steps.
    if (m_hab == NULLHANDLE || m_hmq == NULLHANDLE) {
		LOG((CLOG_CRIT "couldn't get the hab(%lx)/hmq(%lx) of the current thread!", m_hab, m_hmq));
		assert(m_hab != NULLHANDLE && m_hmq != NULLHANDLE);
	}
    assert(CPMUtil::getHAB() == m_hab);

	// create a message type for custom events
	m_userEvent  = WM_USER + 42 + 42 + 42;

	// get message type for daemon quit
	m_daemonQuit = WM_USER + 666;

    // create the event semaphore we'll be waiting on when waiting for messages.
    m_hev = NULLHANDLE;
    APIRET rc = DosCreateEventSem(NULL, &m_hev, 0, FALSE);
    assert(rc == NO_ERROR);
    LOG((CLOG_DEBUG2 "CPMEventQueueBuffer: m_userEvent=%#lx m_daemonQuit=%#lx m_hev=%#lx m_hab=%#lx m_hmq=%#lx m_shallTermiate=%d m_shallDestroyMsgQueue=%d", 
         m_userEvent, m_daemonQuit, m_hev, m_hab, m_hmq, m_shallTerminate, m_shallDestroyMsgQueue));
}

CPMEventQueueBuffer::~CPMEventQueueBuffer()
{
	// do nothing
    DosCloseEventSem(m_hev);
    if (m_shallDestroyMsgQueue) {
        WinDestroyMsgQueue(m_hmq);
    }
    if (m_shallTerminate) {
        WinTerminate(m_hab);
    }
}

void
CPMEventQueueBuffer::waitForEvent(double timeout)
{
	// check if messages are available first.
    ULONG fStatus = WinQueryQueueStatus(HWND_DESKTOP);
    if (!fStatus) {
        ULONG cPosts;
        DosResetEventSem(m_hev, &cPosts);
        fStatus = WinQueryQueueStatus(HWND_DESKTOP);
    }
    LOG((CLOG_DEBUG2 "waitForEvent: %#lx", fStatus)); 
    if (!fStatus) {
        // convert timeout and wait.
        ULONG ulPMTimeout = timeout < 0.0
                          ? SEM_INDEFINITE_WAIT
                          : (ULONG)(1000.0 * timeout);
        WinWaitEventSem(m_hev, ulPMTimeout);
        LOG((CLOG_DEBUG2 "waitForEvent: ulPMTimeout=%ld", ulPMTimeout)); 
    }
}

IEventQueueBuffer::Type
CPMEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID)
{
	// peek at messages first.
    QMSG qmsg;
    if (!WinPeekMsg(m_hab, &qmsg, NULLHANDLE, 0, 0, PM_NOREMOVE)) {
        LOG((CLOG_DEBUG2 "getEvent: kNone"));
		return kNone;
    }

    // get the message
    if (    !WinGetMsg(m_hab, &m_event, NULLHANDLE, 0, 0)
        ||  (m_event.msg == m_daemonQuit && m_daemonQuit != 0)) {
		event = CEvent(CEvent::kQuit);
		return kSystem;
	}

    if (m_event.msg == m_userEvent) {
		dataID = (UInt32)(uintptr_t)m_event.mp1;
        LOG((CLOG_DEBUG2 "getEvent: dataID=%#x", dataID));
		return kUser;
	}

    event = CEvent(CEvent::kSystem, IEventQueue::getSystemTarget(), &m_event);
    LOG((CLOG_DEBUG2 "getEvent: kSystem"));
    return kSystem;
}

bool
CPMEventQueueBuffer::addEvent(UInt32 dataID)
{
    LOG((CLOG_DEBUG2 "addEvent: dataID=%#x", dataID));
    BOOL fRc = WinPostQueueMsg(m_hmq, m_userEvent, (MPARAM)dataID, 0) != FALSE;
    if (fRc) {
        DosPostEventSem(m_hev);
    } else {
        LOG((CLOG_CRIT "addEvent: dataID=%#x fRc=%d lasterr=%d", dataID, WinGetLastError(CPMUtil::getHAB()))); 
    }
    return fRc;
}

bool
CPMEventQueueBuffer::isEmpty() const
{
    ULONG fStatus = WinQueryQueueStatus(HWND_DESKTOP);
    LOG((CLOG_DEBUG2 "isEmpty: %#lx", fStatus)); 
	return fStatus == 0;
}

CEventQueueTimer*
CPMEventQueueBuffer::newTimer(double, bool) const
{
	return new CEventQueueTimer;
}

void
CPMEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const
{
	delete timer;
}

/*
 * Local Variables:
 *  mode: c
 *  c-file-style: "k&r"
 *  c-basic-offset: 4
 *  tab-width: 4
 *  indent-tabs-mode: t
 * End:
 */

