/*
    File:       CIrDevice.cpp

    Contains:   Generic interface to IR hardware.

*/

#include "CIrDevice.h"
#include "IrGlue.h"
#include "IrLAP.h"
#include "AppleIrDA.h"
#include "IrDALog.h"

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

enum IrLogCodes
{
    kLogNew = 1,
    kLogFree,
    kLogInit,
    kLogStart,
    kLogStop,
    
    kLogChangeSpeed,
    kLogSetSpeedComplete,
    
    kLogReadComplete,
    kLogPacketDropped,
    
    kLogStartXmit,
    kLogStartXmitPutBuf,
    kLogStartXmitCtlBuf,
    kLogStartXmitCtlSize,
    kLogStartXmitDataBuf,
    kLogStartXmitDataSize,
    kLogStartXmitFrame,
    kLogStartXmitLength,
    kLogStartXmitData,
    kLogTransmitComplete,
    kLogStartXmitError,
    
    kLogSetAddress,
    kLogNotOurAddress,
    
};

static
EventTraceCauseDesc IrLogEvents[] = {
    {kLogNew,                   "IrDevice: new, obj="},
    {kLogFree,                  "IrDevice: free, obj="},
    {kLogInit,                  "IrDevice: init, obj="},
    {kLogStart,                 "IrDevice: start"},
    {kLogStop,                  "IrDevice: stop"},
    
    {kLogChangeSpeed,           "IrDevice: change speed, new="},
    {kLogSetSpeedComplete,      "IrDevice: set speed complete, worked="},
      
    {kLogReadComplete,          "IrDevice: read complete, len=,data="},
    {kLogPacketDropped,         "IrDevice: packet dropped.  no read pending from lap"},
    
    {kLogStartXmit,             "IrDevice: start xmit"},
    {kLogStartXmitPutBuf,       "IrDevice: xmit put buffer"},
    {kLogStartXmitCtlBuf,       "IrDevice: xmit ctl buffer"},
    {kLogStartXmitCtlSize,      "IrDevice: xmit ctl length"},
    {kLogStartXmitDataBuf,      "IrDevice: xmit data buffer"},
    {kLogStartXmitDataSize,     "IrDevice: xmit data length"},
    {kLogStartXmitFrame,        "IrDevice: xmit frame buffer"},
    {kLogStartXmitLength,       "IrDevice: xmit packet length"},
    {kLogStartXmitData,         "IrDevice: xmit packet data"},
    {kLogTransmitComplete,      "IrDevice: transmit complete"},
    {kLogStartXmitError,        "IrDevice: start xmit logic ERROR. fXmitting="},
    
    {kLogSetAddress,            "IrDevice: set lap address"},
    {kLogNotOurAddress,         "IrDevice: not our lap address, in=, our="},
    
};

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

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

#define super OSObject
    OSDefineMetaClassAndStructors(CIrDevice, OSObject);

/*static*/
CIrDevice *
CIrDevice::cIrDevice(TIrGlue *irda, AppleIrDASerial *driver)
{
    CIrDevice *obj = new CIrDevice;
    XTRACE(kLogNew, (int)obj >> 16, (short)obj);
    if (obj && !obj->Init(irda, driver)) {
	obj->release();
	obj = nil;
    }
    return obj;
}

void
CIrDevice::free(void)
{
    XTRACE(kLogFree, (int)this >> 16, (short)this);
    super::free();
}

Boolean
CIrDevice::Init(TIrGlue *irda, AppleIrDASerial *driver)
{
    XTRACE(kLogInit, (int)this >> 16, (short)this);
    
    
    fIrDA = nil;
    fDriver = nil;
    fGetBuffer = nil;
    bzero(&fIrStatus, sizeof(fIrStatus));
    fLAPAddr = 0;
    fSpeed = 0;
    fBofs = 0;
    fXmitting = false;                  // true if in middle of xmit
    
    if (!super::init()) return false;
    
    fIrDA = irda;
    fDriver = driver;
    
    ResetStats();
    return true;
}

void
CIrDevice::Start(void)
{
    XTRACE(kLogStart, 0, 0);
    ChangeSpeed(9600);  // set to default speed
}

void
CIrDevice::Stop(void)
{
    XTRACE(kLogStop, 0, 0);
    
    fDriver = nil;
}

void
CIrDevice::SetLAPAddress(UInt8 addr)            // Set our LAP address
{
    XTRACE(kLogSetAddress, 0, addr);
    fLAPAddr = addr;
}


//--------------------------------------------------------------------------------
//      ValidFrameAddress
//
// Check the lap address field for either broadcast, or our address
//--------------------------------------------------------------------------------
Boolean
CIrDevice::ValidFrameAddress(UInt8 aField)
{
    Boolean rc =  ((aField >> 1) == kIrLAPBroadcastAddr) || ((aField >> 1) == fLAPAddr);
    if (!rc) {
	XTRACE(kLogNotOurAddress, aField, fLAPAddr);
    }
    return rc;
}

//--------------------------------------------------------------------------------
//      ChangeSpeed
//--------------------------------------------------------------------------------
IrDAErr CIrDevice::ChangeSpeed(unsigned long bps)
{
    XTRACE(kLogChangeSpeed, bps >> 16, (short)bps);
    require(fDriver, Fail);
    check(fXmitting == false);
	
    if (bps != fSpeed) {
	(void) fDriver->SetSpeed(bps);      // start the speed change sequence
	fSpeed = bps;
    }
    
    /***
    if (1) {        // doing this right again finally
	TIrLAP *lap;
	require(fIrDA, Fail);
	lap = fIrDA->GetLAP();
	require(lap, Fail);
	lap->ChangeSpeedComplete();     // finally tell lap that the speed change finished
    }
    ***/
    return noErr;

Fail:
    return kIrDAErrWrongState;
    
} // CIrDevice::ChangeSpeed

void
CIrDevice::SetSpeedComplete(Boolean worked)
{
    TIrLAP *lap;
    XTRACE(kLogSetSpeedComplete, 0, worked);
    require(fIrDA, Fail);
    lap = fIrDA->GetLAP();
    require(lap, Fail);
    lap->ChangeSpeedComplete();

Fail:
    return;
}

//--------------------------------------------------------------------------------
//      GetMediaBusy
//--------------------------------------------------------------------------------
Boolean CIrDevice::GetMediaBusy(void)
{
    // return fDevice->GetMediaBusy();
    return false;
}

//--------------------------------------------------------------------------------
//      ResetMediaBusy
//--------------------------------------------------------------------------------
void CIrDevice::ResetMediaBusy(void)
{
    // fDevice->ResetMediaBusy();
    return;
}

//--------------------------------------------------------------------------------
//      StartReceive
//--------------------------------------------------------------------------------
void CIrDevice::StartReceive(CBufferSegment* inputBuffer)
{
//  check(fGetBuffer == nil);       // see if a read is "pending" already
    fGetBuffer = inputBuffer;
}

//--------------------------------------------------------------------------------
//      StopReceive
//--------------------------------------------------------------------------------
void CIrDevice::StopReceive(void)
{
    //check(fGetBuffer != nil);     // this happens
    fGetBuffer = nil;
}

//--------------------------------------------------------------------------------
//      ReadComplete
//--------------------------------------------------------------------------------
void CIrDevice::ReadComplete(UInt8 *buffer, UInt32 length)
{
    XTRACE(kLogReadComplete, 0, length);
    UByte aField, cField;
    
    // if debugging really high, log data bytes too
#if (hasTracing > 0 && hasCIrDeviceTracing > 1)
    {
	int len = length;
	UInt32 w;
	UInt8 *b = buffer;
	int i;
	
	while (len > 0) {
	    w = 0;
	    for (i = 0 ; i < 4; i++) {
		w = w << 8;
		if (len > 0)            // don't run off end (pad w/zeros)
		    w = w | *b;
		b++;
		len--;
	    }
	    XTRACE(kLogReadComplete, w >> 16, (short)w);
	}
    }

#endif // hasCIrDeviceTracing > 1

    // test for data recvd but LAP doesn't have a read pending
    if (fGetBuffer == nil) {
	XTRACE(kLogPacketDropped, 0, 0);
	Stats_PacketDropped();              // different bucket for invalid sequence vs. this?
	return;
    }

    require(length >= 2, Fail);
    
    aField = buffer[0];
    cField = buffer[1];
    length -= 2;
    buffer += 2;
    
    if (length > 0) {
	fGetBuffer->Putn(buffer, length);       // copy to CBufferSegment
	fGetBuffer->Hide(fGetBuffer->GetSize() - fGetBuffer->Position(), kPosEnd);
	// Seek to beginning of the buffer for our client
	fGetBuffer->Seek(0, kPosBeg);
    }
    
    if (ValidFrameAddress(aField)) {                        // if packet addressed to us
	Stats_DataPacketIn();
	fGetBuffer = nil;                                   // read is done, clear our "read pending" flag
	fIrDA->GetLAP()->InputComplete(aField,cField);      // then let LAP know about it (already owns "fGetBuffer")
    }
    else {
	int review_media_busy_logic;
    //  fMediaBusy = true;                                  // else did we just saw someone else's traffic?
	Stats_PacketDropped();                              // different counters?
    }
Fail:
    return;
    
} // CIrDevice::ReadComplete

//--------------------------------------------------------------------------------
//      StartTransmit
//--------------------------------------------------------------------------------
void CIrDevice::StartTransmit(TIrLAPPutBuffer* outputBuffer, ULong leadInCount)
{
    UByte   *ctlBuffer;
    UByte   *dataBuffer;
    int     ctlSize;
    int     dataSize;
    IOReturn    ior;
    
    require(fDriver, Fail);
    require(outputBuffer, Fail);
    require(fXmitting == false, Fail);
	
    fXmitting = true;
    
    if (fBofs != leadInCount) {
	fDriver->SetBofCount(leadInCount);      // cache and call only if different?
	fBofs = leadInCount;
    }

    XTRACE(kLogStartXmitPutBuf, (int)outputBuffer >> 16, (short)outputBuffer);
    
    ctlBuffer  = outputBuffer->GetCtrlBuffer();
    XTRACE(kLogStartXmitCtlBuf, (int)ctlBuffer >> 16, (short)ctlBuffer);
    
    ctlSize    = outputBuffer->GetCtrlSize();
    XTRACE(kLogStartXmitCtlSize, (int)ctlSize >> 16, (short)ctlSize);
    
    dataBuffer = outputBuffer->GetDataBuffer();
    XTRACE(kLogStartXmitDataBuf, (int)dataBuffer >> 16, (short)dataBuffer);
    
    dataSize   = outputBuffer->GetDataSize();
    XTRACE(kLogStartXmitDataSize, (int)dataSize >> 16, (short)dataSize);
	
    //require(ctlSize + dataSize <= LapLength, Fail);
			
#if (hasTracing > 0 && hasCIrDeviceTracing > 1)
    if (1) {
	int len = ctlSize + dataSize;   // dump control and data buffers
	UInt32 w;
	UInt8 *b = ctlBuffer;           // start with control buffer, then switch to data buffer
	int i;
	
	XTRACE(kLogStartXmitLength, len >> 16, (short)len);
	
	require(len > 0 && ctlSize > 0 && ctlBuffer != nil, Fail);  // sanity (assumes non-empty ctlbuffer)
	
	while (len > 0) {               // loop over the packet
	    w = 0;                      // logging 4 bytes at a time
	    for (i = 0 ; i < 4; i++) {
		w = w << 8; 
		if (len > 0)            // don't run off end (pad w/zeros)
		    w = w | *b;
		b++;
		if (b == &ctlBuffer[ctlSize])   // if just incr'd past end of control buffer
		    b = dataBuffer;             //  then switch to data buffer
		len--;
	    }
	    XTRACE(kLogStartXmitData, w >> 16, (short)w);
	}
    }
#endif // hasCIrDeviceTracing > 1

    // start the transmit
    ior = fDriver->StartTransmit(ctlSize, ctlBuffer, dataSize, dataBuffer);
    if (ior != kIOReturnSuccess) {
	XTRACE(kLogStartXmitError, ior >> 16, (short)ior);
	TransmitComplete(false);
	return;
    }
    XTRACE(kLogStartXmitData, 0xffff, 0xffff);
    return;

Fail:
    XTRACE(kLogStartXmitError, 0, fXmitting);
    return;
}

void CIrDevice::TransmitComplete(Boolean worked)
{   
    TIrLAP *lap;

    XTRACE(kLogTransmitComplete, 0, worked);
    check(fXmitting);
    
    fXmitting = false;
    require(fIrDA, Fail);
    
    lap = fIrDA->GetLAP();
    require(lap, Fail);
    Stats_DataPacketOut();
    
    lap->OutputComplete();      // what to do if the write failed?
    
Fail:
    return;
}

//--------------------------------------------------------------------------------
//      ResetStats
//--------------------------------------------------------------------------------
void CIrDevice::ResetStats()
{
    bzero(&fIrStatus, sizeof(fIrStatus));
}
