/*
 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (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 OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
#define CFRUNLOOP_NEW_API 1

#include <CoreFoundation/CFMachPort.h>
//#include <IOKit/hid/IOHIDLib.h>
//#include <unistd.h>

#include "IOHIDQueueClass.h"
#include "IOHIDLibUserClient.h"

__BEGIN_DECLS
#include <IOKit/IODataQueueClient.h>
#include <mach/mach_interface.h>
#include <IOKit/iokitmig.h>
__END_DECLS

#define ownerCheck() do {		\
    if (!fOwningDevice)			\
	return kIOReturnNoDevice;	\
} while (0)

#define connectCheck() do {		\
    if ((!fOwningDevice) ||		\
    	(!fOwningDevice->fConnection))	\
	return kIOReturnNoDevice;	\
} while (0)

#define openCheck() do {	    \
    if (!fIsCreated)		    \
        return kIOReturnNotOpen;    \
} while (0)

#define allChecks() do {	    \
    connectCheck();		    \
    openCheck();		    \
} while (0)

IOHIDQueueClass::IOHIDQueueClass()
: IOHIDIUnknown(NULL),
  fAsyncPort(MACH_PORT_NULL),
  fIsCreated(false)
{
    fHIDQueue.pseudoVTable = (IUnknownVTbl *)  &sHIDQueueInterfaceV1;
    fHIDQueue.obj = this;
}

IOHIDQueueClass::~IOHIDQueueClass()
{
    // if we are owned, detatch
    if (fOwningDevice)
        fOwningDevice->detachQueue(this);
}

HRESULT IOHIDQueueClass::queryInterface(REFIID /*iid*/, void **	/*ppv*/)
{
    // ¥¥¥ should we return our parent if that type is asked for???
    
    return E_NOINTERFACE;
}

IOReturn IOHIDQueueClass::
createAsyncEventSource(CFRunLoopSourceRef *source)
{
    IOReturn ret;
    CFMachPortRef cfPort;
    CFMachPortContext context;
    Boolean shouldFreeInfo;

    if (!fAsyncPort) {     
        ret = createAsyncPort(0);
        if (kIOReturnSuccess != ret)
            return ret;
    }

    context.version = 1;
    context.info = this;
    context.retain = NULL;
    context.release = NULL;
    context.copyDescription = NULL;

    cfPort = CFMachPortCreateWithPort(NULL, fAsyncPort,
                (CFMachPortCallBack) IODispatchCalloutFromMessage,
                &context, &shouldFreeInfo);
    if (!cfPort)
        return kIOReturnNoMemory;
    
    fCFSource = CFMachPortCreateRunLoopSource(NULL, cfPort, 0);
    CFRelease(cfPort);
    if (!fCFSource)
        return kIOReturnNoMemory;

    if (source)
        *source = fCFSource;

    return kIOReturnSuccess;
}

CFRunLoopSourceRef IOHIDQueueClass::getAsyncEventSource()
{
    return fCFSource;
}

IOReturn IOHIDQueueClass::createAsyncPort(mach_port_t *port)
{
    IOReturn ret;
    
    connectCheck();
    
    ret = IOCreateReceivePort(kOSAsyncCompleteMessageID, &fAsyncPort);
    if (kIOReturnSuccess == ret) {
        if (port)
            *port = fAsyncPort;

        if (fIsCreated) {
            natural_t asyncRef[1];
            mach_msg_type_number_t len = 0;
        
            // async kIOCDBUserClientSetAsyncPort, kIOUCScalarIScalarO, 0, 0
            return io_async_method_structureI_structureO(
                    fOwningDevice->fConnection, fAsyncPort, asyncRef, 1,
                    kIOHIDLibUserClientSetAsyncPort, NULL, 0, NULL, &len);
        }
    }

    return ret;
}

mach_port_t IOHIDQueueClass::getAsyncPort()
{
    return fAsyncPort;
}

IOReturn IOHIDQueueClass::create (UInt32 flags, UInt32 depth)
{
    IOReturn ret = kIOReturnSuccess;

    connectCheck();
    
    // ¥¥Êtodo, check flags/depth to see if different (might need to recreate?)
    if (fIsCreated)
        return kIOReturnSuccess;

    // sent message to create queue
    //  kIOHIDLibUserClientCreateQueue, kIOUCScalarIScalarO, 2, 1
    int args[6], i = 0;
    args[i++] = flags;
    args[i++] = depth;
    mach_msg_type_number_t len = 1;
    ret = io_connect_method_scalarI_scalarO(
            fOwningDevice->fConnection, kIOHIDLibUserClientCreateQueue, args, i, (int *) &fQueueRef, &len);
    if (ret != kIOReturnSuccess)
        return ret;
    
    // we have created it
    fIsCreated = true;
    fCreatedFlags = flags;
    fCreatedDepth = depth;
    
    // if we have async port, set it on other side
    if (fAsyncPort)
    {
        natural_t asyncRef[1];
        mach_msg_type_number_t len = 0;
    
        // async 
        // kIOHIDLibUserClientSetAsyncPort, kIOUCScalarIScalarO, 0, 0
        ret = io_async_method_scalarI_scalarO(
                fOwningDevice->fConnection, fAsyncPort, asyncRef, 1,
                kIOHIDLibUserClientSetAsyncPort, NULL, 0, NULL, &len);
        if (ret != kIOReturnSuccess) {
            (void) this->dispose();
            return ret;
        }
    }
    
    // get the queue shared memory
    vm_address_t address = nil;
    vm_size_t size = 0;
    
    ret = IOConnectMapMemory (	fOwningDevice->fConnection, 
                                fQueueRef, 
                                mach_task_self(), 
                                &address, 
                                &size, 
                                kIOMapAnywhere	);
    if (ret == kIOReturnSuccess)
    {
        fQueueMappedMemory = (IODataQueueMemory *) address;
        fQueueMappedMemorySize = size;
    }
    
    return ret;
}

IOReturn IOHIDQueueClass::dispose()
{
    IOReturn ret = kIOReturnSuccess;

    allChecks();

    // sent message to dispose queue
    mach_msg_type_number_t len = 0;

    //  kIOHIDLibUserClientDisposeQueue, kIOUCScalarIScalarO, 1, 0
    int args[6], i = 0;
    args[i++] = fQueueRef;
    ret = io_connect_method_scalarI_scalarO(
            fOwningDevice->fConnection, kIOHIDLibUserClientDisposeQueue, args, i, NULL, &len);
    if (ret != kIOReturnSuccess)
        return ret;

    // mark it dead
    fIsCreated = false;
    
    // ¥¥¥¥ TODO unmap memory when that call works
    
    return kIOReturnSuccess;
}

/* Any number of hid elements can feed the same queue */
IOReturn IOHIDQueueClass::addElement (
                            IOHIDElementCookie elementCookie,
                            UInt32 flags)
{
    IOReturn ret = kIOReturnSuccess;

    allChecks();

    //  kIOHIDLibUserClientAddElementToQueue, kIOUCScalarIScalarO, 3, 0
    int args[6], i = 0;
    args[i++] = fQueueRef;
    args[i++] = (int) elementCookie;
    args[i++] = flags;
    mach_msg_type_number_t len = 0;
    ret = io_connect_method_scalarI_scalarO(
            fOwningDevice->fConnection, kIOHIDLibUserClientAddElementToQueue, args, i, NULL, &len);
    if (ret != kIOReturnSuccess)
        return ret;

    return kIOReturnSuccess;
}

IOReturn IOHIDQueueClass::removeElement (IOHIDElementCookie elementCookie)
{
    IOReturn ret = kIOReturnSuccess;

    allChecks();

    //  kIOHIDLibUserClientRemoveElementFromQueue, kIOUCScalarIScalarO, 2, 0
    int args[6], i = 0;
    args[i++] = fQueueRef;
    args[i++] = (int) elementCookie;
    mach_msg_type_number_t len = 0;
    ret = io_connect_method_scalarI_scalarO(
            fOwningDevice->fConnection, kIOHIDLibUserClientRemoveElementFromQueue, args, i, NULL, &len);
    if (ret != kIOReturnSuccess)
        return ret;

    return kIOReturnSuccess;
}

Boolean IOHIDQueueClass::hasElement (IOHIDElementCookie elementCookie)
{
    Boolean returnHasElement = false;

    // cannot do allChecks(), since return is a Boolean
    if (((!fOwningDevice) ||
    	(!fOwningDevice->fConnection)) ||
        (!fIsCreated))
        return false;

    //  kIOHIDLibUserClientQueueHasElement, kIOUCScalarIScalarO, 2, 1
    int args[6], i = 0;
    args[i++] = fQueueRef;
    args[i++] = (int) elementCookie;
    mach_msg_type_number_t len = 1;
    IOReturn ret = io_connect_method_scalarI_scalarO(
            fOwningDevice->fConnection, kIOHIDLibUserClientQueueHasElement, args, 
            i, (int *) &returnHasElement, &len);
    if (ret != kIOReturnSuccess)
        return false;

    return returnHasElement;
}


/* start/stop data delivery to a queue */
IOReturn IOHIDQueueClass::start ()
{
    IOReturn ret = kIOReturnSuccess;

    allChecks();

    //  kIOHIDLibUserClientStartQueue, kIOUCScalarIScalarO, 1, 0
    int args[6], i = 0;
    args[i++] = fQueueRef;
    mach_msg_type_number_t len = 0;
    ret = io_connect_method_scalarI_scalarO(
            fOwningDevice->fConnection, kIOHIDLibUserClientStartQueue, args, i, NULL, &len);
    if (ret != kIOReturnSuccess)
        return ret;

    return kIOReturnSuccess;
}

IOReturn IOHIDQueueClass::stop ()
{
    IOReturn ret = kIOReturnSuccess;

    allChecks();

    //  kIOHIDLibUserClientStopQueue, kIOUCScalarIScalarO, 1, 0
    int args[6], i = 0;
    args[i++] = fQueueRef;
    mach_msg_type_number_t len = 0;
    ret = io_connect_method_scalarI_scalarO(
            fOwningDevice->fConnection, kIOHIDLibUserClientStopQueue, args, i, NULL, &len);
    if (ret != kIOReturnSuccess)
        return ret;
    
    // ¥¥¥ TODO after we stop the queue, we should empty the queue here, in user space
    // (to be consistant with setting the head from user space)
    
    return kIOReturnSuccess;
}

/* read next event from a queue */
/* maxtime, if non-zero, limits read events to those that occured */
/*   on or before maxTime */
/* timoutMS is the timeout in milliseconds, a zero timeout will cause */
/*	this call to be non-blocking (returning queue empty) if there */
/*	is a NULL callback, and blocking forever until the queue is */
/*	non-empty if their is a valid callback */
IOReturn IOHIDQueueClass::getNextEvent (
                        IOHIDEventStruct *	event,
                        AbsoluteTime		maxTime,
                        UInt32 			timeoutMS)
{
    IOReturn ret = kIOReturnSuccess;
    
#if 0
    printf ("IOHIDQueueClass::getNextEvent about to peek\n");

    printf ("fQueueMappedMemory->queueSize = %lx\n", fQueueMappedMemory->queueSize);
    printf ("fQueueMappedMemory->head = %lx\n", fQueueMappedMemory->head);
    printf ("fQueueMappedMemory->tail = %lx\n", fQueueMappedMemory->tail);
#endif
    
    // check entry size
    IODataQueueEntry * nextEntry = IODataQueuePeek(fQueueMappedMemory);

	// if queue empty, then stop
	if (nextEntry == NULL)
		return kIOReturnUnderrun;

#if 0
    printf ("IODataQueuePeek: %lx\n", (UInt32) nextEntry);
    if (nextEntry)
    {
        printf ("nextEntry->size = %lx\n", nextEntry->size);
        printf ("nextEntry->data = %lx\n", (UInt32) nextEntry->data);

        IOHIDElementValue * nextElementValue = (IOHIDElementValue *) &nextEntry->data;
        
        printf ("nextElementValue->cookie = %lx\n", (UInt32) nextElementValue->cookie);
        printf ("nextElementValue->value[0] = %lx\n", nextElementValue->value[0]);
    }
#endif

    // the element value (note size only 1 for now)
    IOHIDElementValue hidElementValue;
    UInt32 dataSize = sizeof (IOHIDElementValue);
    
#if 0
    // ¥¥¥ TODO deal with long sizes
    if (event->longValueSize !=	0)
        printf ("long size specified\n");
#endif
    
    // check size of next entry
    if (nextEntry && nextEntry->size != sizeof(IOHIDElementValue))
        printf ("size mismatch (%ld, %ld)\n", nextEntry->size, sizeof(IOHIDElementValue));
    
    // dequeue the item
//    printf ("IOHIDQueueClass::getNextEvent about to dequeue\n");
    ret = IODataQueueDequeue(fQueueMappedMemory, NULL, &dataSize);
//    printf ("IODataQueueDequeue result %lx\n", (UInt32) ret);
    
    if (nextEntry)
    {
        IOHIDElementValue * nextElementValue = (IOHIDElementValue *) &nextEntry->data;
        hidElementValue = *nextElementValue;
    }

    // if we got an entry
    if (ret == kIOReturnSuccess && nextEntry)
    {
#if 0
        printf ("hidElementValue.cookie = %lx\n", (UInt32) hidElementValue.cookie);
        printf ("hidElementValue.value[0] = %lx\n", hidElementValue.value[0]);
#endif

        // check size of result
        if (dataSize != sizeof(IOHIDElementValue))
            printf ("size mismatch (%ld, %ld)\n", dataSize, sizeof(IOHIDElementValue));
        
        // copy the data to the event struct
        event->type = fOwningDevice->getElementType(hidElementValue.cookie);
        event->elementCookie = hidElementValue.cookie;
        event->value = hidElementValue.value[0];
        event->timestamp = hidElementValue.timestamp;
        event->longValueSize = 0;
    }
    
    
    return ret;
}


/* set a callback for notification when queue transistions from non-empty */
/* callback, if non-NULL is a callback to be called when data is */
/*  inserted to the queue  */
/* callbackTarget and callbackRefcon are passed to the callback */
IOReturn IOHIDQueueClass::setEventCallout (
                        IOHIDCallbackFunction * 	callback,
                        void * 			callbackTarget,
                        void *			callbackRefcon)
{
    return kIOReturnUnsupported;
}


/* Get the current notification callout */
IOReturn IOHIDQueueClass::getEventCallout (
                        IOHIDCallbackFunction ** 	outCallback,
                        void ** 			outCallbackTarget,
                        void **			outCallbackRefcon)
{
    return kIOReturnUnsupported;
}


IOHIDQueueInterface IOHIDQueueClass::sHIDQueueInterfaceV1 =
{
    0,
    &IOHIDIUnknown::genericQueryInterface,
    &IOHIDIUnknown::genericAddRef,
    &IOHIDIUnknown::genericRelease,
    &IOHIDQueueClass::queueCreateAsyncEventSource,
    &IOHIDQueueClass::queueGetAsyncEventSource,
    &IOHIDQueueClass::queueCreateAsyncPort,
    &IOHIDQueueClass::queueGetAsyncPort,
    &IOHIDQueueClass::queueCreate,
    &IOHIDQueueClass::queueDispose,
    &IOHIDQueueClass::queueAddElement,
    &IOHIDQueueClass::queueRemoveElement,
    &IOHIDQueueClass::queueHasElement,
    &IOHIDQueueClass::queueStart,
    &IOHIDQueueClass::queueStop,
    &IOHIDQueueClass::queueGetNextEvent,
    &IOHIDQueueClass::queueSetEventCallout,
    &IOHIDQueueClass::queueGetEventCallout,
};

// Methods for routing asynchronous completion plumbing.
IOReturn IOHIDQueueClass::
queueCreateAsyncEventSource(void *self, CFRunLoopSourceRef *source)
    { return getThis(self)->createAsyncEventSource(source); }

CFRunLoopSourceRef IOHIDQueueClass::
queueGetAsyncEventSource(void *self)
    { return getThis(self)->getAsyncEventSource(); }

IOReturn IOHIDQueueClass::
queueCreateAsyncPort(void *self, mach_port_t *port)
    { return getThis(self)->createAsyncPort(port); }

mach_port_t IOHIDQueueClass::
queueGetAsyncPort(void *self)
    { return getThis(self)->getAsyncPort(); }

/* Basic IOHIDQueue interface */
IOReturn IOHIDQueueClass::
        queueCreate (void * 			self, 
                    UInt32 			flags,
                    UInt32			depth)
    { return getThis(self)->create(flags, depth); }

IOReturn IOHIDQueueClass::queueDispose (void * self)
    { return getThis(self)->dispose(); }

/* Any number of hid elements can feed the same queue */
IOReturn IOHIDQueueClass::queueAddElement (void * self,
                            IOHIDElementCookie elementCookie,
                            UInt32 flags)
    { return getThis(self)->addElement(elementCookie, flags); }

IOReturn IOHIDQueueClass::queueRemoveElement (void * self, IOHIDElementCookie elementCookie)
    { return getThis(self)->removeElement(elementCookie); }

Boolean IOHIDQueueClass::queueHasElement (void * self, IOHIDElementCookie elementCookie)
    { return getThis(self)->hasElement(elementCookie); }

/* start/stop data delivery to a queue */
IOReturn IOHIDQueueClass::queueStart (void * self)
    { return getThis(self)->start(); }

IOReturn IOHIDQueueClass::queueStop (void * self)
    { return getThis(self)->stop(); }

/* read next event from a queue */
IOReturn IOHIDQueueClass::queueGetNextEvent (
                        void * 			self,
                        IOHIDEventStruct *	event,
                        AbsoluteTime		maxTime,
                        UInt32 			timeoutMS)
    { return getThis(self)->getNextEvent(event, maxTime, timeoutMS); }

/* set a callback for notification when queue transistions from non-empty */
IOReturn IOHIDQueueClass::queueSetEventCallout (
                        void * 			self,
                        IOHIDCallbackFunction * callback,
                        void * 			callbackTarget,
                        void *			callbackRefcon)
    { return getThis(self)->setEventCallout(callback, callbackTarget, callbackRefcon); }

/* Get the current notification callout */
IOReturn IOHIDQueueClass::queueGetEventCallout (
                        void * 			self,
                        IOHIDCallbackFunction ** outCallback,
                        void ** 		outCallbackTarget,
                        void **			outCallbackRefcon)
    { return getThis(self)->getEventCallout(outCallback, outCallbackTarget, outCallbackRefcon); }

