/*
    File:       IrLMP.cpp

    Contains:   Implementation of the TIrLMP class

*/

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

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

enum IrLMPTraceCodes
{
    kCreate = 1,
    kInit,
    kLogFree,
    kUnexpectedEvent,

    kStnCntlReadyDiscoverEvent,
    kStnCntlReadyConnectEvent,
    kStnCntlReadyListenEvent,
    kStnCntlReadyDiscRequestEvent,
    kStnCntlReadyDiscReplyEvent,

    kStnCntlDiscoverDiscoverEvent,
    kStnCntlResolveDiscoverEvent,
    
    kLogLMPResetEntry,
    kLogLMPResetExit,
    kLogFillInLMPDUHeader,
    kLogStartOneSecTicker,
    
    kEnqueueEvent,
    kDequeueEventStart,
    kDequeueEventEnd
};

EventTraceCauseDesc IrLMPTraceEvents[] = {
    {kCreate,                       "irlmp: create obj="},
    {kInit,                         "irlmp: init, lmp="},
    {kLogFree,                      "irlmp: free"},
    {kUnexpectedEvent,              "irlmp: unexpected event"},

    {kStnCntlReadyDiscoverEvent,    "irlmp: Ready discover request"},
    {kStnCntlReadyConnectEvent,     "irlmp: Ready connect request"},
    {kStnCntlReadyListenEvent,      "irlmp: Ready listen request"},
    {kStnCntlReadyDiscRequestEvent, "irlmp: Ready disconnect request"},
    {kStnCntlReadyDiscReplyEvent,   "irlmp: Ready disconnect reply"},

    {kStnCntlDiscoverDiscoverEvent, "irlmp: Discv discover reply"},
    {kStnCntlResolveDiscoverEvent,  "irlmp: Reslv discover reply"},
    
    {kLogLMPResetEntry,             "irlmp: reset entry"},
    {kLogLMPResetExit,              "irlmp: reset finished"},
    {kLogFillInLMPDUHeader,         "irlmp: Fill In LMP PDU Header"},
    {kLogStartOneSecTicker,         "irlmp: StartOneSecTicker"},
    
    {kEnqueueEvent,                 "irlmp: Event Queued"},
    {kDequeueEventStart,            "irlmp: Event Start"},
    {kDequeueEventEnd,              "irlmp: Event End"}
};

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

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

#define GetLAP          (fIrDA->GetLAP())
#define GetLAPConn      (fIrDA->GetLAPConn())
#define GetDiscovery    (fIrDA->GetDiscovery())

//--------------------------------------------------------------------------------
#define super TIrStream
    OSDefineMetaClassAndStructors(TIrLMP, TIrStream);

//--------------------------------------------------------------------------------
//      TIrLMP
//--------------------------------------------------------------------------------
/*static*/
TIrLMP * TIrLMP::tIrLMP(TIrGlue* irda)
{
    TIrLMP *obj = new TIrLMP;
    
    XTRACE(kCreate, (int)obj >> 16, (short)obj);

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

void TIrLMP::free(void)
{
    XTRACE(kLogFree, (int)this >> 16, (short)this);
    
    if (fPendingRequests) {     // cleanup pending event list
	fPendingRequests->release();
	fPendingRequests = nil;
    }
    
    super::free();
}


//--------------------------------------------------------------------------------
//      Init
//--------------------------------------------------------------------------------
Boolean TIrLMP::Init(TIrGlue* irda)
{
    XTRACE(kInit, (int)this >> 16, (short)this);
    
    
    fState = kIrLMPReady;
    fTimerClients = 0;

    fNumAddrConflicts = 0;
    bzero(fAddrConflicts, sizeof(fAddrConflicts));

    fPendingRequests = nil;
		

#if (hasTracing > 0 && hasLMPTracing > 0)
    if (!super::Init(irda, IrLMPTraceEvents, kEnqueueEvent)) return false;
#else
    if (!super::Init(irda)) return false;
#endif
	
    
    fPendingRequests = CList::cList();      // make pending requests list
    require(fPendingRequests, Fail);

    return true;

Fail:

    return false;
    
} // TIrLMP::Init


//--------------------------------------------------------------------------------
//      Reset
//--------------------------------------------------------------------------------
void TIrLMP::Reset()
{
    XTRACE(kLogLMPResetEntry, 0, 0);
    // This is only intended for orderly reset (see IrGlue.c DisconnectComplete).

    fState = kIrLMPReady;
    fTimerClients = 0;

    if (GetLAPConn != nil) {
	GetLAPConn->Reset();
    }
    XTRACE(kLogLMPResetExit, 0, 0);

} // TIrLMP::Reset



//--------------------------------------------------------------------------------
//      NextState
//
//      Station Control state transitions (IrLMP 3.5.2.3.1)
//      Only supports Ready, Discover, and Resolve Address states
//--------------------------------------------------------------------------------
void TIrLMP::NextState(ULong event)
{
    switch (fState) {
	case kIrLMPReady:
	    HandleReadyStateEvent(event);
	    break;

	case kIrLMPDiscover:
	    HandleDiscoverStateEvent(event);
	    break;

	case kIrLMPResolveAddress:
	    HandleResolveAddressStateEvent(event);
	    break;

	default:
	    DebugLog("TIrLMP::NextState: bad fState");
	    break;
    }

} // TIrLMP::NextState


//--------------------------------------------------------------------------------
//      HandleReadyStateEvent
//
//      Station Control Ready state transitions (IrLMP 3.5.2.3.2)
//      Not all states are supported (see above comment)
//--------------------------------------------------------------------------------
void TIrLMP::HandleReadyStateEvent(ULong event)
{
    switch (event) {
	case kIrDiscoverRequestEvent:
	    {
		XTRACE(kStnCntlReadyDiscoverEvent, 0, 0);
		TIrDiscoverRequest* discoverRequest = (TIrDiscoverRequest*)GetCurrentEvent();
		discoverRequest->fConflictDevAddr = kIrLAPBroadcastDevAddr;
		fState = kIrLMPDiscover;
		GetLAP->EnqueueEvent(discoverRequest);
	    }
	    break;

	case kIrConnectRequestEvent:
	    XTRACE(kStnCntlReadyConnectEvent, 0, 0);
	    // Forward connect request to LAPConn
	    GetLAPConn->EnqueueEvent(GetCurrentEvent());
	    break;

	case kIrListenRequestEvent:
	    XTRACE(kStnCntlReadyListenEvent, 0, 0);
	    // Forward listen request to LAPConn
	    GetLAPConn->EnqueueEvent(GetCurrentEvent());
	    break;

	case kIrConnectReplyEvent:
	case kIrListenReplyEvent:
	case kIrGetDataRequestEvent:
	case kIrCancelGetRequestEvent:
	    // Forward these to IrLAPConn
	    GetLAPConn->EnqueueEvent(GetCurrentEvent());
	    break;

	case kIrPutDataRequestEvent:
	case kIrCancelPutRequestEvent:
	    // Forward these to IrLAP
	    GetLAP->EnqueueEvent(GetCurrentEvent());
	    break;

	case kIrDisconnectRequestEvent:
	    // If request came from IrDA (IrGlue) pass it directly to IrLAP
	    //if (((TIrDisconnectEvent*)GetCurrentEvent())->fLSAPConn == nil) {
	    //  XTRACE(kStnCntlReadyDiscRequestEvent, 0, 0);
	    //  GetLAP->EnqueueEvent(GetCurrentEvent());
	    //}

	    // else (request came from an LSAPConn), pass it on to IrLAPConn
	    //else {
		XTRACE(kStnCntlReadyDiscRequestEvent, 1, 0);
		GetLAPConn->EnqueueEvent(GetCurrentEvent());
	    //}
	    break;

	case kIrDisconnectReplyEvent:
	    // If request came from IrDA (IrGlue) pass it back
	    //if (((TIrDisconnectEvent*)GetCurrentEvent())->fLSAPConn == nil) {
	    //  XTRACE(kStnCntlReadyDiscReplyEvent, 0, 0);
	    //  fIrDA->EnqueueEvent(GetCurrentEvent());
	    //}

	    // else (request came from an LSAPConn), pass it on to IrLAPConn
	    //else {
		XTRACE(kStnCntlReadyDiscReplyEvent, 1, 0);
		GetLAPConn->EnqueueEvent(GetCurrentEvent());
	    //}
	    break;
	    
	case kIrDiscoverReplyEvent:                 // jdg: this can happen now if we're doing
	    HandleDiscoverStateEvent(event);        // lots of discovers while connected and
	    break;                                  // then get an async disconnect (lmp reset)

	default:
	    XTRACE(kUnexpectedEvent, fState, event);
	    DebugLog("TIrLMP::HandleReadyStateEvent: bad event");
	    break;
    }

} // TIrLMP::HandleReadyStateEvent


//--------------------------------------------------------------------------------
//      HandleDiscoverStateEvent
//--------------------------------------------------------------------------------
void TIrLMP::HandleDiscoverStateEvent(ULong event)
{
    switch (event) {
	case kIrDiscoverReplyEvent:
	    {
		TIrDiscoverReply* discoverReply = (TIrDiscoverReply*)GetCurrentEvent();
		// If returned with no error and addr conflicts, do addr conflict resolution
		if ((discoverReply->fResult == noErr) &&
		    (!discoverReply->fPassiveDiscovery) &&
		    (AddrConflicts(discoverReply->fDiscoveredDevices, true))) {
		    // Initiate the first of the resolution calls
		    XTRACE(kStnCntlDiscoverDiscoverEvent, 0, 1);
		    XASSERT(fNumAddrConflicts > 0);
		    // Reuse the reply for the request to resolve the address conflict.
		    discoverReply->fEvent = kIrDiscoverRequestEvent;
		    discoverReply->fConflictDevAddr = fAddrConflicts[--fNumAddrConflicts];
		    fState = kIrLMPResolveAddress;
		    GetLAP->EnqueueEvent(discoverReply);
		}
		else {
		    // Either some error or no addr conflicts so discovery is complete
		    XTRACE(kStnCntlDiscoverDiscoverEvent, 0, 2);
		    fState = kIrLMPReady;
		    GetDiscovery->EnqueueEvent(discoverReply);
		    // JDG: queue up any pending requests for us (via normal queue, sigh)
		    if (fPendingRequests && !fPendingRequests->Empty()) {
			CListIterator *iter = CListIterator::cListIterator(fPendingRequests);
			for (TIrEvent* request = (TIrEvent*)iter->FirstItem();
			     iter->More(); request = (TIrEvent*)iter->NextItem()) {
				XTRACE(kStnCntlDiscoverDiscoverEvent, request->fEvent, 3);
				// Send the reply to ourselves (sigh)
				this->EnqueueEvent(request);
			}
			// now that we've processed the list, empty it (backwards first for speed)
			while (!fPendingRequests->Empty())
			    fPendingRequests->RemoveLast();     // remove last til no more left
			iter->release();
		    }
		}
	    }
	    break;

	//case kIrDisconnectRequestEvent:
	//  // This is only expected from IrDA (IrGlue) as all discovering/address resolution
	//  // should be done before any connections are established.
	//  XASSERT(((TIrDisconnectRequest*)GetCurrentEvent())->fLSAPConn == nil);
	//  // Pass request on to IrLAP
	//  GetLAP->EnqueueEvent(GetCurrentEvent());
	//  break;

	//case kIrDisconnectReplyEvent:
	//  // Pass reply back to IrDA
	//  GetDiscovery->EnqueueEvent(GetCurrentEvent());
	//  break;

	case kIrConnectReplyEvent:          // JDG ADDED THESE for listen/disconnect/discover
	case kIrListenReplyEvent:
	case kIrGetDataRequestEvent:
	case kIrCancelGetRequestEvent:
	    // Forward these to IrLAPConn
	    GetLAPConn->EnqueueEvent(GetCurrentEvent());
	    break;

	case kIrConnectRequestEvent:            // jdg hacking
	case kIrListenRequestEvent:
	case kIrDisconnectRequestEvent:
	case kIrPutDataRequestEvent:
	case kIrCancelPutRequestEvent:
	    // save until we get back to ready state
	    fPendingRequests->InsertLast(GetCurrentEvent());
	    break;

	default:
	    XTRACE(kUnexpectedEvent, fState, event);
	    DebugLog("TIrLMP::HandleDiscoverStateEvent: bad event");
	    break;
    }

} // TIrLMP::HandleDiscoverStateEvent


//--------------------------------------------------------------------------------
//      HandleResolveAddressStateEvent
//--------------------------------------------------------------------------------
void TIrLMP::HandleResolveAddressStateEvent(ULong event)
{
    switch (event) {
	case kIrDiscoverReplyEvent:
	    {
		XTRACE(kStnCntlResolveDiscoverEvent, 0, 0);
		TIrDiscoverReply* discoverReply = (TIrDiscoverReply*)GetCurrentEvent();

		// Any addr conflicts in current set of responses?  If so this rtn removes them
		(void)AddrConflicts(discoverReply->fDiscoveredDevices, false);

		// If an error was returned or we're finished resolving the original conflicts...
		if ((discoverReply->fResult != noErr) || (fNumAddrConflicts == 0)) {
		    fState = kIrLMPReady;
		    GetDiscovery->EnqueueEvent(discoverReply);
		}

		// Still some more conflicting addresses to resolve
		else {
		    XASSERT(fNumAddrConflicts > 0);
		    // Reuse the reply for the request to resolve the address conflict.
		    discoverReply->fConflictDevAddr = fAddrConflicts[--fNumAddrConflicts];
		    GetLAP->EnqueueEvent(discoverReply);
		}
	    }
	    break;

	case kIrDisconnectRequestEvent:
	    // This is only expected from IrDA (IrGlue) as all discovering/address resolution
	    // should be done before any connections are established.
	    XASSERT(((TIrDisconnectRequest*)GetCurrentEvent())->fLSAPConn == nil);
	    // Pass request on to IrLAP
	    GetLAP->EnqueueEvent(GetCurrentEvent());
	    break;

	case kIrDisconnectReplyEvent:
	    // Pass reply back to IrDA
	    GetDiscovery->EnqueueEvent(GetCurrentEvent());
	    break;

	default:
	    XTRACE(kUnexpectedEvent, fState, event);
	    DebugLog("TIrLMP::HandleResolveAddressStateEvent: bad event");
	    break;
    }

} // TIrLMP::HandleResolveAddressStateEvent


//================================ Helper methods ================================


//--------------------------------------------------------------------------------
//      AddrConflicts
//--------------------------------------------------------------------------------
Boolean TIrLMP::AddrConflicts(CList* discoveredDevices, Boolean setAddrConflicts)
{
    // This looks for any address conflicts between my address and all of the addresses
    // represented in the discoveredDevices list.  Any discoveredDevices with
    // conflicting addresses are removed from the list of discoveredDevices.
    // If setAddrConflicts is true then the conflicting addresses are saved in
    // fAddrConflicts, which is then used for address resolution.

    ULong addrToCheck;
    ULong uniqueCount = 0;
    Boolean thisConflicts;
    Boolean conflicts = false;
    ULong uniqueAddrs[kMaxReturnedAddrs+1];     // Extra 1 for my address

    // Initialize unique addrs to my address to weed out conlicts with my address too.
    uniqueAddrs[uniqueCount++] = GetLAP->GetMyDevAddr();

    // Init addr conflict fields if setting addr conflict info
    if (setAddrConflicts) {
	fNumAddrConflicts = 0;
    }

    // No conflicts if no entries returned
    if (discoveredDevices->GetArraySize() > 0) {
	// Go backwards so entries don't shift down (and also faster if entry removed from end)
	for (FastInt index1 = discoveredDevices->GetArraySize() - 1; index1 >= 0 ; index1--) {
	    TIrDscInfo* discoveryInfo = (TIrDscInfo*)discoveredDevices->At(index1);
	    // Now check to see if this entries addr is already in the uniqueAddrs list
	    thisConflicts = false;
	    addrToCheck = discoveryInfo->GetDeviceAddr();
	    for (ULong index2 = 0; index2 < uniqueCount; index2++) {
		if (addrToCheck == uniqueAddrs[index2]) {
		    // There is an address conflict, maybe add to conflicts, remove it
		    conflicts = thisConflicts = true;
		    if (setAddrConflicts) {
			if (fNumAddrConflicts < kMaxAddrConflicts) {
			    fAddrConflicts[fNumAddrConflicts++] = addrToCheck;
			}
		    }
		    discoveredDevices->RemoveAt(index1);
		    discoveryInfo->release();
		    break;
		}
	    }
	    // If no conflicts then this address is also unique, add it to the unique addr list.
	    if (!thisConflicts) {
		if (uniqueCount < (kMaxReturnedAddrs+1)) {
		    uniqueAddrs[uniqueCount++] = addrToCheck;
		}
		else {
		    DebugLog("TIrLMP::AddrConflicts: too many unique addrs");
		}
	    }
	}
    }

    return conflicts;

} // TIrLMP::AddrConflicts


//--------------------------------------------------------------------------------
//      Demultiplexor
//--------------------------------------------------------------------------------
void TIrLMP::Demultiplexor(CBufferSegment* inputBuffer)
{
    // All of the real work is done in IrLAPConn - pass it on
    GetLAPConn->Demultiplexor(inputBuffer);

} // TIrLMP::Demultiplexor


//--------------------------------------------------------------------------------
//      FillInLMPDUHeader
//--------------------------------------------------------------------------------
ULong TIrLMP::FillInLMPDUHeader(TIrPutRequest* putRequest, UByte* buffer)
{
    int lapconn = (int)GetLAPConn;
    
    XTRACE(kLogFillInLMPDUHeader, lapconn >> 16, (short)lapconn);
    // All of the real work is done in IrLAPConn - pass it on
    return GetLAPConn->FillInLMPDUHeader(putRequest, buffer);

} // TIrLMP::FillInLMPDUHeader


//--------------------------------------------------------------------------------
//      StartOneSecTicker
//--------------------------------------------------------------------------------
void TIrLMP::StartOneSecTicker()
{
    XTRACE(kLogStartOneSecTicker, 0, fTimerClients);
    
    // NOTE: This is exclusively used for the LSAPConn connect watchdog timer(s)
    // I'm trying to keep it small and simple.  If there are other timers that
    // can take advantage of this 1 second tick counter then they may be able to
    // be piggy-backed on top of this timer.

    if (fTimerClients++ == 0) {
	fIrDA->StartTimer(kTimer_LMP, 1 * kSeconds, kIrConnWatchdogExpiredEvent);
    }

} // TIrLMP::StartOneSecTicker


//--------------------------------------------------------------------------------
//      StopOneSecTicker
//--------------------------------------------------------------------------------
void TIrLMP::StopOneSecTicker()
{
    // NOTE: This is exclusively used for the LSAPConn connect watchdog timer(s)

    if (fTimerClients > 0) {
	if (--fTimerClients == 0) {
	    fIrDA->StopTimer(kTimer_LMP);
	}
    }

} // TIrLMP::StopOneSecTicker


//--------------------------------------------------------------------------------
//      TimerComplete
//--------------------------------------------------------------------------------
void TIrLMP::TimerComplete(ULong refCon)
{
    // All of the real work is done in IrLAPConn - pass it on
    GetLAPConn->TimerComplete(refCon);

    // If one sec ticker is still active, retrigger the ticker
    if (refCon == kIrConnWatchdogExpiredEvent && fTimerClients > 0) {
	fIrDA->StartTimer(kTimer_LMP, 1 * kSeconds, kIrConnWatchdogExpiredEvent);
    }

} // TIrLMP::TimerComplete

