/*************************************************************************\
* 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. 
\*************************************************************************/
/*
 *      $Revision-Id$
 *
 *      Author  Jeffrey O. Hill
 *              johill@lanl.gov
 *              505 665 1831
 */

#include "epicsGuard.h"
#include "gddAppTable.h" // EPICS application type table
#include "gddApps.h"
#include "dbMapper.h" // EPICS application type table
#include "errlog.h"

#define epicsExportSharedSymbols
#include "caServerDefs.h"
#include "caServerI.h"
#include "casPVI.h"
#include "chanIntfForPV.h"
#include "casAsyncIOI.h"
#include "casMonitor.h"

casPVI::casPVI ( casPV & intf ) : 
	pCAS ( NULL ), pPV ( & intf ), nMonAttached ( 0u ), 
        nIOAttached ( 0u ), deletePending ( false ) {}

casPVI::~casPVI ()
{
	//
	// all channels should have been destroyed 
    // (otherwise the server tool is yanking the 
    // PV out from under the server)
	//
	casVerify ( this->chanList.count() == 0u );

	//
	// all outstanding IO should have been deleted
	// when we destroyed the channels
	//
	casVerify ( this->nIOAttached == 0u );
    if ( this->nIOAttached ) {
        errlogPrintf ( "The number of IO objected attached is %u\n", this->nIOAttached );
    }

	//
	// all monitors should have been deleted
	// when we destroyed the channels
	//
	casVerify ( this->nMonAttached == 0u );

    {
        epicsGuard < epicsMutex > guard ( this->mutex );
        this->deletePending = true;
        if ( this->pPV ) {
            this->pPV->destroyRequest ();
        }
    }
}

void casPVI::casPVDestroyNotify ()
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    this->pPV = 0;
    if ( ! this->deletePending ) {
        // last channel to be destroyed destroys the casPVI
        tsDLIter < chanIntfForPV > iter = this->chanList.firstIter ();
        while ( iter.valid() ) {
            iter->postDestroyEvent ();
            iter++;
        }
    }
}

//
// check for none attached and delete self if so
//
// this call must be protected by the server's lock
// ( which also protects channel creation)
//
void casPVI::deleteSignal ()
{
    bool destroyNeeded = false;
    {
        epicsGuard < epicsMutex > guard ( this->mutex );

	    //
	    // if we are not attached to a server then the
	    // following steps are not relevant
	    //
	    if ( this->pCAS ) {
		    if ( this->chanList.count() == 0u ) {
                this->pCAS = NULL;
                // refresh the table whenever the server reattaches to the PV
                this->enumStrTbl.clear ();
                destroyNeeded = true;
		    }
	    }
    }

    if ( destroyNeeded ) {
		delete this;
    }

	// !! dont access self after potential delete above !!
}

caStatus casPVI::attachToServer ( caServerI & cas )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
	if ( this->pCAS ) {
		//
		// currently we enforce that the PV can be attached to only
		// one server at a time
		//
		if ( this->pCAS != & cas ) {
			return S_cas_pvAlreadyAttached;
		}
	}
	else {
		this->pCAS = & cas;
	}
	return S_cas_success;
}

//
// casPVI::updateEnumStringTable ()
//
// fetch string conversion table so that we can perform proper conversion
// of enumerated PVs to strings during reads
//
// what a API complexity nightmare this GDD is
//
caStatus casPVI::updateEnumStringTable ( casCtx & ctxIn )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    
    //
    // create a gdd with the "enum string table" application type
    //
    //	gddArray(int app, aitEnum prim, int dimen, ...);
    gdd * pTmp = new gddScalar ( gddAppType_enums );
    if ( pTmp == NULL ) {
        errMessage ( S_cas_noMemory, 
            "unable to create gdd for read of application type \"enums\" string"
            " conversion table for enumerated PV" );
        return S_cas_noMemory;
    }

    caStatus status = convertContainerMemberToAtomic ( *pTmp, 
         gddAppType_enums, MAX_ENUM_STATES );
    if ( status != S_cas_success ) {
        pTmp->unreference ();
        errMessage ( status, 
            "unable to to config gdd for read of application type \"enums\" string"
            " conversion table for enumerated PV");
        return status;
    }

    //
    // read the enum string table
    //
    status = this->read ( ctxIn, *pTmp );
    if ( status == S_cas_success ) {
        updateEnumStringTableAsyncCompletion ( *pTmp );
	}
    else if ( status != S_casApp_asyncCompletion && 
                status != S_casApp_postponeAsyncIO ) {
        errMessage ( status, 
            "- unable to read application type \"enums\" string"
            " conversion table for enumerated PV");
    }

    pTmp->unreference ();

    return status;
}

void casPVI::updateEnumStringTableAsyncCompletion ( const gdd & resp )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    
    if ( resp.isContainer() ) {
        errMessage ( S_cas_badType, 
            "application type \"enums\" string conversion table for"
            " enumerated PV was a container (expected vector of strings)" );
        return;
    }
    
    if  ( resp.dimension() == 0 ) {
        if ( resp.primitiveType() == aitEnumString ) {
            aitString *pStr = (aitString *) resp.dataVoid ();
            if ( ! this->enumStrTbl.setString ( 0, pStr->string() ) ) {
                errMessage ( S_cas_noMemory, 
                    "no memory to set enumerated PV string cache" );
            }
        }
        else if ( resp.primitiveType() == aitEnumFixedString ) {
            aitFixedString *pStr = (aitFixedString *) resp.dataVoid ();
            if ( ! this->enumStrTbl.setString ( 0, pStr->fixed_string ) ) {
                errMessage ( S_cas_noMemory, 
                    "no memory to set enumerated PV string cache" );
            }
        }
        else {
            errMessage ( S_cas_badType, 
                "application type \"enums\" string conversion"
                " table for enumerated PV isnt a string type?" );
        }
    }
    else if ( resp.dimension() == 1 ) {
        gddStatus gdd_status;
        aitIndex index, first, count;
        
        gdd_status = resp.getBound ( 0, first, count );
        assert ( gdd_status == 0 );

        //
        // preallocate the correct amount
        //
        this->enumStrTbl.reserve ( count );

        if ( resp.primitiveType() == aitEnumString ) {
            aitString *pStr = (aitString *) resp.dataVoid ();
            for ( index = 0; index<count; index++ ) {
                if ( ! this->enumStrTbl.setString ( index, pStr[index].string() ) ) {
                    errMessage ( S_cas_noMemory, 
                        "no memory to set enumerated PV string cache" );
                }
            }
        }
        else if ( resp.primitiveType() == aitEnumFixedString ) {
            aitFixedString *pStr = (aitFixedString *) resp.dataVoid ();
            for ( index = 0; index < count; index++ ) {
                if ( ! this->enumStrTbl.setString ( index, pStr[index].fixed_string ) ) {
                    errMessage ( S_cas_noMemory, 
                        "no memory to set enumerated PV string cache" );
                }
            }
        }
        else {
            errMessage ( S_cas_badType, 
                "application type \"enums\" string conversion"
                " table for enumerated PV isnt a string type?" );
        }
    }
    else {
        errMessage ( S_cas_badType, 
            "application type \"enums\" string conversion table"
            " for enumerated PV was multi-dimensional"
            " (expected vector of strings)" );
    }
}

void casPVI::postEvent ( const casEventMask & select, const gdd & event )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
	if ( this->nMonAttached ) {
        // we are paying some significant locking overhead for
        // these diagnostic counters
        this->pCAS->updateEventsPostedCounter ( this->nMonAttached );
	    tsDLIter < chanIntfForPV > iter = this->chanList.firstIter ();
        while ( iter.valid () ) {
		    iter->postEvent ( select, event );
		    ++iter;
	    }
	}
}

caStatus casPVI::installMonitor ( 
    casMonitor & mon, tsDLList < casMonitor > & monitorList )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    assert ( this->nMonAttached < UINT_MAX );
	this->nMonAttached++;
    // use pv lock to protect channel's monitor list
	monitorList.add ( mon );
    if ( this->nMonAttached == 1u && this->pPV ) {
		return this->pPV->interestRegister ();
    }
    else {
		return S_cas_success;
    }
}

casMonitor * casPVI::removeMonitor ( 
    tsDLList < casMonitor > & list, ca_uint32_t clientIdIn )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    casMonitor * pMon = 0;
	//
	// (it is reasonable to do a linear search here because
	// sane clients will require only one or two monitors 
	// per channel)
	//
	tsDLIter < casMonitor > iter = list.firstIter ();
    while ( iter.valid () ) {
		if ( iter->matchingClientId ( clientIdIn ) ) {
            list.remove ( *iter.pointer () );
            assert ( this->nMonAttached > 0 );
	        this->nMonAttached--;
            pMon = iter.pointer ();
            break;
		}
		iter++;
	}
    if ( this->nMonAttached == 0u && this->pPV ) {
        this->pPV->interestDelete ();
    }
    return pMon;
}

caServer *casPVI::getExtServer () const // X aCC 361
{
    epicsGuard < epicsMutex > guard ( this->mutex );
	if ( this->pCAS ) {
		return this->pCAS->getAdapter ();
	}
	else {
		return NULL;
	}
}

void casPVI::show ( unsigned level )  const
{
    epicsGuard < epicsMutex > guard ( this->mutex );
	printf ( "CA Server PV: nChanAttached=%u nMonAttached=%u nIOAttached=%u\n",
		this->chanList.count(), this->nMonAttached, this->nIOAttached );
	if ( level >= 1u ) {
		printf ( "\tBest external type = %d\n", this->bestExternalType() );
	}
	if ( level >= 2u ) {
        this->pPV->show ( level - 2u );
	}
}

void casPVI::installChannel ( chanIntfForPV & chan )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
	this->chanList.add ( chan );
}
 
void casPVI::removeChannel ( 
    chanIntfForPV & chan, tsDLList < casMonitor > & src, 
    tsDLList < casMonitor > & dest )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    src.removeAll ( dest );
    if ( dest.count() ) {
        assert ( this->nMonAttached >= dest.count() );
        this->nMonAttached -= dest.count ();
    }
	this->chanList.remove ( chan );
    if ( this->nMonAttached == 0u && this->pPV ) {
        this->pPV->interestDelete ();
    }
}

void casPVI::clearOutstandingReads ( tsDLList < casAsyncIOI > & ioList )
{
    epicsGuard < epicsMutex > guard ( this->mutex );

    // cancel any pending asynchronous IO 
	tsDLIter < casAsyncIOI > iterIO = 
        ioList.firstIter ();
	while ( iterIO.valid () ) {
		tsDLIter < casAsyncIOI > tmp = iterIO;
		++tmp;
        if ( iterIO->oneShotReadOP () ) {
            ioList.remove ( *iterIO );
		    delete iterIO.pointer ();
            assert ( this->nIOAttached != 0 );
            this->nIOAttached--;
        }
		iterIO = tmp;
	}
}

void casPVI::destroyAllIO ( tsDLList < casAsyncIOI > & ioList )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
	while ( casAsyncIOI * pIO = ioList.get() ) {
        pIO->removeFromEventQueue ();
		delete pIO;
        assert ( this->nIOAttached != 0 );
        this->nIOAttached--;
	}
}

void casPVI::installIO ( 
    tsDLList < casAsyncIOI > & ioList, casAsyncIOI & io )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    ioList.add ( io );
    assert ( this->nIOAttached != UINT_MAX );
    this->nIOAttached++;
}

void casPVI::uninstallIO ( 
    tsDLList < casAsyncIOI > & ioList, casAsyncIOI & io )
{
    {
        epicsGuard < epicsMutex > guard ( this->mutex );
        ioList.remove ( io );
        assert ( this->nIOAttached != 0 );
        this->nIOAttached--;
    }
	this->ioBlockedList::signal();
}

caStatus  casPVI::bestDBRType ( unsigned & dbrType ) // X aCC 361
{
    epicsGuard < epicsMutex > guard ( this->mutex );
	aitEnum bestAIT = this->bestExternalType ();
    if ( bestAIT == aitEnumInvalid || bestAIT < 0 ) {
		return S_cas_badType;
    }
    unsigned aitIndex = static_cast < unsigned > ( bestAIT );
	if ( aitIndex >= sizeof ( gddAitToDbr ) / sizeof ( gddAitToDbr[0] ) ) {
		return S_cas_badType;
    }
	dbrType = gddAitToDbr[bestAIT];
	return S_cas_success;
}

caStatus casPVI::read ( const casCtx & ctx, gdd & prototype )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    if ( this->pPV ) {
        caStatus status = this->pPV->beginTransaction ();
        if ( status != S_casApp_success ) {
            return status;
        }
        status = this->pPV->read ( ctx, prototype );
        this->pPV->endTransaction ();
        return status;
    }
    else {
        return S_cas_disconnect;
    }
}

caStatus casPVI::write ( const casCtx & ctx, const gdd & value )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    if ( this->pPV ) {
        caStatus status = this->pPV->beginTransaction ();
        if ( status != S_casApp_success ) {
            return status;
        }
        status = this->pPV->write ( ctx, value );
        this->pPV->endTransaction ();
        return status;
    }
    else {
        return S_cas_disconnect;
    }
}

caStatus casPVI::writeNotify ( const casCtx & ctx, const gdd & value )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    if ( this->pPV ) {
        caStatus status = this->pPV->beginTransaction ();
        if ( status != S_casApp_success ) {
            return status;
        }
        status = this->pPV->writeNotify ( ctx, value );
        this->pPV->endTransaction ();
        return status;
    }
    else {
        return S_cas_disconnect;
    }
}

casChannel * casPVI::createChannel ( const casCtx & ctx,
    const char * const pUserName, const char * const pHostName )
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    if ( this->pPV ) {
        return this->pPV->createChannel ( ctx, pUserName, pHostName );
    }
    else {
        return 0;
    }
}

aitEnum casPVI::bestExternalType () const
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    if ( this->pPV ) {
        return this->pPV->bestExternalType ();
    }
    else {
        return aitEnumInvalid;
    }
}

// CA only does 1D arrays for now 
aitIndex casPVI::nativeCount () 
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    if ( this->pPV ) {
	    if ( this->pPV->maxDimension() == 0u ) {
		    return 1u; // scalar
	    }
	    return this->pPV->maxBound ( 0u );
    }
    else {
        return S_cas_disconnect;
    }
}

const char * casPVI::getName () const
{
    epicsGuard < epicsMutex > guard ( this->mutex );
    if ( this->pPV ) {
        return this->pPV->getName ();
    }
    else {
        return "<disconnected>";
    }
}

