/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
	File:		EventContext.cpp

	Contains:	Impelments object in .h file
					
	
	
*/

#include "EventContext.h"
#include "OSThread.h"
#include "atomic.h"

#include <fcntl.h>
#include <errno.h>

#ifndef __Win32__
#include <unistd.h>
#endif

#if MACOSXEVENTQUEUE
#include "tempcalls.h" //includes MacOS X prototypes of event queue functions
#endif

#define EVENT_CONTEXT_DEBUG 0

#if EVENT_CONTEXT_DEBUG
#include "OS.h"
#endif

#ifdef __Win32__
unsigned int EventContext::sUniqueID = WM_USER; // See commentary in RequestEvent
#else
unsigned int EventContext::sUniqueID = 1;
#endif

EventContext::EventContext(int inFileDesc, EventThread* inThread)
:	fFileDesc(inFileDesc),
 	fUniqueID(0),
	fUniqueIDStr((char*)&fUniqueID, sizeof(fUniqueID)),
	fEventThread(inThread),
	fWatchEventCalled(false),
	fAutoCleanup(true)
{}

void EventContext::InitNonBlocking(int inFileDesc)
{
	fFileDesc = inFileDesc;
	
#ifdef __Win32__
	u_long one = 1;
	int err = ::ioctlsocket(fFileDesc, FIONBIO, &one);
#else
	int flag = ::fcntl(fFileDesc, F_GETFL, 0);
	int err = ::fcntl(fFileDesc, F_SETFL, flag | O_NONBLOCK);
#endif
	AssertV(err == 0, OSThread::GetErrno());
}

void EventContext::Cleanup()
{
	int err = 0;
	
	if (fFileDesc != kInvalidFileDesc)
	{
		//if this object is registered in the table, unregister it now
		if (fUniqueID > 0)
		{
			fEventThread->fRefTable.UnRegister(&fRef);

#if !MACOSXEVENTQUEUE
			select_removeevent(fFileDesc);//The eventqueue / select shim requires this
#ifdef __Win32__
			err = ::closesocket(fFileDesc);
#endif

#else
			//On Linux (possibly other UNIX implementations) you MUST NOT close the fd before
			//removing the fd from the select mask, and having the select function wake up
			//to register this fact. If you close the fd first, bad things may happen, like
			//the socket not getting unbound from the port & IP addr.
			//
			//So, what we do is have the select thread itself call close. This is triggered
			//by calling removeevent.
			err = ::close(fFileDesc);
#endif		
		}
		else
#ifdef __Win32__
			err = ::closesocket(fFileDesc);
#else
			err = ::close(fFileDesc);
#endif
	}

	fFileDesc = kInvalidFileDesc;
	fUniqueID = 0;
	
	AssertV(err == 0, OSThread::GetErrno());//we don't really care if there was an error, but it's nice to know
}


void EventContext::SnarfEventContext( EventContext &fromContext )
{  
	//+ show that we called watchevent
	// copy the unique id
	// set our fUniqueIDStr to the unique id
	// copy the eventreq
	// find the old event object
	// show us as the object in the fRefTable
	//		we take the OSRef from the old context, point it at our context
	//
	//TODO - this whole operation causes a race condition for Event posting
	//  way up the chain we need to disable event posting
	// or copy the posted events afer this op completes
	
	fromContext.fFileDesc = kInvalidFileDesc;
	
	fWatchEventCalled = fromContext.fWatchEventCalled; 
	fUniqueID = fromContext.fUniqueID;
	fUniqueIDStr.Set((char*)&fUniqueID, sizeof(fUniqueID)),
	
	::memcpy( &fEventReq, &fromContext.fEventReq, sizeof( struct eventreq  ) );

	fRef.Set( fUniqueIDStr, this );
	fEventThread->fRefTable.Swap(&fRef);
	fEventThread->fRefTable.UnRegister(&fromContext.fRef);
}

void EventContext::RequestEvent(int theMask)
{
#if DEBUG
	fModwatched = true;
#endif

	//
	// The first time this function gets called, we're supposed to
	// call watchevent. Each subsequent time, call modwatch. That's
	// the way the MacOS X event queue works.
	
	if (fWatchEventCalled)
	{
		fEventReq.er_eventbits = theMask;
#if MACOSXEVENTQUEUE
		if (modwatch(&fEventReq, theMask) != 0)
#else
		if (select_modwatch(&fEventReq, theMask) != 0)
#endif	
			AssertV(false, OSThread::GetErrno());
	}
	else
	{
		//allocate a Unique ID for this socket, and add it to the ref table
		
#ifdef __Win32__
		//
		// Kind of a hack. On Win32, the way that we pass around the unique ID is
		// by making it the message ID of our Win32 message (see win32ev.cpp).
		// Messages must be >= WM_USER. Hence this code to restrict the numberspace
		// of our UniqueIDs. 
		if (!compare_and_store(8192, WM_USER, &sUniqueID)) 	// Fix 2466667: message IDs above a
			fUniqueID = (PointerSizedInt)atomic_add(&sUniqueID, 1);			// level are ignored, so wrap at 8192
		else
			fUniqueID = (PointerSizedInt)WM_USER;
#else
		if (!compare_and_store(10000000, 1, &sUniqueID))
			fUniqueID = (PointerSizedInt)atomic_add(&sUniqueID, 1);
		else
			fUniqueID = 1;
#endif

		fRef.Set(fUniqueIDStr, this);
		fEventThread->fRefTable.Register(&fRef);
			
		//fill out the eventreq data structure
		::memset( &fEventReq, '\0', sizeof(fEventReq));
		fEventReq.er_type = EV_FD;
		fEventReq.er_handle = fFileDesc;
		fEventReq.er_eventbits = theMask;
		fEventReq.er_data = (void*)fUniqueID;

		fWatchEventCalled = true;
#if MACOSXEVENTQUEUE
		if (watchevent(&fEventReq, theMask) != 0)
#else
		if (select_watchevent(&fEventReq, theMask) != 0)
#endif	
			//this should never fail, but if it does, cleanup.
			AssertV(false, OSThread::GetErrno());
			
	}
}

void EventThread::Entry()
{
	struct eventreq theCurrentEvent;
	::memset( &theCurrentEvent, '\0', sizeof(theCurrentEvent) );
	
	while (true)
	{
		int theErrno = EINTR;
		while (theErrno == EINTR)
		{
#if MACOSXEVENTQUEUE
			int theReturnValue = waitevent(&theCurrentEvent, NULL);
#else
			int theReturnValue = select_waitevent(&theCurrentEvent, NULL);
#endif	
			//Sort of a hack. In the POSIX version of the server, waitevent can return
			//an actual POSIX errorcode.
			if (theReturnValue >= 0)
				theErrno = theReturnValue;
			else
				theErrno = OSThread::GetErrno();
		}
		
		AssertV(theErrno == 0, theErrno);
		
		//ok, there's data waiting on this socket. Send a wakeup.
		if (theCurrentEvent.er_data != NULL)
		{
			//The cookie in this event is an ObjectID. Resolve that objectID into
			//a pointer.
			StrPtrLen idStr((char*)&theCurrentEvent.er_data, sizeof(theCurrentEvent.er_data));
			OSRef* ref = fRefTable.Resolve(&idStr);
			if (ref != NULL)
			{
				EventContext* theContext = (EventContext*)ref->GetObject();
#if DEBUG
				theContext->fModwatched = false;
#endif
				theContext->ProcessEvent(theCurrentEvent.er_eventbits);
				fRefTable.Release(ref);
				
				
			}
		}

#if EVENT_CONTEXT_DEBUG
		SInt64	yieldStart = OS::Milliseconds();
#endif

		this->ThreadYield();

#if EVENT_CONTEXT_DEBUG
		SInt64	yieldDur = OS::Milliseconds() - yieldStart;
		static SInt64	numZeroYields;
		
		if ( yieldDur > 1 )
		{
			printf( "EventThread time in OSTHread::Yield %i, numZeroYields %i\n", (long)yieldDur, (long)numZeroYields );
			numZeroYields = 0;
		}
		else
			numZeroYields++;
#endif
	}
}
