/*
    File:       IrDiscovery.cpp

    Contains:   xxx put contents here xxx
*/

#include "IrDiscovery.h"
#include "CList.h"
#include "IrDscInfo.h"
#include "IrGlue.h"
#include "IrLMP.h"
#include "CListIterator.h"

#if (hasTracing > 0 && hasIrDiscoveryTracing > 0)

enum IrDiscoverTraceCodes
{
    kLogNew = 1,
    kLogFree,
    kLogInit,
    kLogNextState,
    kLogExtDiscStart,
    kLogDiscoveryStart,
    kLogDiscoverComplete,
    kIrExtDiscoverComplete,
    kLogDiscoverDeleteList,
    
    kEnqueueEvent,
    kDequeueEventStart,
    kDequeueEventEnd

};

static
EventTraceCauseDesc IrDiscoverEvents[] = {
    {kLogNew,                   "IrDiscovery: new object="},
    {kLogFree,                  "IrDiscovery: free object"},
    {kLogInit,                  "IrDiscovery: init"},
    {kLogNextState,             "IrDiscovery: next state, event=, state="},
    {kLogExtDiscStart,          "IrDiscovery: Ext start, numSlots="},
    {kLogDiscoveryStart,        "IrDiscovery: start, slots=, state="},
    {kLogDiscoverComplete,      "IrDiscovery: complete, obj="},
    {kIrExtDiscoverComplete,    "IrDiscovery: Ext complete count="},
    {kLogDiscoverDeleteList,    "IrDiscovery: delete discovered list, all="},

    {kEnqueueEvent,             "IrDiscovery: Event Queued"},
    {kDequeueEventStart,        "IrDiscovery: Event Start"},
    {kDequeueEventEnd,          "IrDiscovery: Event End"}
};

#define XTRACE(x, y, z) IrDALogAdd( x, y, z, IrDiscoverEvents, true )

#else
    #define XTRACE(x, y, z) ((void)0)
#endif

enum {
    kMaxDiscoverListSize = 16           // Limit the discovered list size
};

#define super TIrStream
    OSDefineMetaClassAndStructors(CIrDiscovery, TIrStream);

/*static*/
CIrDiscovery *
CIrDiscovery::cIrDiscovery(TIrGlue * glue)
{
    CIrDiscovery *obj = new CIrDiscovery;
    
    XTRACE(kLogNew, (int)obj >> 16, (short)obj);

    if (obj && !obj->Init(glue)) {
	obj->release();
	obj = nil;
    }
    return obj;
}

void
CIrDiscovery::free()
{
    int Review_Event_Usage;     // release events on the pendng discover list?
    
    XTRACE(kLogFree, (int)this >> 16, (short)this);

    DeleteDiscoveredDevicesList();      // free the objects on the list

#define FREE(x) { if (x) { (x)->release(); x = nil; }}  
    FREE(fDiscoveredDevices);
    FREE(fPendingDiscoverList);
    FREE(fMyDscInfo);

    
    super::free();
    return;
}


Boolean
CIrDiscovery::Init(TIrGlue * glue)
{
    int SET_LOCAL_HOSTNAME_HERE;            // how to set hostname for discovery info?
    
    XTRACE(kLogInit, (int)this >> 16, (short)this);
    
    fState  = kDiscoverIdle;
    fPendingDiscoverList = nil;
    fDiscoveredDevices = nil;
    fMyDscInfo = nil;
	    

#if (hasTracing > 0 && hasIrDiscoveryTracing > 0)
    if (!super::Init(glue, IrDiscoverEvents, kEnqueueEvent)) return false;
#else
    if (!super::Init(glue)) return false;
#endif


    fPendingDiscoverList = CList::cList();
    require(fPendingDiscoverList, Fail);
    
    fDiscoveredDevices = CList::cList();
    require(fDiscoveredDevices, Fail);
    
    fMyDscInfo = TIrDscInfo::tIrDscInfo();
    require(fMyDscInfo, Fail);
    
    fMyDscInfo->SetNickname("Mac/OS-X");                    // FIXME
    fMyDscInfo->SetServiceHints(kDevInfoHintComputer);      // clients can now add/delete from this set
    
/******
    Str255          nameStr;
    StringHandle    machNameHdl;
    int             i;
    
    nameStr[0] = 0;                                 // zero out string (check for empty later)
    machNameHdl = GetString(kComputerNameId);       // get the resource (don't free it ... check)
    if ( machNameHdl ) {                            // if the file sharing machine name was found
	for( i = 0; i < (*machNameHdl)[0]; i++ )        // Copy and convert to a "C" string
	    nameStr[i] = (*machNameHdl)[i+1];           // Don't convert in place.
	nameStr[i] = 0;
	nameStr[kMaxNicknameLen] = 0;               // Truncate the string if necessary
	fMyDscInfo.SetNickname( ( UChar * ) nameStr );
    }
    if (nameStr[0] == 0)                            // if no resource or empty string
	 fMyDscInfo.SetNickname(&kIASDeviceName[0]);    // Use the default "Macintosh" (from globals)
    
    fMyDscInfo.SetServiceHints(kDevInfoHintComputer);       // clients can now add/delete from this set
    return noErr;
    
Fail_NewDiscDevList:
    delete fPendingDiscoverList;
    
Fail_NewPendDiscList:
    return 1;
    ******/
    return true;
    
Fail:

    if (fPendingDiscoverList) {
	fPendingDiscoverList->release();
	fPendingDiscoverList = nil;
    }
    
    if (fDiscoveredDevices) {
	fDiscoveredDevices->release();
	fDiscoveredDevices = nil;
    }
    
    if (fMyDscInfo) {
	fMyDscInfo->release();
	fMyDscInfo = nil;
    }
    return false;
}


//--------------------------------------------------------------------------------
//      NextState
//--------------------------------------------------------------------------------
void CIrDiscovery::NextState( ULong event )
{
    XTRACE(kLogNextState, event, fState);
    
    require(event == kIrDiscoverRequestEvent || event == kIrDiscoverReplyEvent || event == kIrDisconnectReplyEvent, Fail);
    
    switch (event) {
	case kIrDiscoverRequestEvent:
	    DiscoverStart();
	    break;

	case kIrDiscoverReplyEvent:
	    HandleDiscoverComplete();
	    break;

	case kIrDisconnectReplyEvent:
//          HandleDisconnectComplete( nil );
	    break;
    }

Fail:
    return;
    
} // CIrDiscovery::NextState



//--------------------------------------------------------------------------------
//      DiscoverStart
//--------------------------------------------------------------------------------
void CIrDiscovery::DiscoverStart()
{
    TIrDiscoverRequest * request = (TIrDiscoverRequest *)this->GetCurrentEvent();
    IrDAErr err = noErr;
    
    XTRACE(kLogDiscoveryStart, request->fNumSlots, fState);
    
    require(request->fClient, Fail);
    
    if( request->fNumSlots > kMaxDiscoverSlots )
	err = errDiscoveryTooManySlots;                 // Range check the slot count
		
    if( err ) {                                         // Bounce it back to the client
	request->fResult    = err;                      
	request->fEvent     = kIrDiscoverReplyEvent;
	request->fClient->EnqueueEvent( request );
	return;
    }   
	
    if( request->fNumSlots == 0 )                   // Use the default
	request->fNumSlots = kDiscoverDefaultSlotCount;
	    
    // Can I do a discovery ?
    if( fState == kDiscoverActive ) {       // There's a pending discover.  Push on the list
	if( fPendingDiscoverList )          // Queue up the request
	    fPendingDiscoverList->InsertLast( request );    
	return;
    }
	
    // If there is a LAP connection then respond to client with the current
    // discovery info.
    
    if( fIrDA->IsLAPConnected() ) {
	// Remove every response except the connected device's
	request->fResult                = errDiscoveryInConnection;
	request->fDiscoveredDevices     = fDiscoveredDevices;
	request->fEvent =  kIrDiscoverReplyEvent;           // jdg, it's a reply event, not a request
	request->fClient->EnqueueEvent( request );
    }
    // Issue request to LMP to perform a XID discovery
    else {
	fState = kDiscoverActive;                   // Set flag we are in progress
	DeleteDiscoveredDevicesList();              // JDG: reset list of discovered devices
	request->fDiscoveredDevices = fDiscoveredDevices;
	
	// All done - until discover completes
	    
	// Note: fMyDscInfo fields initialized by caller (oh yeah???????)
	fIrDA->GetLMP()->EnqueueEvent( request );
    }
    return;
    
Fail:
    // free event?
    return;
} // CIrDiscovery::DiscoverStart

//--------------------------------------------------------------------------------
//      HandleDiscoverComplete
//--------------------------------------------------------------------------------
void CIrDiscovery::HandleDiscoverComplete()
{
    TIrDiscoverReply    *   discoverReply = (TIrDiscoverReply*)GetCurrentEvent();
    TIrDiscoverReply    *   pendingRequest;
    
    XTRACE( kLogDiscoverComplete, (int)this >> 16, (short)this);
    check( fDiscoveredDevices == discoverReply->fDiscoveredDevices );

    // Complete discover request (let caller pick a device to connect to)
    fState = kDiscoverIdle;

    if( discoverReply->fClient == this )
	this->HandleExtDiscoverComplete( discoverReply );
    else
	discoverReply->fClient->EnqueueEvent( discoverReply );
	
    if( fPendingDiscoverList->Count() ) {       // Notify all pendingrequestors if necessary
	CListIterator *iter = CListIterator::cListIterator(fPendingDiscoverList);
	require(iter, Fail);
	for( pendingRequest = ( TIrDiscoverReply * )iter->FirstItem(); iter->More(); 
	     pendingRequest = ( TIrDiscoverReply * )iter->NextItem() ) {
	     
	     // Its a response now.
	     pendingRequest->fEvent = kIrDiscoverReplyEvent;
	     
	     // Copy the results from the reply to each pending reply
	     pendingRequest->fResult            = discoverReply->fResult;
	     pendingRequest->fNumSlots          = discoverReply->fNumSlots;
	     pendingRequest->fConflictDevAddr   = discoverReply->fConflictDevAddr;
	     pendingRequest->fDiscoveredDevices = discoverReply->fDiscoveredDevices;
	     pendingRequest->fPassiveDiscovery  = discoverReply->fPassiveDiscovery;

	    // return pending request
	    if( pendingRequest->fClient == this )
		this->HandleExtDiscoverComplete( pendingRequest );
	    else
		pendingRequest->fClient->EnqueueEvent( pendingRequest );
	}
	iter->release();
    }
    // This response if complete so clear the pending list
    fPendingDiscoverList->RemoveAll();
    return;
    
Fail:
    return;
	
} // CIrDiscovery::HandleDiscoverComplete


//--------------------------------------------------------------------------------
//      ExtDiscoverStart
//--------------------------------------------------------------------------------
IrDAErr CIrDiscovery::ExtDiscoverStart(     UInt32                          numSlots)
					//ExtDiscoveryUserCallBackUPP       callback,
					//ExtDiscoveryBlock         *   userData        )
{
    TIrExtDiscoverRequest *request;

    XTRACE(kLogExtDiscStart, 0, numSlots);
	
    request = (TIrExtDiscoverRequest *)fIrDA->GrabEventBlock(0, 0);
    require(request, Fail);
    request->fEvent     = kIrDiscoverRequestEvent;
    request->fResult    = noErr;
    
    if( numSlots > kMaxDiscoverSlots )
	request->fNumSlots = kMaxDiscoverSlots;         // Limit the max slot count
    else
	request->fNumSlots = numSlots;
	
    //userData->count = 0;                              // Set it to a safe value
	
    request->fClient    = this;                         // Fill in the fields and
    //request->fCallBack    = callback;                     // send it off to myself
    //request->fUserData    = userData;                     
    
    this->EnqueueEvent(request);
    
    return noErr;

Fail:
    return errNoMemory;
    
} // CIrDiscovery::ExtDiscoverStart
    

//--------------------------------------------------------------------------------
//      DeleteDiscoveredDevicesList
//--------------------------------------------------------------------------------
void CIrDiscovery::DeleteDiscoveredDevicesList()
{
    XTRACE(kLogDiscoverDeleteList, 0, 0);
    
    if (fDiscoveredDevices) {
	while (true) {
	    TIrDscInfo *dscInfo = (TIrDscInfo *)fDiscoveredDevices->Last();
	    if (dscInfo != nil) {
		dscInfo->release();
		fDiscoveredDevices->RemoveLast();
	    }
	    else {
		break;
	    }
	}
    }

} // CIrDiscovery::DeleteDiscoveredDevicesList

//--------------------------------------------------------------------------------
//      PassiveDiscovery
//--------------------------------------------------------------------------------
void CIrDiscovery::PassiveDiscovery(TIrDscInfo * dscInfo)
{
    if (fDiscoveredDevices) {
//
//  Limit the list size to the predetermined size (16).
//
	SInt16  discCount = fDiscoveredDevices->Count();
	if (discCount > kMaxDiscoverListSize) {         // Trim it back so the new one
							// will fit.
	    SInt16 deleteCount = discCount - kMaxDiscoverListSize;
	    
	    while (deleteCount--) {                     
		TIrDscInfo* dscInfo = (TIrDscInfo *)fDiscoveredDevices->First();
		if (dscInfo != nil) {
		    dscInfo->release();
		    fDiscoveredDevices->RemoveFirst();
		}
	    }
	}
//
// Now scan the list for a duplicate entry.  The LAP address is used for the test
//
	TIrDscInfo *oldInfo;
	
	discCount = fDiscoveredDevices->Count();
	for( SInt16 index = 0; index < discCount; index++ ) {
	    oldInfo = ( TIrDscInfo *)fDiscoveredDevices->At(index);
	    if (oldInfo && (oldInfo->GetDeviceAddr() == dscInfo->GetDeviceAddr())) {
		fDiscoveredDevices->RemoveAt(index);
		oldInfo->release();
		break;			// found it in list, quit searching
	    }
	}
	fDiscoveredDevices->InsertFirst( dscInfo );         // jdg, was insertlast ...
    }
}

//--------------------------------------------------------------------------------
//      GetRemoteDeviceName
//--------------------------------------------------------------------------------
void CIrDiscovery::GetRemoteDeviceName( UInt32 lapAddr, UInt8 * name )
{
    TIrDscInfo  *   oldInfo;
    SInt16          discCount = fDiscoveredDevices->Count();
    Boolean         found = false;
    
    name[0] = 0;
    
    for( SInt16 index = 0; index < discCount; index++ ) {
	oldInfo = ( TIrDscInfo * )fDiscoveredDevices->At( index );
	if( oldInfo->GetDeviceAddr() == lapAddr ) {
	    oldInfo->GetNickname( name );
	    found = true;
	    break;
	}
    }
    if(!found)  {
	name[0] = 0;
    }
}
    

//--------------------------------------------------------------------------------
//      HandleExtDiscoverComplete
//--------------------------------------------------------------------------------
void CIrDiscovery::HandleExtDiscoverComplete( TIrDiscoverReply * reply )
{
    //TIrExtDiscoverReply           *   myReply     = ( TIrExtDiscoverReply * )reply;
    //ExtDiscoveryBlock         *   userResults = ( ExtDiscoveryBlock * )myReply->fUserData;
    //ExtDiscoveryUserCallBackUPP       callback    = ( ExtDiscoveryUserCallBackUPP )myReply->fCallBack;
    //TIrDscInfo                    *   dscInfo;
    
    //userResults->count = fDiscoveredDevices->GetArraySize();
    XTRACE(kIrExtDiscoverComplete, 0, fDiscoveredDevices->GetArraySize());      // Log the count.

    /*****
    if( userResults->count > kMaxDiscoverSlots )                // Check if the count is within
	userResults->count = 0;                                 // bounds.  If not then something
								// bad has happened.  Bag it.
    userResults->passiveDiscovery   = myReply->fPassiveDiscovery > 0;

    // Copy the results into the caller's block.
    if( userResults->count > 0 ) {
	for( UInt16 index = 0; index < userResults->count; index++ ) {
	
	    dscInfo = ( TIrDscInfo * )fDiscoveredDevices->At( index );
	    userResults->discoverResult[index].serviceHints         = dscInfo->GetServiceHints();
	    userResults->discoverResult[index].addr                 = dscInfo->GetDeviceAddr();
	    dscInfo->GetNickname( userResults->discoverResult[index].name );
	}
    }
    reply->fEvent = nil;                                        // Free the event
    
    CallExtDiscoveryCallBackProc( callback, myReply->fResult );
    ****/
    
    TIrEvent::ReleaseEventBlock(reply);         // release the event

} // CIrDiscovery::HandleMyDiscoverComplete

