/* **********************************************************************
 * *	                                                                *
 * *             RealTimeDisplayQueue Global Inits                      *
 * *                                                                    *
 * **********************************************************************
 * File: libs/rtdisp/RtqGlobal.C
 *
 *                    COPYRIGHT (c) 2003, 2005 BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HPUX
#include <sys/signal.h>
#endif

#include "sigtypes.h"
#include "setup.h"
#include "sig_rtdisp.h"

#include "config_lib.h"
#include "rtq_lib.h"
#include "user_lib.h"

#include "RtdSupport_h.h"

static SINT4 iRtSize_c, iRtId_c;
static int   iClientType_c;

static void ForkXmtProcess
(volatile const struct  rtdisp_setup *pRSetup_a );

/* ==============================================================
 * Create or attach to existing shared memory.
 */

MESSAGE RtqBufferAttach
( volatile struct rtdisp_control **pRtGlobal_a,
  int  iClientType_a ) 
{
  UINT2 lcreated;
  UINT1 lWriteEnable;
  SINT4 ireqsize;
  MESSAGE iStatus;
  struct rtdisp_setup RSetup;

  /* ---------------------------------------------------------------------- */

  iClientType_c = iClientType_a;

  switch( iClientType_c )
    {
    case RTQ_CLIENT_WRITER_APP:
    case RTQ_CLIENT_WRITER_RVP:
      /* Fill in the setup structure */
      iStatus = RtdispLoadSetup( NULL, &RSetup );
      if( iStatus != SS_NORMAL )
	{
	  iClientType_c = RTQ_CLIENT_DISABLED;
	  *pRtGlobal_a = NULL;
	  return iStatus;
	}
      /* Check that setup is allowing us to be the writer */
      if ( ((iClientType_c==RTQ_CLIENT_WRITER_APP) && 
	    (RSetup.lEnabled!=RTD_ENABLE_APP) ) ||
	   ((iClientType_c==RTQ_CLIENT_WRITER_RVP) && 
	    (RSetup.lEnabled!=RTD_ENABLE_RVP) ) )
	{
	  iClientType_c = RTQ_CLIENT_DISABLED;
	  *pRtGlobal_a = NULL;
	  return SS_NORMAL;
	}
      lWriteEnable = TRUE;
      break;

    default:
      lWriteEnable = FALSE;
      break;
    }

  /* Create the rtdisp shared memory section.  This is writeable only by the
   * writing process.
   */
  ireqsize = sizeof( struct rtdisp_control );

  iStatus = sig_shmem_map( ireqsize, "iris_rtq_buffer", lWriteEnable, (void**)pRtGlobal_a,
			   &iRtSize_c, &iRtId_c, &lcreated );

  /* Check for memory mapping errors
   */
  if ( iStatus != SS_NORMAL ) { iClientType_c = RTQ_CLIENT_DISABLED; return iStatus;}

  /* Check that memory was created as appropriate
   */
  switch (iClientType_c)
    {
    case RTQ_CLIENT_WRITER_APP:
    case RTQ_CLIENT_WRITER_RVP:
      if( !lcreated )
	{
	  iStatus = sig_shmem_unmap( (void *)*pRtGlobal_a, iRtSize_c, iRtId_c ) ;
	  if (iStatus != SS_NORMAL) sig_signal( iStatus );
	  iClientType_c = RTQ_CLIENT_DISABLED;
	  *pRtGlobal_a = NULL;
	  return USER_SHMEM_FOUND;
	}
      else
	{
	  volatile struct RtqQueueElement *pElement;
	  UINT4 iIndex;

	  /* Initialize the memory. */
	  memset( (void *)*pRtGlobal_a, 0, iRtSize_c );
	  for (iIndex=0; iIndex<RTQ_QUEUE_SIZE; iIndex++)
	    {
	      pElement = &((*pRtGlobal_a)->disqueue[iIndex]);
	      pElement->iSequenceNum = iIndex;
	    }
	  (*pRtGlobal_a)->iQueueIn = RTQ_QUEUE_SIZE-1;
	  (*pRtGlobal_a)->lWriterAlive = TRUE;

	  /* Fill in the setup structure, cast discards qualifiers */
	  ((struct rtdisp_control *)(*pRtGlobal_a))->RSetup = RSetup;
	}
      ForkXmtProcess( &((*pRtGlobal_a)->RSetup) ) ;
      break;

    default:
      if (lcreated)
	{
	  iStatus = sig_shmem_unmap( (void *)*pRtGlobal_a, iRtSize_c, iRtId_c ) ;
	  if (iStatus != SS_NORMAL) sig_signal( iStatus );
	  iClientType_c = RTQ_CLIENT_DISABLED;
	  *pRtGlobal_a = NULL;
	  return USER_SHMEM_MISSING;
	}
      break;
    }

  return( iStatus ) ;
}

/* ====================================================================
 * This subroutine forks a single process.
 */
static void ForkXmtProcess
(volatile const struct  rtdisp_setup *pRSetup_a )
{
  char sbin[DIRECTORY_SIZE], *penv;
  MESSAGE iStatus;
  int   iChannel;
  UINT1 lForkedOne;

  /* ---------------------------------------------------------------------- */
  penv = getenv( "IRIS_BIN" );
  if (NULL == penv) {
    sig_signal_va( USER_NO_ENV, 1, "IRIS_BIN" ) ; return ;
  }
  strncpy( sbin, penv, sizeof(sbin)-1 ) ;
  sbin[DIRECTORY_SIZE-1]=0;

  lForkedOne=FALSE;
  for (iChannel=0; iChannel<RTD_CHANNEL_COUNT; iChannel++)
    {
      if (pRSetup_a->Channel[iChannel].sXmtName[0])
	{
	  if (pRSetup_a->Channel[iChannel].sBroadcastAddress[0])
	    {
	      char sfullname[PATHNAME_SIZE], sProcessName[40];
	      
	      lForkedOne=TRUE;
	      strcpy( sfullname, sbin );
	      strcat( sfullname, (char *)pRSetup_a->Channel[iChannel].sXmtName );
	      sprintf( sProcessName, "IRIS_RTD_XMT%d", iChannel+1 );
	      
	      /* Must be daemon process so open files are not preserved */
	      iStatus = sig_fork_daemon(sfullname,      /* program name */
					sProcessName );    /* Process name */
	      
	      if( SS_NORMAL == iStatus )
		{
		  sig_microSleep( 300000 );
		}
	      else
		{
		  sig_signal(iStatus);
		}
	    }
	  else
	    {
	      sig_signal_va( RTQLIB_MISSING_ADDR, 1, iChannel+1 );
	    }
	}  /* End if (pRSetup_a->Channel[iChannel].sXmtName[0]) */
    }  /* End for (iChannel=0;... */

  if (!lForkedOne) sig_signal( RTQLIB_ALL_OFF );

  return;
}

/* ======================================================================
 * De-allocate the IRIS shared memory segment
 */
MESSAGE RtqBufferRelease
( volatile struct rtdisp_control *pRtGlobal_a )
{
  MESSAGE ireturn_status;
  
  switch (iClientType_c)
    {
      
    case RTQ_CLIENT_DISABLED:
      return SS_NORMAL;
      break;
      
    case RTQ_CLIENT_WRITER_APP:
    case RTQ_CLIENT_WRITER_RVP:
      pRtGlobal_a->lWriterAlive = FALSE;
      break;
    }

  ireturn_status = sig_shmem_unmap( (void *)pRtGlobal_a, iRtSize_c, iRtId_c ) ;

  iClientType_c = RTQ_CLIENT_DISABLED;

  return( ireturn_status ) ;
}

/* ====================================================================
 * Returns TRUE as long as the writer is still alive.  Readers should check
 * this and exit gracefully when the writer stops.
 */
UINT1 RtqWriterStatus
( volatile const struct rtdisp_control *pRtGlobal_a )
{
  if (iClientType_c!=RTQ_CLIENT_READER) return FALSE;

  return pRtGlobal_a->lWriterAlive;
}


/* ====================================================================
 * Returns the sequence number for the last ray in the buffer.
 */
UINT4 RtqGetCurrentSequence
( volatile const struct rtdisp_control *pRtGlobal_a )
{
  volatile const struct RtqQueueElement *pLastElement;
  SINT4 iQueueIn;
  
  if (iClientType_c!=RTQ_CLIENT_READER) return 0;

  iQueueIn     = (SINT4)(UINT4)pRtGlobal_a->iQueueIn;
  pLastElement = &(pRtGlobal_a->disqueue[iQueueIn]);

  return pLastElement->iSequenceNum;
}

/* ======================================================================
 * Write something to the real time display queue. 
 */
void RtqInsertRay
( volatile struct rtdisp_control *pRtGlobal_a,
  const struct RtqQueueElement *pElement_a )  /* Data to write */
{
  volatile struct RtqQueueElement *pLastElement, *pCurrentElement;
  UINT2 iQueueIn;
  
  /* Skip insert if we are not a writer */
  switch (iClientType_c)
    {
    case RTQ_CLIENT_WRITER_APP:
    case RTQ_CLIENT_WRITER_RVP:
      break;

    default:
      return;
    }

  iQueueIn = pRtGlobal_a->iQueueIn;
  pLastElement = &(pRtGlobal_a->disqueue[iQueueIn]);

  iQueueIn = (iQueueIn+1) % RTQ_QUEUE_SIZE;
  pCurrentElement = &(pRtGlobal_a->disqueue[iQueueIn]);

  /* Cast discards volatile */
  *(struct RtqQueueElement*)pCurrentElement = *pElement_a;
  pCurrentElement->iSequenceNum = pLastElement->iSequenceNum+1;

  pRtGlobal_a->iQueueIn = iQueueIn;
}

/* ======================================================================
 * Read something from the real time display queue. It will get the next
 * ray after the previous one, unless it is more than halfway back in
 * history, in which case it will get the halfway back ray.  This implements
 * our skipping catchup feature.
 * Returns TRUE if it found such a ray, else FALSE.
 */
UINT1 RtqGetRay
( volatile const struct rtdisp_control *pRtGlobal_a,
  struct RtqQueueElement *pElement_a,    /* Data read */
  UINT4 iLastSequenceNum_a )   /* The sequence number of the last ray read */
{
  volatile const struct RtqQueueElement *pLastElement;
  SINT4 iQueueIn, iQueueOut; UINT4 iSequenceAge;

  if (iClientType_c!=RTQ_CLIENT_READER) return FALSE;
  
  iQueueIn     = (SINT4)(UINT4)pRtGlobal_a->iQueueIn;
  pLastElement = &(pRtGlobal_a->disqueue[iQueueIn]);

  iSequenceAge = pLastElement->iSequenceNum - iLastSequenceNum_a;

  /*
  fprintf( stderr, "RtqGetRay, wanted:%d, last:%d, QueueIn:%d\n",
	   iLastSequenceNum_a, pLastElement->iSequenceNum, iQueueIn );
  */

  if (iSequenceAge == 0) return FALSE;  /* No new rays found */
  iSequenceAge--;  /* Decrement age to get the next ray after age! */

  if (iSequenceAge < (RTQ_QUEUE_SIZE/2))
    {
      /* Go back a short way */
      iQueueOut = iQueueIn - (SINT4)iSequenceAge;
    }
  else
    {
      /* Go back half way */
      iQueueOut = iQueueIn-(RTQ_QUEUE_SIZE/2);
    }
  if (iQueueOut<0) iQueueOut+=RTQ_QUEUE_SIZE;

  /* Cast discards volatile */
  *pElement_a = *(struct RtqQueueElement*)&(pRtGlobal_a->disqueue[iQueueOut]);
  return TRUE;

}
