/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                 ws_mouse.c

A demonstrator for CGIplus Web Socket scripts.

The JavaScript-driven client-side of this demonstrator is in

  WASD_ROOT:[SRC.WEBSOCKET]WS_MOUSE.HTML

To help manage potential bandwidth consumption the update rate is explicitly
limited.  To disable this for experimentation with maximum granularity define
the logical name "WS_MOUSE_UPDATE_MAX" and HTTPD/DO=DCL=DELETE.

Suppress host name lookup by defining the logical name WS_MOUSE_NO_LOOKUP.

Experiment with the WsLibOnNextRequest() approach to request synchronisation by
defining the logical name WS_MOUSE_ON_NEXT_REQUEST.


COPYRIGHT
---------
Copyright (C) 2010-2021 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION HISTORY
---------------
18-AUG-2014  MGD  v1.2.0, demonstrate WsLibOnNextRequest()
21-JUL-2012  MGD  v1.1.1, WsLibDestroy() deprecated
03-DEC-2011  MGD  v1.1.0, a touch more sophistication
24-JUL-2010  MGD  v1.0.0, initial development
*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#define SOFTWAREVN "1.2.0"
#define SOFTWARENM "WS_MOUSE"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>

#include <descrip.h>
#include <iodef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <starlet.h>

#include "wslib.h"

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define Debug 0

#define FI_LI "WS_MOUSE", __LINE__
#define EXIT_FI_LI(status) { printf ("[%s:%d]", FI_LI); exit(status); }

int  ConcurrentMax,
     ConnectedCount,
     UpdateMax,
     UsageCount;

void (*UpdateFunction)() = NULL;

struct MouseClient
{
   int  UpdateCount,
        YouIdent;

   char  ConnectTime [32],
         InputBuffer [256],
         MouseCoord [32],
         RemoteHostPort [128+16];

   struct WsLibStruct  *WsLibPtr;
};

/* function prototypes */
void AddClient ();
void NextRequest ();
void RemoveClient (struct WsLibStruct*);
void RemoteHostPort (struct MouseClient*);
void UpdateClient (struct WsLibStruct*);
void UpdateAllClients ();

/*****************************************************************************/
/*
AST delivery is disabled during client acceptance and the add-client function
is deferred using an AST to help minimise the client setup window with a
potentially busy WebSocket application.
*/

main ()

{
   /*********/
   /* begin */
   /*********/

   /* don't want the C-RTL fiddling with the carriage control */
   stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin");

   UpdateMax = (getenv("WS_MOUSE_UPDATE_MAX") != NULL);

   /* no clients is two minutes in seconds */
   WsLibSetLifeSecs (2*60);

   if (!WsLibIsCgiPlus())
   {
      fprintf (stdout, "Status: 500\r\n\r\n\
WebSocket needs to be CGIplus!\n");
   }
   else
   if (getenv("WS_MOUSE_ON_NEXT_REQUEST") != NULL)
   {
      WsLibOnNextRequest (NextRequest);
      /* asynchronous from here-on-in */
      for (;;) sys$hiber();
   }
   else
   {
      for (;;)
      {
         WsLibCgiVar ("");

         sys$setast (0);

         UsageCount++;

         AddClient (0);

         WsLibCgiPlusEof ();

         sys$setast (1);
      }
   }

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Asynchronous notification the next request is available.  This function is
called at AST-delivery level and so no need to disable ASTs.  Add the client
and then notify the server we're ready for another.
*/

void NextRequest ()

{
   /*********/
   /* begin */
   /*********/

   UsageCount++;

   AddClient (1);

   WsLibCgiPlusEof ();
}

/*****************************************************************************/
/*
Allocate a client structure and add it to the head of the list.
*/

void AddClient (int OnNext)

{
   int  len, status;
   char  ConnectTime [32],
         YouString [32];
   $DESCRIPTOR (TimeFaoDsc, "!20%D\0");
   $DESCRIPTOR (TimeDsc, "");
   struct WsLibStruct  *wsptr;
   struct MouseClient  *clptr;

   /*********/
   /* begin */
   /*********/

   clptr = calloc (1, sizeof(struct MouseClient));
   if (!clptr) EXIT_FI_LI (vaxc$errno);

   RemoteHostPort (clptr);
   TimeDsc.dsc$a_pointer = clptr->ConnectTime;
   TimeDsc.dsc$w_length = sizeof(clptr->ConnectTime);
   sys$fao (&TimeFaoDsc, 0, &TimeDsc, 0);
   clptr->YouIdent = UsageCount;

   /* create a WebSocket library structure for the client */
   if (!(clptr->WsLibPtr = wsptr = WsLibCreate (clptr, RemoveClient)))
   {
      /* failed, commonly on some WebSocket protocol issue */
   /* if asynchronous next-request convey this to the JavaScript client */
      free (clptr);
      return;
   }

   /* open the IPC to the WebSocket (mailboxes) */
   status = WsLibOpen (wsptr);
   if (VMSnok(status)) EXIT_FI_LI(status);

   /* no user interaction is two minutes in seconds */
   WsLibSetIdleSecs (wsptr, 2*60);

   len = sprintf (YouString, "you=%u", clptr->YouIdent);
   WsLibWrite (wsptr, YouString, len, WSLIB_ASYNCH);

   /* if maximum granularity then convey this to the JavaScript client */
   if (UpdateMax) WsLibWrite (wsptr, "update=max", 10, WSLIB_ASYNCH);

   /* if asynchronous next-request convey this to the JavaScript client */
   if (OnNext) WsLibWrite (wsptr, "OnNext", 6, WSLIB_ASYNCH);

   ConnectedCount++;

   WsLibRead (wsptr, clptr->InputBuffer,
              sizeof(clptr->InputBuffer),
              UpdateClient);
}

/*****************************************************************************/
/*
Destory the wsLIB structure and free the client structure memory.
*/

void RemoveClient (struct WsLibStruct *wsptr)

{
   struct MouseClient  *clptr;

   /*********/
   /* begin */
   /*********/

   clptr = WsLibGetUserData (wsptr);

   free (clptr);

   if (ConnectedCount) ConnectedCount--;
}

/*****************************************************************************/
/*
Read from WebSocket client has concluded.  These are the mouse coordinates. 
Ensure they look like they should and store them.  Update all (other) clients
with this change.
*/

void UpdateClient (struct WsLibStruct *wsptr)

{
   static unsigned long  OneTenthDelta [2] = { -1000000, -1 };

   int  mx, my, status;
   struct MouseClient  *clptr;

   /*********/
   /* begin */
   /*********/

   if (!UpdateFunction)
   {
      UpdateFunction = UpdateAllClients;
      status = sys$setimr (0, &OneTenthDelta, UpdateFunction, 1, 0);
      if (VMSnok(status)) EXIT_FI_LI (status);
   }

   if (VMSnok (WsLibReadStatus(wsptr)))
   {
      /* WEBSOCKET_INPUT read error (can be EOF) */
      WsLibClose (wsptr, 0, NULL);
      return;
   }

   clptr = WsLibGetUserData(wsptr);

   clptr->UpdateCount++;

   if (clptr->InputBuffer[0])
   {
      sscanf (clptr->InputBuffer, "%d,%d", &mx, &my);
      sprintf (clptr->MouseCoord, "%d,%d", mx, my);
   }
   else
      clptr->MouseCoord[0] = '\0';

   WsLibRead (wsptr,
              clptr->InputBuffer,
              sizeof(clptr->InputBuffer),
              UpdateClient);
}

/*****************************************************************************/
/*
Build a string buffer with the details of all connected clients.  Write this
buffer to all connected clients simultaneously updating all with the new
details.  No UTF-8 conversion is required because the text is all 7 bit ASCII.
By default the update rate is explicitly limited to 100mS.
*/

void UpdateAllClients ()

{
   static char  UpdateBuffer [2048];

   int  cnt = 0,
        status;
   char  *sptr, *zptr;
   struct WsLibStruct  *wsptr;
   struct MouseClient  *clptr;

   /*********/
   /* begin */
   /*********/

   UpdateFunction = NULL;

   zptr = (sptr = UpdateBuffer) + sizeof(UpdateBuffer)-128;

   for (wsptr = NULL; WsLibNext(&wsptr);)
   {
      clptr = WsLibGetUserData(wsptr);
      sptr += sprintf (sptr, "%u|%d|%s|%s|%d|%s\n",
                       clptr->YouIdent, ++cnt, clptr->ConnectTime,
                       clptr->RemoteHostPort, clptr->UpdateCount,
                       clptr->MouseCoord);
      if (sptr > zptr) break;
   }

   if (sptr > zptr) sptr += sprintf (sptr, "[overflow]||||||");
   *sptr = '\0';

   for (wsptr = NULL; WsLibNext(&wsptr);)
      WsLibWrite (wsptr, UpdateBuffer, sptr-UpdateBuffer, WSLIB_ASYNCH);
}

/*****************************************************************************/
/*
Get the remote host name/address and port number (blocking, IPv4 only).
*/

void RemoteHostPort (struct MouseClient *clptr)

{
   int  IpAddr;
   struct hostent  *HostEntryPtr;

   /*********/
   /* begin */
   /*********/

   /* if the server has host name resolution enabled */
   strcpy (clptr->RemoteHostPort, WsLibCgiVar("REMOTE_HOST"));
   /* otherwise resolve it ourselves */
   if (!strcmp (clptr->RemoteHostPort, WsLibCgiVar("REMOTE_ADDR")))
      if (!getenv ("WS_MOUSE_NO_LOOKUP"))
         if ((IpAddr = inet_addr (clptr->RemoteHostPort)) != -1)
            if (HostEntryPtr = gethostbyaddr (&IpAddr, 4, AF_INET))
               strcpy (clptr->RemoteHostPort, HostEntryPtr->h_name);
   strcat (clptr->RemoteHostPort, ":");
   strcat (clptr->RemoteHostPort, WsLibCgiVar("REMOTE_PORT"));
}

/*****************************************************************************/

