/*
    File:       IrComm.cpp

    Contains:   IrCOMM protocol layer

*/

#include "IrComm.h"
#include "IrDAComm.h"
#include "IrGlue.h"
#include "CBufferSegment.h"
#include "ttppdu.h"
#include "IrDALog.h"

#pragma mark -- Globals

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

enum TraceCodes
{
    kLogNew = 1,
    kLogFree,
    kLogInit,
    kLogTxAvail,
    kLogWrite,
    kLogReturnCredit,
    kLogBackEnable,
    
    kLogTryConnect,
    kLogListen,
    kLogDisconnect,
    kLogDiscoverComplete,
    kLogLkupDone,
    kLogConnectConfirm,
    kLogDisconnected,
    
    kLogConnectIndication,
    kLogAcceptDone,
    
    kLogTTPDataRead,
    kLogTTPDataRead2,
    
    kLogDataWrite,
    kLogDataRead
};

static
EventTraceCauseDesc gTraceEvents[] = {
    {kLogNew,               "IrComm: new, obj="},
    {kLogFree,              "IrComm: free, obj="},
    {kLogInit,              "IrComm: init, obj="},
    {kLogTxAvail,           "IrComm: tx available, returning="},
    {kLogWrite,             "IrComm: write, count="},
    {kLogReturnCredit,      "IrComm: return credit. bytes consumed, count="},
    {kLogBackEnable,        "IrComm: back eable"},
    
    {kLogTryConnect,        "IrComm: try to connect"},
    {kLogListen,            "IrComm: listen"},
    {kLogDisconnect,        "IrComm: disconnect request"},
    {kLogDiscoverComplete,  "IrComm: discover complete, numfound=, result="},
    {kLogLkupDone,          "IrComm: lookup done, result=, peerid="},
    {kLogConnectConfirm,    "IrComm: connect confirm, result="},
    {kLogDisconnected,      "IrComm: disconnected.  reason="},
    
    {kLogConnectIndication, "IrComm: listen complete"},
    {kLogAcceptDone,        "IrComm: accept done"},
    
    {kLogTTPDataRead,           "IrComm: read complete, result=, len="},
    {kLogTTPDataRead2,          "IrComm: read ctlLen=, dataLen="},

    {kLogDataWrite,         "IrComm: write data"},
    {kLogDataRead,          "IrComm: read data"}
};

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

#pragma mark -- Prototypes

void    InitSizeQueue(void);                    // init the silly size queue
void    AddPacketSize(short length);            // add a packet's size to the fifo
short   FirstPacketSize(void);                  // return size (remaining) from the first packet in the q
void    ShrinkFirstPacketSize(short length);    // mark a few bytes as consumed from the first packet in q
void    NukeFirstPacketSize(void);              // first packet in the q has been consumed

extern IrDAErr Dequeue(QElemPtr qElement, QHdrPtr qHeader);     // MOVE these too **************
extern IrDAErr Enqueue(QElemPtr qElement, QHdrPtr qHeader);

#if (hasTracing > 0 && hasIrCommTracing > 1)
void IrDALogData(int msg, UInt8 *buf, int count);       // log data into irdalog
#define LOGDATA(msg, buf, count)    IrDALogData(msg, buf, count)
#else
#define LOGDATA(msg, buf, count) (void)0)
#endif


#define super TTinyTP
    OSDefineMetaClassAndStructors(IrComm, TTinyTP);

/*static*/
IrComm *
IrComm::irComm(TIrGlue *irda, IrDAComm *irdacomm)
{
    IrComm *obj = new IrComm;
    
    XTRACE(kLogNew, (int)obj >> 16, (short)obj);
    
    if (obj && !obj->Init(irda, irdacomm)) {
	obj->release();
	obj = nil;
    }
    
    return obj;
}

void
IrComm::free(void)
{
    XTRACE(kLogFree, (int)this >>16, (short)this);
    
    ///xxx;

    super::free();
}

Boolean
IrComm::Init(TIrGlue *irda, IrDAComm *irdacomm)
{
    XTRACE(kLogInit, (int)this >> 16, (short)this);
    UInt8 *classname = (UInt8 *)"IrDA:IrCOMM";  // same lsap for client and server!
    
    fIrDAComm = irdacomm;
    fConnected = false;
    fMaxPacketSize = 2048;      // set for real when connected
    
    InitSizeQueue();
    
    if (!super::TTPInitialize(
		    irda,
		    (UInt32)kTinyTPCredit,                  // credits (in packets)
		    (UInt32)kAssignDynamicLSAPId,           // any old lsap id for now (will register it later?)
		    classname,                              // unused classname (real one done later)
		    (ULong)kDevInfoHintIrCOMM)) return false;       // init tinytp, bail if fails
    
    ////
    return true;
}


// Return the number of bytes we can transmit without starting
// to worry about flow control.

UInt32
IrComm::TxBufferAvailable(void)
{
    if (fConnected) {
	UInt32  count;
	count = TTPXmitQueueSize(fMaxPacketSize) * fMaxPacketSize;
	XTRACE(kLogTxAvail, count >> 16, (short)count);
	return count;
    }
    XTRACE(kLogTxAvail, 0xffff, 0xffff);
    return 30*1024;         // anything big, we just going to flush it (race condition?)
}

//
// break the write up into packet size chucks
// and send them off to tinytp for writing
//
UInt32
IrComm::Write(UInt8 *buf, UInt32 length)
{
    UInt32 written = 0;
    
    XTRACE(kLogWrite, length >> 16, (short)length);
    
    require(buf, Done);
    require(length > 0, Done);

    while (length > 0) {
	CBufferSegment *pkt;
	int rc, len;
	
	require(TTPXmitQueueSize(fMaxPacketSize) > 0, Done);    // should have at least room for one more write
	
	len = Min(length, fMaxPacketSize);
	require(len > 0, Done);
	require(len < 2047, Done);  // sanity
	
	pkt = BufAlloc(len + 1);
	require(pkt, Done);
		
	rc = pkt->Put(0);           // no ircomm control info in this packet
	check(rc == 0);             // should return the byte we're putting
	
	rc = pkt->Putn(buf, len);
	require(rc == len, Done);   // should return count of bytes moved (will truncate to fit in buffer)
	
	BufHideRest(pkt);           // hide rest of the buffer (i.e. set the write length)
	
	DoDataRequest(pkt);         // send to tinytp for xmit
	
	BufFree(pkt);               // tinytp copies the data from us, free our copy (keep the cbuf?)
	
	buf     += len;
	written += len;
	length  -= len;
    }
    //IOLog("IrComm: wrote %ld bytes\n", written);
    
Done:
    return written;
}

//
// our tty client has consumed some bytes, convert the count into
// a packet count and extend that many credits back to our ttp peer.
//
void
IrComm::ReturnCredit(UInt32 bytecount)
{
    XTRACE(kLogReturnCredit, bytecount >> 16, (short)bytecount);
    short x;
    
    while (bytecount > 0) {         // loop until we've "processed" what's been returned
	x = FirstPacketSize();      // get size of first packet in the fifo (what's left anyway)
	require(x > 0, Fail);       // sanity
	
	if (bytecount >= (UInt32)x) {       // if we've consumed all of the first packet
	    NukeFirstPacketSize();          // take it out of the fifo
	    bytecount -= x;                 // adjust amount consumed
	    TTPRxDone();                    // the flow control work: extend tinytp credit by one -- we've consumed a pkt!
	}
	else {                          // first packet size is more than we've consumed
	    ShrinkFirstPacketSize(bytecount);
	    bytecount = 0;              // and we're done
	}
    }
Fail:
    return;
}

void
IrComm::TryConnect(int slots)
{
    XTRACE(kLogTryConnect, 0, fConnected);
    
    if (fConnected == false)
	DoDiscoverRequest(slots);       // look for peer (1,6,8,16 legal slot counts)
}

void
IrComm::Listen(void)
{
    CBufferSegment *buf;

    XTRACE(kLogListen, 0, fConnected);
    require(fConnected == false, Fail);
    
    buf = BufAlloc(1000);       // for initial connect message data
    require(buf, Fail);
    
    DoListenRequest(buf);
    
Fail:
    return;
}

void
IrComm::Disconnect(void)
{
    int review_disconnect_logic;
    
    XTRACE(kLogDisconnect, 0, 0);
    
    // review ... 
    DoDisconnectRequest(nil);
}

#pragma mark ==== TinyTP Callbacks ====
    
void IrComm::TTPDiscoverComplete(int numFound, IrDAErr result)
{
    int peer_index;                     // init -1 to keep from connnecting to non-ircomm hint devices, else init to 0
    Boolean found_ircomm_peer = false;
    
    XTRACE(kLogDiscoverComplete, numFound, result);
    
#if (hasTracing > 0 && hasIrCommTracing > 1)
    peer_index = 0;                         // if debug, allow connect attempt to first device if no ircomm 
    DebugLog("numFound=%d, result=%ld", numFound, result);
#else
    peer_index = -1;                        // if normal build, insist on an ircomm device before connecting
#endif
    if (numFound > 0) {
	int i;
#if (hasTracing > 0 && hasIrCommTracing > 1)
	for (i = 0 ; i < numFound; i++) {       // log all the discovered peers -- debugging only
	    DebugLog("discovered at %ld nicname '%s' hints 0x%lx",
			fDiscoverInfo[i].addr,
			fDiscoverInfo[i].name,
			fDiscoverInfo[i].serviceHints);
	}
#endif
	for (i = 0 ; i < numFound; i++) {                       // find a peer with the ircomm hint bit
	    if (fDiscoverInfo[i].serviceHints & kDevInfoHintIrCOMM) {   // ircomm hint!
		peer_index = i;
		found_ircomm_peer = true;
		break;
	    }
	}
	
	/* now try and do an lsap lookup if our peer had the ircomm hint bit set */
	/* or if debug build, then maybe try to connect to an hp netbeamir */
	if (peer_index >= 0) {          // if we found an ircomm peer, do ias query
	    IrDAErr err;
	    UInt8 *classname = (UInt8 *)"IrDA:IrCOMM";
	    UInt8 *attrname = (UInt8 *)"IrDA:TinyTP:LsapSel";
#if (hasTracing > 0 && hasIrCommTracing > 0)
	    if (found_ircomm_peer == false) classname = (UInt8 *)"HP-BDP";          // jdg's debugging
#endif            
	    fPeerAddress = fDiscoverInfo[peer_index].addr;
	    err = LSAPLookup(classname, attrname, fDiscoverInfo[peer_index].addr);
	    ncheck(err);
	    if (err == noErr)       // success
		return;
	}
    }
    
    // Fall through with any error
    if (fIrDAComm)
	fIrDAComm->ConnectionStatus(false);     // we're disconnected
    
}

void IrComm::TTPLookupComplete(IrDAErr result,UInt32 peerLSAPId)
{
    XTRACE(kLogLkupDone, result, peerLSAPId);

#if (hasTracing > 0 && hasIrCommTracing > 0)
    DebugLog("lookup complete, result=%ld, lsap=%ld", result, peerLSAPId);
#endif
    if (result == noErr && peerLSAPId > 0) {
	DoConnectRequest (              // connect to a remote peer
	    fPeerAddress,                   // peer address
	    peerLSAPId,                     // remote LSAP id
	    nil, 0, nil);                   // requested QoS (unimpld), no reassembly, no user level data
    }
    else {
	fConnected = false;
	if (fIrDAComm)
	    fIrDAComm->ConnectionStatus(false);     // we're disconnected
    }
}

void
IrComm::TTPConnectIndication (      // Listen complete
	IrDAErr result,
	TTPSAP  SAP,                        // calling TTP SAP
	TIrQOS *ourQOS,                     // our QoS (post negotiation)
	TIrQOS *peerQOS,                    // peer QoS (post negotiation)
	int     MaxSduSize,                 // calling MaxSduSize
	TTPBuf  *UserData)                  // calling UserData
{
    XTRACE(kLogConnectIndication, 0, result);
    
    require(UserData, Fail);            // sanity, we allocated this in listen, should always be there
    BufFree(UserData);                  // returned with data from connect msg.  should look at it here.
    
    if (result == noErr) {
	if (peerQOS)    fMaxPacketSize = peerQOS->GetDataSize() - 5;
	else            fMaxPacketSize = 2048;  // never happens I hope
	
	DoConnectResponse (             // reply to a connect indication (i.e. accept call)
		SAP,                    // calling TTP SAP
		0,                      // ircomm doesn't do tinytp fragmentation (silly)
		nil);                   // called UserData
    }
    else if (fIrDAComm)
	    fIrDAComm->ConnectionStatus(false);
Fail:
    return;
}


void
IrComm::TTPConnectConfirm (     // Connect complete
	TTPSAP  SAP,                        // called TTP SAP
	TIrQOS *ourQOS,                     // our QoS (post negotiation)
	TIrQOS *peerQOS,                    // peer QoS (post negotiation)
	int     MaxSduSize,                 // called MaxSduSize
	TTPBuf  *UserData)                  // called UserData
{
    XTRACE(kLogConnectConfirm, 0, 0);
    
    check(ourQOS);
    check(peerQOS);
    if (peerQOS) {
	fMaxPacketSize = peerQOS->GetDataSize() - 5;
	//IOLog("ircomm - max packet size read as %d\n", fMaxPacketSize);
    }
    else {
	fMaxPacketSize = 2048;  // never happens I hope
	//IOLog("ircomm - qos nil, max packet set to %d\n", fMaxPacketSize);
    }
    
    if (fIrDAComm) {
	fConnected = true;
	fIrDAComm->ConnectionStatus(true);
    }
}
    

void
IrComm::TTPDisconnectIndication (       // Disconnect complete
	int     reason,
	TTPBuf  *UserData)
{
    XTRACE(kLogDisconnected, 0, reason);
    fConnected = false;
    if (fIrDAComm)
	fIrDAComm->ConnectionStatus(false);     // we're disconnected
}


void
IrComm::TTPDataIndication(TTPBuf *userdata, TTP_Read_Status status)
{
    UInt32 ctlLen, dataLen, count;
    UInt8 *ctlBuffer, *dataBuffer, *buf;

    require(userdata, Fail);
    nrequire(status, Fail);
    
    XTRACE(kLogTTPDataRead, status, BufSize(userdata));

    count = BufSize(userdata);
    buf   = BufBase(userdata);
    
    //IOLog("IrComm got %ld bytes\n", count);

//  LOGDATA(kLogDataRead, buf, count);      // send all data to irdalog if tracing > 1
    
    ctlLen    = *buf;                       // first byte is length of control data
    ctlBuffer = buf + 1;                    // control data (if any) after ctl length

    dataLen     = count - ctlLen - 1;       // data is packet - ctlLen byte - ctl buffer
    dataBuffer  = ctlBuffer + ctlLen;       // data follows control buffer
    
    XTRACE(kLogTTPDataRead2, ctlLen, dataLen);
    //
    // parse control buffer here (eventually)
    //
    if (dataLen) {                                  // if any data to pass up
	AddPacketSize(dataLen);                             // keep track of packet lens for flow control
	if (fIrDAComm)
	    fIrDAComm->IrCommDataRead(dataBuffer, dataLen);     // send data to IrDAComm and pseudo tty
    } 
	else TTPRxDone();
    
    BufFree(userdata);                      // release the cbuffer
    
Fail:
    return;
}

void
IrComm::TTPUDataIndication(TTPBuf *UserData)
{
    // crash, this isn't impld
}
    

void 
IrComm::TTPAcceptDoneIndication(IrDAErr result)     // connection fully opened
{
    XTRACE(kLogAcceptDone, 0, result);
    
    fConnected = (result == noErr);

    if (fIrDAComm)
	fIrDAComm->ConnectionStatus(result == noErr);
}           


void
IrComm::TTPBackEnable(void)         // more tinytp buffers are available
{
    XTRACE(kLogBackEnable, 0, 0);
    if (fIrDAComm)
	fIrDAComm->BackEnable();
}

#pragma mark ==== Really silly packet size FIFO ====

//
// This is really silly, but we need to keep track
// of packet sizes in a race-condition safe manner.
// at least if we want to try and do flow control
// in a moderately sane manner (todo: explore insanity)
//
#define kSizeQueueLength    (kTinyTPCredit+2)       // if more, then should never ever run out
QElem   gSizeQueueArray[kSizeQueueLength];          // our queue elements fit in std QElem
QHdr    gAvailSizeQueue;            // the free list
QHdr    gSizeQueue;                 // the "packet size" fifo

void
InitSizeQueue(void)
{
    int i;
    
    gAvailSizeQueue.qFlags = gSizeQueue.qFlags = 0;
    gAvailSizeQueue.qHead  = gSizeQueue.qHead = nil;
    gAvailSizeQueue.qTail  = gSizeQueue.qTail = nil;
    for (i = 0 ; i < kSizeQueueLength; i++) {
	Enqueue(&gSizeQueueArray[i], &gAvailSizeQueue);
    }
}

//
// caller has a new packet, the size of which we
// want to save in a fifo for flow control.
// Can you say "excessively complex"?
//
void
AddPacketSize(short length)
{
    QElemPtr    q;
    IrDAErr     err;
    
    q = gAvailSizeQueue.qHead;
    require(q, Fail);                   // really shouldn't happen
    
    err = Dequeue(q, &gAvailSizeQueue);
    nrequire(err, Fail);
	
    q->qLink = nil;
    q->qType = 123;
    q->qData[0] = length;
    Enqueue(q, &gSizeQueue);

Fail:
    return;
}

//
// caller wants to know the size of the first packet 
//
short
FirstPacketSize(void)
{
    QElemPtr    q;
    q = gSizeQueue.qHead;
    if (q == nil) return 0;
    return q->qData[0];
}

//
// caller has consumed some, but not all, of the 
// data in the first packet.  adjust the length
//
void
ShrinkFirstPacketSize(short length)
{
    QElemPtr    q;
    q = gSizeQueue.qHead;
    require(q, Fail);
    q->qData[0] -= length;
    
Fail:
    return;
}

// caller has consumed first packet (at least)
// so pull it off the size queue and add it 
// back to the free list
void
NukeFirstPacketSize(void)
{
    QElemPtr    q;
    IrDAErr     err;
    
    q = gSizeQueue.qHead;
    require(q, Fail);
    
    err = Dequeue(q, &gSizeQueue);
    nrequire(err, Fail);
    
    Enqueue(q, &gAvailSizeQueue);
    
Fail:   
    return;
}


#if (hasTracing > 0 && hasIrCommTracing > 1)

void IrDALogData(int msg, UInt8 *buf, int count)    // log data into irdalog
{
    UInt32  x = 0;
    int     i = 0;      // count of bytes in x
    
    while (count-- > 0) {
	x = x << 8;
	x |= *buf++;
	if (++i == 4) {
	    XTRACE(msg, x >> 16, (short)x);
	    i = 0;
	    x = 0;
	}
    }
    if (i) XTRACE(msg, x >> 16, (short)x);
}
    
#endif  
    
