/*************************************************************************\
* 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.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

/*
 *
 *
 *                    L O S  A L A M O S
 *              Los Alamos National Laboratory
 *               Los Alamos, New Mexico 87545
 *
 *  Copyright, 1986, The Regents of the University of California.
 *
 *
 *  Author Jeffrey O. Hill
 *  johill@lanl.gov
 *  505 665 1831
 */

#ifndef INC_virtualCircuit_H
#define INC_virtualCircuit_H

#include "tsDLList.h"

#include "comBuf.h"
#include "caServerID.h"
#include "netiiu.h"
#include "comQueSend.h"
#include "comQueRecv.h"
#include "tcpRecvWatchdog.h"
#include "tcpSendWatchdog.h"
#include "hostNameCache.h"
#include "SearchDest.h"
#include "compilerDependencies.h"

class callbackManager;

// a modified ca header with capacity for large arrays
struct caHdrLargeArray {
    ca_uint32_t m_postsize;     // size of message extension
    ca_uint32_t m_count;        // operation data count
    ca_uint32_t m_cid;          // channel identifier
    ca_uint32_t m_available;    // protocol stub dependent
    ca_uint16_t m_dataType;     // operation data type
    ca_uint16_t m_cmmd;         // operation to be performed
};

class ipAddrToAsciiEngine;

class tcpRecvThread : private epicsThreadRunable {
public:
    tcpRecvThread (
        class tcpiiu & iiuIn, epicsMutex & cbMutexIn, cacContextNotify &,
        const char * pName, unsigned int stackSize, unsigned int priority );
    virtual ~tcpRecvThread ();
    void start ();
    void exitWait ();
    bool exitWait ( double delay );
    void interruptSocketRecv ();
    void show ( unsigned level ) const;
private:
    epicsThread thread;
    class tcpiiu & iiu;
    epicsMutex & cbMutex;
    cacContextNotify & ctxNotify;
    void run ();
    void connect (
        epicsGuard < epicsMutex > & guard );
    bool validFillStatus (
        epicsGuard < epicsMutex > & guard,
        const statusWireIO & stat );
};

class tcpSendThread : private epicsThreadRunable {
public:
    tcpSendThread (
        class tcpiiu & iiuIn, const char * pName,
        unsigned int stackSize, unsigned int priority );
    virtual ~tcpSendThread ();
    void start ();
    void exitWait ();
    void interruptSocketSend ();
    void show ( unsigned level ) const;
private:
    epicsThread thread;
    class tcpiiu & iiu;
    void run ();
};

class SearchDestTCP : public SearchDest {
public:
    SearchDestTCP ( cac &, const osiSockAddr & );
    void searchRequest ( epicsGuard < epicsMutex > & guard,
         const char * pbuf, size_t len );
    void show ( epicsGuard < epicsMutex > & guard, unsigned level ) const;
    void setCircuit ( tcpiiu * );
    void disable ();
    void enable ();
private:
    tcpiiu * _ptcpiiu;
    cac & _cac;
    const osiSockAddr _addr;
    bool _active;
};

class tcpiiu :
        public netiiu, public tsDLNode < tcpiiu >,
        public tsSLNode < tcpiiu >, public caServerID,
        private wireSendAdapter, private wireRecvAdapter {
    friend void SearchDestTCP::searchRequest ( epicsGuard < epicsMutex > & guard,
                                               const char * pbuf, size_t len );
public:
    tcpiiu ( cac & cac, epicsMutex & mutualExclusion, epicsMutex & callbackControl,
        cacContextNotify &, double connectionTimeout, epicsTimerQueue & timerQueue,
        const osiSockAddr & addrIn, comBufMemoryManager &, unsigned minorVersion,
        ipAddrToAsciiEngine & engineIn, const cacChannel::priLev & priorityIn,
        SearchDestTCP * pSearchDestIn = NULL);
    ~tcpiiu ();
    void start (
        epicsGuard < epicsMutex > & );
    void responsiveCircuitNotify (
        epicsGuard < epicsMutex > & cbGuard,
        epicsGuard < epicsMutex > & guard );
    void sendTimeoutNotify (
        callbackManager & cbMgr,
        epicsGuard < epicsMutex > & guard );
    void receiveTimeoutNotify(
        callbackManager &,
        epicsGuard < epicsMutex > & );
    void beaconAnomalyNotify (
        epicsGuard < epicsMutex > & );
    void beaconArrivalNotify (
        epicsGuard < epicsMutex > & );
    void probeResponseNotify (
        epicsGuard < epicsMutex > & );

    void flushRequest (
        epicsGuard < epicsMutex > & );
    unsigned requestMessageBytesPending (
        epicsGuard < epicsMutex > & mutualExclusionGuard );
    void flush (
        epicsGuard < epicsMutex > & mutualExclusionGuard );

    void show ( unsigned level ) const;
    bool setEchoRequestPending (
        epicsGuard < epicsMutex > & );
    void requestRecvProcessPostponedFlush (
        epicsGuard < epicsMutex > & );
    void clearChannelRequest (
        epicsGuard < epicsMutex > &,
        ca_uint32_t sid, ca_uint32_t cid );

    bool ca_v41_ok (
        epicsGuard < epicsMutex > & ) const;
    bool ca_v42_ok (
        epicsGuard < epicsMutex > & ) const;
    bool ca_v44_ok (
        epicsGuard < epicsMutex > & ) const;
    bool ca_v49_ok (
        epicsGuard < epicsMutex > & ) const;

    unsigned getHostName (
        epicsGuard < epicsMutex > &,
        char *pBuf, unsigned bufLength ) const throw ();
    bool alive (
        epicsGuard < epicsMutex > & ) const;
    bool connecting (
        epicsGuard < epicsMutex > & ) const;
    bool receiveThreadIsBusy (
        epicsGuard < epicsMutex > & );
    osiSockAddr getNetworkAddress (
        epicsGuard < epicsMutex > & ) const;
    int printFormated (
        epicsGuard < epicsMutex > & cbGuard,
        const char *pformat, ... );
    unsigned channelCount (
        epicsGuard < epicsMutex > & );
    void disconnectAllChannels (
        epicsGuard < epicsMutex > & cbGuard,
        epicsGuard < epicsMutex > & guard, class udpiiu & );
    void unlinkAllChannels (
        epicsGuard < epicsMutex > & cbGuard,
        epicsGuard < epicsMutex > & guard );
    void installChannel (
        epicsGuard < epicsMutex > &, nciu & chan,
        unsigned sidIn, ca_uint16_t typeIn, arrayElementCount countIn );
    void uninstallChan (
        epicsGuard < epicsMutex > & guard, nciu & chan );
    bool connectNotify (
        epicsGuard < epicsMutex > &, nciu & chan );

    void searchRespNotify (
        const epicsTime &, const caHdrLargeArray & );
    void versionRespNotify ( const caHdrLargeArray & );

    void * operator new ( size_t size,
        tsFreeList < class tcpiiu, 32, epicsMutexNOOP >  & );
    epicsPlacementDeleteOperator (( void *,
        tsFreeList < class tcpiiu, 32, epicsMutexNOOP > & ))

private:
    hostNameCache hostNameCacheInstance;
    tcpRecvThread recvThread;
    tcpSendThread sendThread;
    tcpRecvWatchdog recvDog;
    tcpSendWatchdog sendDog;
    comQueSend sendQue;
    comQueRecv recvQue;
    // nciu state field tells us which list
    // protected by the callback mutex
    tsDLList < nciu > createReqPend;
    tsDLList < nciu > createRespPend;
    tsDLList < nciu > v42ConnCallbackPend;
    tsDLList < nciu > subscripReqPend;
    tsDLList < nciu > connectedList;
    tsDLList < nciu > unrespCircuit;
    tsDLList < nciu > subscripUpdateReqPend;
    caHdrLargeArray curMsg;
    arrayElementCount curDataMax;
    arrayElementCount curDataBytes;
    comBufMemoryManager & comBufMemMgr;
    cac & cacRef;
    char * pCurData;
    SearchDestTCP * pSearchDest;
    epicsMutex & mutex;
    epicsMutex & cbMutex;
    unsigned minorProtocolVersion;
    enum iiu_conn_state {
        iiucs_connecting, // pending circuit connect
        iiucs_connected, // live circuit
        iiucs_clean_shutdown, // live circuit will shutdown when flush completes
        iiucs_disconnected, // socket informed us of disconnect
        iiucs_abort_shutdown // socket has been closed
                } state;
    epicsEvent sendThreadFlushEvent;
    epicsEvent flushBlockEvent;
    SOCKET sock;
    unsigned contigRecvMsgCount;
    unsigned blockingForFlush;
    unsigned socketLibrarySendBufferSize;
    unsigned unacknowledgedSendBytes;
    unsigned channelCountTot;
    bool _receiveThreadIsBusy;
    bool busyStateDetected; // only modified by the recv thread
    bool flowControlActive; // only modified by the send process thread
    bool echoRequestPending;
    bool oldMsgHeaderAvailable;
    bool msgHeaderAvailable;
    bool earlyFlush;
    bool recvProcessPostponedFlush;
    bool discardingPendingData;
    bool socketHasBeenClosed;
    bool unresponsiveCircuit;

    bool processIncoming (
        const epicsTime & currentTime, callbackManager & );
    unsigned sendBytes ( const void *pBuf,
        unsigned nBytesInBuf, const epicsTime & currentTime );
    void recvBytes (
        void * pBuf, unsigned nBytesInBuf, statusWireIO & );
    const char * pHostName (
        epicsGuard < epicsMutex > & ) const throw ();
    double receiveWatchdogDelay (
        epicsGuard < epicsMutex > & ) const;
    void unresponsiveCircuitNotify (
        epicsGuard < epicsMutex > & cbGuard,
        epicsGuard < epicsMutex > & guard );
    void initiateCleanShutdown (
        epicsGuard < epicsMutex > & );
    void initiateAbortShutdown (
        epicsGuard < epicsMutex > & );
    void disconnectNotify (
        epicsGuard < epicsMutex > & );
    bool bytesArePendingInOS () const;
    void decrementBlockingForFlushCount (
        epicsGuard < epicsMutex > & guard );
    bool isNameService () const;

    // send protocol stubs
    void echoRequest (
        epicsGuard < epicsMutex > & );
    void versionMessage (
        epicsGuard < epicsMutex > &, const cacChannel::priLev & priority );
    void disableFlowControlRequest (
        epicsGuard < epicsMutex > & );
    void enableFlowControlRequest (
        epicsGuard < epicsMutex > & );
    void hostNameSetRequest (
        epicsGuard < epicsMutex > & );
    void userNameSetRequest (
        epicsGuard < epicsMutex > & );
    void createChannelRequest (
        nciu &, epicsGuard < epicsMutex > & );
    void writeRequest (
        epicsGuard < epicsMutex > &, nciu &,
        unsigned type, arrayElementCount nElem, const void *pValue );
    void writeNotifyRequest (
        epicsGuard < epicsMutex > &, nciu &,
        netWriteNotifyIO &, unsigned type,
        arrayElementCount nElem, const void *pValue );
    void readNotifyRequest (
        epicsGuard < epicsMutex > &, nciu &,
        netReadNotifyIO &, unsigned type,
        arrayElementCount nElem );
    void subscriptionRequest (
        epicsGuard < epicsMutex > &,
        nciu &, netSubscription & subscr );
    void subscriptionUpdateRequest (
        epicsGuard < epicsMutex > &,
        nciu & chan, netSubscription & subscr );
    void subscriptionCancelRequest (
        epicsGuard < epicsMutex > &,
        nciu & chan, netSubscription & subscr );
    void flushIfRecvProcessRequested (
        epicsGuard < epicsMutex > & );
    bool sendThreadFlush (
        epicsGuard < epicsMutex > & );

    // netiiu stubs
    void uninstallChanDueToSuccessfulSearchResponse (
        epicsGuard < epicsMutex > &, nciu &, const class epicsTime & );
    bool searchMsg (
        epicsGuard < epicsMutex > &, ca_uint32_t id,
            const char * pName, unsigned nameLength );

    friend class tcpRecvThread;
    friend class tcpSendThread;

    tcpiiu ( const tcpiiu & );
    tcpiiu & operator = ( const tcpiiu & );
    void operator delete ( void * );
};

inline void * tcpiiu::operator new ( size_t size,
    tsFreeList < class tcpiiu, 32, epicsMutexNOOP > & mgr )
{
    return mgr.allocate ( size );
}

#ifdef CXX_PLACEMENT_DELETE
inline void tcpiiu::operator delete ( void * pCadaver,
    tsFreeList < class tcpiiu, 32, epicsMutexNOOP > & mgr )
{
    mgr.release ( pCadaver );
}
#endif

inline bool tcpiiu::ca_v41_ok (
    epicsGuard < epicsMutex > & ) const
{
    return CA_V41 ( this->minorProtocolVersion );
}

inline bool tcpiiu::ca_v44_ok (
    epicsGuard < epicsMutex > & ) const
{
    return CA_V44 ( this->minorProtocolVersion );
}

inline bool tcpiiu::ca_v49_ok (
    epicsGuard < epicsMutex > & ) const
{
    return CA_V49 ( this->minorProtocolVersion );
}

inline bool tcpiiu::alive (
    epicsGuard < epicsMutex > & ) const
{
    return ( this->state == iiucs_connecting ||
        this->state == iiucs_connected );
}

inline bool tcpiiu::connecting (
    epicsGuard < epicsMutex > & ) const
{
    return ( this->state == iiucs_connecting );
}

inline bool tcpiiu::receiveThreadIsBusy (
    epicsGuard < epicsMutex > & guard )
{
    guard.assertIdenticalMutex ( this->mutex );
    return this->_receiveThreadIsBusy;
}

inline void tcpiiu::beaconAnomalyNotify (
    epicsGuard < epicsMutex > & guard )
{
    //guard.assertIdenticalMutex ( this->cacRef.mutexRef () );
    this->recvDog.beaconAnomalyNotify ( guard );
}

inline void tcpiiu::beaconArrivalNotify (
    epicsGuard < epicsMutex > & guard )
{
    //guard.assertIdenticalMutex ( this->cacRef.mutexRef () );
    this->recvDog.beaconArrivalNotify ( guard );
}

inline void tcpiiu::probeResponseNotify (
    epicsGuard < epicsMutex > & cbGuard )
{
    this->recvDog.probeResponseNotify ( cbGuard );
}

inline bool tcpiiu::isNameService () const
{
    return ( this->pSearchDest != NULL );
}

inline void SearchDestTCP::setCircuit ( tcpiiu * piiu )
{
    _ptcpiiu = piiu;
}

#endif // ifdef INC_virtualCircuit_H
