! $Id$
!
! Earth System Modeling Framework
! Copyright (c) 2002-2024, University Corporation for Atmospheric Research,
! Massachusetts Institute of Technology, GEOEhysical Fluid Dynamics
! Laboratory, University of Michigan, National Centers for Environmental
! Prediction, Los Alamos National Laboratory, Argonne National Laboratory,
! NASA Goddard Space Flight Center.
! Licensed under the University of Illinois-NCSA License.
!
!==============================================================================

!==============================================================================
!ESMF_MULTI_PROC_EXAMPLE        String used by test script to count examples.
!==============================================================================

#define FILENAME "src/Superstructure/Component/examples/ESMF_CompTunnelEx.F90"
#include "ESMF_Macros.inc"
#include "ESMF.h"

module ESMF_CompTunnelEx_mod

  ! modules
  use ESMF
  
  implicit none
  
  private
  
  public setservices, setservicesCPL

  contains !--------------------------------------------------------------------

  recursive subroutine setservices(gcomp, rc)
    ! arguments
    type(ESMF_GridComp):: gcomp
    integer, intent(out):: rc
    
    ! Initialize
    rc = ESMF_SUCCESS

    ! register RUN method
    call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, &
      userRoutine=initialize, rc=rc)
    if (rc/=ESMF_SUCCESS) return ! bail out
    call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_RUN, &
      userRoutine=run, rc=rc)
    call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_FINALIZE, &
      userRoutine=finalize, rc=rc)
    if (rc/=ESMF_SUCCESS) return ! bail out

  end subroutine !--------------------------------------------------------------
  
  recursive subroutine initialize(gcomp, istate, estate, clock, rc)
    ! arguments
    type(ESMF_GridComp):: gcomp
    type(ESMF_State):: istate, estate
    type(ESMF_Clock):: clock
    integer, intent(out):: rc
    
    ! Initialize
    rc = ESMF_SUCCESS
        
    call ESMF_LogWrite("Actual Component Initialize", ESMF_LOGMSG_INFO, rc=rc)
    if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, &
      line=__LINE__, &
      file=FILENAME)) &
      return  ! bail out

  end subroutine !--------------------------------------------------------------

  recursive subroutine run(gcomp, istate, estate, clock, rc)
    ! arguments
    type(ESMF_GridComp):: gcomp
    type(ESMF_State):: istate, estate
    type(ESMF_Clock):: clock
    integer, intent(out):: rc
    
    ! Initialize
    rc = ESMF_SUCCESS
    
    call ESMF_LogWrite("Actual Component Run", ESMF_LOGMSG_INFO, rc=rc)
    if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, &
      line=__LINE__, &
      file=FILENAME)) &
      return  ! bail out

  end subroutine !--------------------------------------------------------------

  recursive subroutine finalize(gcomp, istate, estate, clock, rc)
    ! arguments
    type(ESMF_GridComp):: gcomp
    type(ESMF_State):: istate, estate
    type(ESMF_Clock):: clock
    integer, intent(out):: rc
    
    ! Initialize
    rc = ESMF_SUCCESS
    
    call ESMF_LogWrite("Actual Component Finalize", ESMF_LOGMSG_INFO, rc=rc)
    if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, &
      line=__LINE__, &
      file=FILENAME)) &
      return  ! bail out
          
  end subroutine !--------------------------------------------------------------

  recursive subroutine setservicesCPL(cplcomp, rc)
    ! arguments
    type(ESMF_CplComp):: cplcomp
    integer, intent(out):: rc
    
    ! Initialize
    rc = ESMF_SUCCESS

  end subroutine !--------------------------------------------------------------
  
end module

!-------------------------------------------------------------------------
!-------------------------------------------------------------------------
!-------------------------------------------------------------------------

program ESMF_CompTunnelEx
  
  ! The ESMF Framework module
  use ESMF
  use ESMF_TestMod
  
  ! User supplied modules
  use ESMF_CompTunnelEx_mod, only: setservices, setservicesCPL
  
  implicit none
  
  ! Local variables
  integer                 :: rc, userRc
  type(ESMF_VM)           :: vm
  integer                 :: localPet, petCount
  integer                 :: petList(3)
  type(ESMF_GridComp)     :: actualComp, dualComp
  type(ESMF_CplComp)      :: actualCplComp
  logical                 :: timeoutFlag
  
  character(ESMF_MAXSTR)  :: testname
  character(ESMF_MAXSTR)  :: failMsg

  integer                 :: finalrc, result

!-------------------------------------------------------------------------
!-------------------------------------------------------------------------
  write(failMsg, *)   "Example failure"
  write(testname, *)  "Example ESMF_CompTunnelEx"
! ------------------------------------------------------------------------------
! ------------------------------------------------------------------------------
  finalrc = ESMF_SUCCESS
!-------------------------------------------------------------------------
  ! Initialize the Framework and get the default VM
  call ESMF_Initialize(vm=vm, defaultlogfilename="CompTunnelEx.Log", &
    logkindflag=ESMF_LOGKIND_MULTI, rc=rc)
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  
  call ESMF_VMGet(vm, localPet=localPet, petCount=petCount, rc=rc)
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  
  if (petCount /= 6) then
    finalrc = ESMF_FAILURE
    goto 10
  endif
!-------------------------------------------------------------------------

!-------------------------------------------------------------------------
!-------------------------------------------------------------------------
#ifdef ESMF_TESTCOMPTUNNEL
!-------------------------------------------------------------------------
!-------------------------------------------------------------------------

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{Creating an {\em actual} Component}
! 
! \label{sec:CompTunnelActualCreate}
!
! The creation process of an {\em actual} Gridded Component, which will become
! one of the two end points of a Component Tunnel, is identical to the creation
! of a regular Gridded Component. On the actual side, an actual Component is 
! very similar to a regular Component. Here the actual Component is created
! with a custom {\tt petList}.
!EOE
!BOC
  petList = (/0,1,2/)
  actualComp = ESMF_GridCompCreate(petList=petList, name="actual", rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  petList = (/3,4,5/)
  actualCplComp = ESMF_CplCompCreate(petList=petList, name="actCplComp", rc=rc)
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{Creating a {\em dual} Component}
! 
! \label{sec:CompTunnelDualCreate}
!
! The same way an actual Component appears as a regular Component in
! the context of the actual side application, a {\em dual} Component
! is created as a regular Component on the dual side.
! A dual Gridded Component with custom {\tt petList} is created using the
! regular create call.
!EOE
!BOC
  petList = (/4,3,5/)
  dualComp = ESMF_GridCompCreate(petList=petList, name="dual", rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{Setting up the {\em actual} side of a Component Tunnel}
! 
! \label{sec:CompTunnelActualSide}
!
! After creation, the regular procedure for registering the standard Component
! methods is followed for the actual Gridded Component.
!EOE
!BOC
  call ESMF_GridCompSetServices(actualComp, userRoutine=setservices, &
    userRc=userRc, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  if (userRc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
! So far the {\tt actualComp} object is no different from a regular Gridded
! Component. In order to turn it into the {\em actual} end point of a Component
! Tunnel the {\tt ServiceLoop()} method is called. Here the socket-based
! implementation is chosen.
!EOE
!BOC
  call ESMF_GridCompServiceLoop(actualComp, port=61010, timeout=20, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
! This call opens the actual side of the Component Tunnel in form of a
! socket-based server, listening on {\tt port} 61010. The {\tt timeout} argument
! specifies how long the actual side will wait for the dual side
! to connect, before the actual side returns with a time out condition. The
! time out is set to 20 seconds.
!
! At this point, before a dual Component connects to the other side of the 
! Component Tunnel, it is
! possible to manually connect to the waiting actual Component. This can be
! useful when debugging connection issues. A convenient tool for this is the 
! standard {\tt telnet} application. Below is a transcript of such a connection.
! The manually typed commands are separate from the previous responses by a 
! blank line.
!
! \begin{verbatim}
! $ telnet localhost 61010
! Trying 127.0.0.1...
! Connected to localhost.
! Escape character is '^]'.
! Hello from ESMF Actual Component server!
!
! date
! Tue Apr  3 21:53:03 2012
! 
! version
! ESMF_VERSION_STRING: 5.3.0
! \end{verbatim}
!
! If at any point the {\tt telnet} session is manually shut down, the 
! {\tt ServiceLoop()} on the actual side will return with an error condition. 
! The clean way to
! disconnect the {\tt telnet} session, and to have the {\tt ServiceLoop()}
! wait for a new connection, e.g. from a dual Component, is to send the
! {\tt reconnect} command. This will automatically shut down the {\tt telnet}
! connection.
!
! \begin{verbatim}
! reconnect
! Actual Component server will reconnect now!
! Connection closed by foreign host.
! $
! \end{verbatim}
!
! At this point the actual Component is back in listening mode, with a time out
! of 20 seconds, as specified during the ServiceLoop() call.
!
! \begin{sloppypar}
! Before moving on to the dual side of the GridComp based Component Tunnel 
! example, it should be pointed out that the exact same procedure is used to
! set up the actual side of a {\em CplComp} based Component Tunnel. Assuming
! that {\tt actualCplComp} is a CplComp object for which SetServices has already
! been called, the actual side uses {\tt ESMF\_CplCompServiceLoop()} to start
! listening for connections from the dual side.
! \end{sloppypar}
!EOE

  call ESMF_CplCompSetServices(actualCplComp, userRoutine=setservicesCPL, &
    userRc=userRc, rc=rc)
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  if (userRc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)

!BOC
  call ESMF_CplCompServiceLoop(actualCplComp, port=61011, timeout=2, &
    timeoutFlag=timeoutFlag, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
! Here the {\tt timeoutFlag} is specified in order to prevent the expected
! time-out condition to be indicated through the return code. Instead, when
! {\tt timeoutFlag} is present, the return code is still {\tt ESMF\_SUCCESS}, 
! but {\tt timeoutFlag} is set to {\tt .true.} when a time-out occurs.
!EOE

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{Setting up the {\em dual} side of a Component Tunnel}
! 
! \label{sec:CompTunnelDualSide}
!
! On the dual side, the {\tt dualComp} object needs to be connected to the
! actual Component in order to complete the Component Tunnel. Instead of
! registering standard Component methods locally, a special variant of the
! {\tt SetServices()} call is used to connect to the actual Component.
!EOE
!BOC
  call ESMF_GridCompSetServices(dualComp, port=61010, server="localhost", &
    timeout=10, timeoutFlag=timeoutFlag, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
! The {\tt port} and {\tt server} arguments are used to connect to the desired
! actual Component. The time out of 10 seconds ensures that if the actual
! Component is not available, a time out condition is returned instead of
! resulting in a hang. The {\tt timeoutFlag} argument further absorbs the time
! out condition, either returning as {\tt .true.} or {\tt .false.}. In this mode
! the standard {\tt rc} will indicate success even when a time out condition
! was reached.
!EOE

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{Invoking standard Component methods through a Component Tunnel}
! 
! \label{sec:CompTunnelInvoking}
!
! Once a Component Tunnel is established, the actual Component is fully under
! the control of the dual Component. A standard Component method invoked on the
! dual Component is not executed by the dual Component itself, but by the 
! actual Component instead. In fact, it is the entry points registered with
! the actual Component that are executed when standard methods are invoked on
! the dual Component. The connected {\tt dualComp} object serves as a portal
! through which the connected {\tt actualComp} becomes accessible on the dual
! side.
!
! Typically the first standard method called is the {\tt CompInitialize()}
! routine.
!EOE
!BOC
  call ESMF_GridCompInitialize(dualComp, timeout=10, timeoutFlag=timeoutFlag, &
    userRc=userRc, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  if (userRc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
! Again, the {\tt timeout} argument serves to prevent the dual side from 
! hanging if the actual Component application has experienced a catastrophic
! condition and is no longer available, or takes longer than expected. The
! presence of the {\tt timeoutFlag} allows time out conditions to be caught
! gracefully, so the dual side can deal with it in an orderly fashion, instead
! of triggering an application abort due to an error condition.
!
! The {\tt CompRun()} and {\tt CompFinalize()} methods follow the same format.
!
!BOC
  call ESMF_GridCompRun(dualComp, timeout=10, timeoutFlag=timeoutFlag, &
    userRc=userRc, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  if (userRc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOC
  call ESMF_GridCompFinalize(dualComp, timeout=10, timeoutFlag=timeoutFlag, &
    userRc=userRc, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  if (userRc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{The non-blocking option to invoke standard Component methods through a Component Tunnel}
! 
! \label{sec:CompTunnelInvokingNonblocking}
!
! Standard Component methods called on a connected dual Component are executed
! on the actual side, across the PETs of the actual Component. By default the
! dual Component PETs are blocked until the actual Component has finished
! executing the invoked Component method, or until a time out condition has been
! reached. In many practical applications a more loose synchronization between
! dual and actual Components is useful. Having the PETs of a dual
! Component return immediately from a standard Component method allows multiple
! dual Component, on the same PETs, to control multiple actual Components. 
! If the actual Components are executing in separate executables, or the same 
! executable but on exclusive sets of PETs, they can execute concurrently, even
! with the controlling dual Components all running on the same PETs.
! The non-blocking dual side regains control over the actual Component by 
! synchronizing through the CompWait() call.
!
! Any of the standard Component methods can be called in non-blocking mode
! by setting the optional {\tt syncflag} argument to 
! {\tt ESMF\_SYNC\_NONBLOCKING}. 
!EOE
!BOC
  call ESMF_GridCompInitialize(dualComp, syncflag=ESMF_SYNC_NONBLOCKING, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
!
! {\em If} communication between the dual and the actual Component was successful, 
! this call will return immediately on all of the dual Component PETs, while the
! actual Component continues to execute the invoked Component method.
! However, if the dual Component has difficulties reaching the actual Component,
! the call will block on all dual PETs until successful contact was made, or the
! default time out (3600 seconds, i.e. 1 hour) has been reached. In most cases a 
! shorter time out condition is desired with the non-blocking option, as shown
! below.
!
! First the dual Component must wait for the outstanding method.
!EOE
!BOC
  call ESMF_GridCompWait(dualComp, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
!
! Now the same non-blocking CompInitialize() call is issued again, but this time
! with an explicit 10 second time out.
!EOE
!BOC
  call ESMF_GridCompInitialize(dualComp, syncflag=ESMF_SYNC_NONBLOCKING, &
    timeout=10, timeoutFlag=timeoutFlag, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
!
! This call is guaranteed to return within 10 seconds, or less, on the dual Component
! PETs, either without time out condition, indicating that the actual Component
! has been contacted successfully, or with time out condition, indicating that
! the actual Component was unreachable at the time. Either way, the dual 
! Component PETs are back under user control quickly.
!
! Calling the CompWait() method on the dual Component causes the dual Component
! PETs to block until the actual Component method has returned, or a time out
! condition has been reached.
!EOE
!BOC
  call ESMF_GridCompWait(dualComp, userRc=userRc, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  if (userRc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
!
! The default time out for CompWait() is 3600 seconds, i.e. 1 hour, just like
! for the other Component methods. However, the semantics of a time out 
! condition under CompWait() is different from the other Component methods. Typically the {\tt timeout} is simply the
! maximum time that any communication between dual and actual Component is allowed 
! to take before a time out condition is raised. For CompWait(), the {\tt timeout}
! is the maximum time that an actual Component is allowed to execute before
! reporting back to the dual Component. Here, even with the default time out, 
! the dual Component would return from CompWait() immediately with a time out
! condition if the actual Component has already been executing for over 1 hour, 
! and is not already waiting to report back when the dual Component calls 
! CompWait(). On the other hand, if it has only been 30 minutes since 
! CompInitialize() was called on the dual Component, then the actual Component
! still has 30 minutes before CompWait() returns with a time out condition.
! During this time (or until the actual Component returns) the dual Component
! PETs are blocked.
!
! A standard Component method is invoked in non-blocking mode.
!EOE
!BOC
  call ESMF_GridCompRun(dualComp, syncflag=ESMF_SYNC_NONBLOCKING, &
    timeout=10, timeoutFlag=timeoutFlag, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
!
! Once the user code on the dual side is ready to regain control over the
! actual Component it calls CompWait() on the dual Component. Here a
! {\tt timeout} of 60s is specified, meaning that the total execution time the
! actual Component spends in the registered Run() routine may not exceed 60s
! before CompWait() returns with a time out condition.
!EOE
!BOC
  call ESMF_GridCompWait(dualComp, timeout=60, userRc=userRc, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  if (userRc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{Destroying a connected {\em dual} Component}
! 
! \label{sec:CompTunnelDualDestroy}
!
! A dual Component that is connected to an actual Component through a Component
! Tunnel is destroyed the same way a regular Component is. The only
! difference is that a connected dual Component may specify a {\tt timeout}
! argument to the {\tt CompDestroy()} call.
!EOE
!BOC
  call ESMF_GridCompDestroy(dualComp, timeout=10, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
!BOE
! The {\tt timeout} argument again ensures that the dual side does not hang
! indefinitely in case the actual Component has become unavailable. If the
! actual Component is available, the destroy call will indicate to the actual
! Component that it should break out of the {\tt ServiceLoop()}. Either way,
! the local dual Component is destroyed.
!EOE

!-----------------------------------------------------------------------------
!BOE
!\subsubsection{Destroying a connected {\em actual} Component}
! 
! \label{sec:CompTunnelActualDestroy}
!
! An actual Component that is in a {\tt ServiceLoop()} must first return from 
! that call before it can be destroyed. This can either happen when a connected
! dual Component calls its {\tt CompDestroy()} method, or if the
! {\tt ServiceLoop()} reaches the specified time out condition. Either way,
! once control has been returned to the user code, the actual Component is 
! destroyed in the same way a regular Component is, by calling the destroy
! method.
!EOE
!BOC
  call ESMF_GridCompDestroy(actualComp, rc=rc)
!EOC
  if (rc /= ESMF_SUCCESS) call ESMF_Finalize(endflag=ESMF_END_ABORT)
  
!-------------------------------------------------------------------------
!-------------------------------------------------------------------------
#endif
!-------------------------------------------------------------------------
!-------------------------------------------------------------------------
10 continue
  ! IMPORTANT: ESMF_STest() prints the PASS string and the # of processors in the log
  ! file that the scripts grep for.
  call ESMF_STest((finalrc.eq.ESMF_SUCCESS), testname, failMsg, result, ESMF_SRCLINE)

  if (rc/=ESMF_SUCCESS) finalrc = ESMF_FAILURE
  if (finalrc==ESMF_SUCCESS) then
    print *, "PASS: ESMF_CompTunnelEx.F90"
  else
    print *, "FAIL: ESMF_CompTunnelEx.F90"
  endif

  call ESMF_Finalize(rc=rc)

end program ESMF_CompTunnelEx
