/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

//
// Example EPICS CA server
// (asynchrronous process variable)
//

#include "exServer.h"

exAsyncPV::exAsyncPV ( exServer & cas, pvInfo & setup, 
                             bool preCreateFlag, bool scanOnIn,
                             double asyncDelayIn ) :
    exScalarPV ( cas, setup, preCreateFlag, scanOnIn ),
    asyncDelay ( asyncDelayIn ),
    simultAsychReadIOCount ( 0u ),
    simultAsychWriteIOCount ( 0u )
{
}

//
// exAsyncPV::read()
//
caStatus exAsyncPV::read (const casCtx &ctx, gdd &valueIn)
{
    exAsyncReadIO *pIO;

    if ( this->simultAsychReadIOCount >= this->cas.maxSimultAsyncIO () ) {
        return S_casApp_postponeAsyncIO;
    }

    pIO = new exAsyncReadIO ( this->cas, ctx,
                *this, valueIn, this->asyncDelay );
    if ( ! pIO ) {
        if ( this->simultAsychReadIOCount > 0 ) {
            return S_casApp_postponeAsyncIO;
        }
        else {
            return S_casApp_noMemory;
        }
    }
    this->simultAsychReadIOCount++;
    return S_casApp_asyncCompletion;
}

//
// exAsyncPV::writeNotify()
//
caStatus exAsyncPV::writeNotify ( const casCtx &ctx, const gdd &valueIn )
{
    if ( this->simultAsychWriteIOCount >= this->cas.maxSimultAsyncIO() ) {
        return S_casApp_postponeAsyncIO;
    }

    exAsyncWriteIO * pIO = new
        exAsyncWriteIO ( this->cas, ctx, *this, 
                        valueIn, this->asyncDelay );
    if ( ! pIO ) {
        if ( this->simultAsychReadIOCount > 0 ) {
            return S_casApp_postponeAsyncIO;
        }
        else {
            return S_casApp_noMemory;
        }
    }
    this->simultAsychWriteIOCount++;
    return S_casApp_asyncCompletion;
}

//
// exAsyncPV::write()
//
caStatus exAsyncPV::write ( const casCtx &ctx, const gdd &valueIn )
{
    // implement the discard intermediate values, but last value
    // sent always applied behavior that IOCs provide excepting
    // that we will alow N requests to pend instead of a limit
    // of only one imposed in the IOC
    if ( this->simultAsychWriteIOCount >= this->cas.maxSimultAsyncIO() ) {
        pStandbyValue.set ( & valueIn );
        return S_casApp_success;
    }

    exAsyncWriteIO * pIO = new 
        exAsyncWriteIO ( this->cas, ctx, *this, 
                        valueIn, this->asyncDelay );
    if ( ! pIO ) {
        pStandbyValue.set ( & valueIn );
        return S_casApp_success;
    }
    this->simultAsychWriteIOCount++;
    return S_casApp_asyncCompletion;
}

// Implementing a specialized update for exAsyncPV
// allows standby value to update when we update 
// the PV from an asynchronous write timer expiration
// which is a better time compared to removeIO below
// which, if used, gets the reads and writes out of
// order. This type of reordering can cause the 
// regression tests to fail.
caStatus exAsyncPV :: updateFromAsyncWrite ( const gdd & src )
{
    caStatus stat = this->update ( src );
    if ( this->simultAsychWriteIOCount <=1 && 
            pStandbyValue.valid () ) {
//printf("updateFromAsyncWrite: write standby\n");
        stat = this->update ( *this->pStandbyValue );
        this->pStandbyValue.set ( 0 );
    }
    return stat;
}

void exAsyncPV::removeReadIO ()
{
    if ( this->simultAsychReadIOCount > 0u ) {
        this->simultAsychReadIOCount--;
    }
    else {
        fprintf ( stderr, "inconsistent simultAsychReadIOCount?\n" );
    }
}

void exAsyncPV::removeWriteIO ()
{
    if ( this->simultAsychWriteIOCount > 0u ) {
        this->simultAsychWriteIOCount--;
        if ( this->simultAsychWriteIOCount == 0 && 
            pStandbyValue.valid () ) {
//printf("removeIO: write standby\n");
            this->update ( *this->pStandbyValue );
            this->pStandbyValue.set ( 0 );
        }
    }
    else {
        fprintf ( stderr, "inconsistent simultAsychWriteIOCount?\n" );
    }
}

//
// exAsyncWriteIO::exAsyncWriteIO()
//
exAsyncWriteIO::exAsyncWriteIO ( exServer & cas,
        const casCtx & ctxIn, exAsyncPV & pvIn, 
        const gdd & valueIn, double asyncDelay ) :
    casAsyncWriteIO ( ctxIn ), pv ( pvIn ), 
        timer ( cas.createTimer () ), pValue(valueIn)
{
    this->timer.start ( *this, asyncDelay );
}

//
// exAsyncWriteIO::~exAsyncWriteIO()
//
exAsyncWriteIO::~exAsyncWriteIO()
{
    this->timer.destroy ();
    // if the timer hasnt expired, and the value 
    // hasnt been written then force it to happen
    // now so that regression testing works
    if ( this->pValue.valid () ) {
        this->pv.updateFromAsyncWrite ( *this->pValue );
    }
    this->pv.removeWriteIO();
}

//
// exAsyncWriteIO::expire()
// (a virtual function that runs when the base timer expires)
//
epicsTimerNotify::expireStatus exAsyncWriteIO::
    expire ( const epicsTime & /* currentTime */ ) 
{
    assert ( this->pValue.valid () );
    caStatus status = this->pv.updateFromAsyncWrite ( *this->pValue );
    this->pValue.set ( 0 );
    this->postIOCompletion ( status );
    return noRestart;
}

//
// exAsyncReadIO::exAsyncReadIO()
//
exAsyncReadIO::exAsyncReadIO ( exServer & cas, const casCtx & ctxIn, 
                              exAsyncPV & pvIn, gdd & protoIn,
                              double asyncDelay ) :
    casAsyncReadIO ( ctxIn ), pv ( pvIn ), 
        timer ( cas.createTimer() ), pProto ( protoIn )
{
    this->timer.start ( *this, asyncDelay );
}

//
// exAsyncReadIO::~exAsyncReadIO()
//
exAsyncReadIO::~exAsyncReadIO()
{
    this->pv.removeReadIO ();
    this->timer.destroy ();
}

//
// exAsyncReadIO::expire()
// (a virtual function that runs when the base timer expires)
//
epicsTimerNotify::expireStatus 
    exAsyncReadIO::expire ( const epicsTime & /* currentTime */ )
{
    //
    // map between the prototype in and the
    // current value
    //
    caStatus status = this->pv.exPV::readNoCtx ( this->pProto );

    //
    // post IO completion
    //
    this->postIOCompletion ( status, *this->pProto );

    return noRestart;
}

