//---------------------------------------------------------------------------
//
// MODULE NAME:     main.cpp
//
// COPYRIGHT:       Copyright (c) 2013, GladeSoft, Inc.
//                  All Rights Reserved.
//
//                  NOTICE: THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF
//                  GLADESOFT, INC., AND SHALL NOT BE REPRODUCED, COPIED
//                  IN WHOLE OR IN PART ADAPTED, MODIFIED, OR
//                  DISSEMINATED WITHOUT THE EXPRESS WRITTEN CONSENT
//                  OF GLADESOFT, INC. ANY REDISTRIBUTION OR
//                  PUBLICATION TO UNAUTHORIZED PERSONS IS STRICTLY
//                  PROHIBITED BY U.S. AND INTERNATIONAL COPYRIGHT LAW.
//
// DESCRIPTION:     An example of the Seminole WebSockets library.
//
// AUTHOR:          M. Grosberg
//
// CREATION DATE:   Oct 14, 2013
//
//---------------------------------------------------------------------------
//
// HISTORY OF CHANGE  
//
// -----+--------+------------------------------------------+------+---------
// VERS | DATE   | DESCRIPTION OF CHANGE                    | INIT | PR#
// =====+========+==========================================+======+=========
//  001 |10/14/13| File creation.                           | MYG  |
// -----+--------+------------------------------------------+------+---------
// -----+--------+------------------------------------------+------+---------
//---------------------------------------------------------------------------

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include "seminole.h"
#include "sem_websockets.h"

#include "sitedata.h"

#if !HTTPD_HAVE_SOCK_WAIT
# error This example requires a platform that provides socket multiplexing.
#endif

#if defined(_POSIX_VERSION)

struct WaitSignaler
{
  int   mPipe[2];

  HttpdSocketWaitHandle Handle() const { return (mPipe[0]); }
  static bool MakeSignaler(WaitSignaler &ws);
  void Signal();
  void Clear();

  WaitSignaler() { mPipe[0] = -1; }
  ~WaitSignaler();
};

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::~WaitSignaler (POSIX version)
//
// ACCESS:         public
//
// DESCRIPTION:    Destroy a signaler object if it was created.
//
// RETURNS:        Success indicator.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
WaitSignaler::~WaitSignaler()
{ 
  if (mPipe[0] != -1)
  {
    close(mPipe[0]);
    close(mPipe[1]);
  }
}

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::MakeSignaler (POSIX version)
//
// ACCESS:         public
//
// DESCRIPTION:    Make a signaling object.
//
// RETURNS:        Success indicator.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
bool WaitSignaler::MakeSignaler(WaitSignaler &ws)
{
  if (pipe(ws.mPipe) != 0)
    return (false);

  return (true);
}

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::Signal (POSIX version)
//
// ACCESS:         public
//
// DESCRIPTION:    Signal the object.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
void WaitSignaler::Signal()
{
  fd_set           wset;
  int              wfd = mPipe[1];
  struct timeval   tv  = { 0, 0 };

  FD_ZERO(&wset);
  FD_SET(wfd, &wset);
  if (select(wfd + 1, NULL, &wset, NULL, &tv) >= 1)
    write(wfd, "", 1);
}

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::Clear (POSIX version)
//
// ACCESS:         public
//
// DESCRIPTION:    Unsignal a previous signal.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
void WaitSignaler::Clear()
{
  fd_set           rset;
  int              rfd = mPipe[0];
  struct timeval   tv  = { 0, 0 };
  char             byte;

  FD_ZERO(&rset);
  FD_SET(rfd, &rset);
  if (select(rfd + 1, &rset, NULL, NULL, &tv) >= 1)
    read(rfd, &byte, 1);
}

#elif defined(WIN32)

struct WaitSignaler
{
  WSAEVENT          mEvent;
  unsigned int      mSemaphore;
  CRITICAL_SECTION  mLock;

  HttpdSocketWaitHandle Handle() const { return (mEvent); }
  static bool MakeSignaler(WaitSignaler &ws);

  void Signal();
  void Clear();

  WaitSignaler() { mEvent = WSA_INVALID_EVENT; }
  ~WaitSignaler();
};

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::MakeSignaler (Win32 version)
//
// ACCESS:         public
//
// DESCRIPTION:    Make a signaling object.
//
// RETURNS:        Success indicator.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
bool WaitSignaler::MakeSignaler(WaitSignaler &ws)
{
  InitializeCriticalSection(&ws.mLock);
  ws.mSemaphore = 0;
  ws.mEvent = WSACreateEvent();
  if (ws.mEvent != WSA_INVALID_EVENT)
    return (true);
  else
  {
    DeleteCriticalSection(&ws.mLock);
    return (false);
  }
}

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::~WaitSignaler (Win32 version)
//
// ACCESS:         public
//
// DESCRIPTION:    Destroy a signaler object if it was created.
//
// RETURNS:        Success indicator.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
WaitSignaler::~WaitSignaler()
{
  if (mEvent != WSA_INVALID_EVENT)
  {
    WSACloseEvent(mEvent);
    DeleteCriticalSection(&mLock);
  }
}

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::Signal (Win32 version)
//
// ACCESS:         public
//
// DESCRIPTION:    Signal the object.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
void WaitSignaler::Signal()
{
  EnterCriticalSection(&mLock);

  if (mSemaphore++ == 0)
    WSASetEvent(mEvent);

  LeaveCriticalSection(&mLock);
}

//---------------------------------------------------------------------------
//
// NAME:           WaitSignaler::Clear (Win32 version)
//
// ACCESS:         public
//
// DESCRIPTION:    Unsignal a previous signal.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
void WaitSignaler::Clear()
{
  EnterCriticalSection(&mLock);

  if (mSemaphore > 0)
  {
    if (--mSemaphore == 0)
      WSAResetEvent(mEvent);
  }

  LeaveCriticalSection(&mLock);
}

#else
# error Unknown platform type -- this example is non-portable.
#endif

#define NUM_SENSORS 8

struct ActiveConnection
{
  HttpdListNode     mNode;
  WaitSignaler      mSignaler;
  int               mSensorValues[NUM_SENSORS];

  ActiveConnection(bool &ok);
  ~ActiveConnection();
};

static const char   gServername[] = "localhost";
static const int    gServerport   = 8080;
static Httpd       *gpWebServer;
static HttpdMutex   gSensorMutex;
static HttpdList    gClientList;

//---------------------------------------------------------------------------
//
// NAME:           ActiveConnection::ActiveConnection
//
// ACCESS:         public
//
// DESCRIPTION:    Construct a connection management object.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
ActiveConnection::ActiveConnection(bool &ok)
{
  for(size_t i = 0; i < HTTPD_NUMELEM(mSensorValues); i++)
    mSensorValues[i] = 50;

  if (WaitSignaler::MakeSignaler(mSignaler))
  {
    mNode.Owner(this);
    gSensorMutex.Lock();
    gClientList.AddToHead(&mNode);
    gSensorMutex.Unlock();

    ok = true;
  }
  else
  {
    ok = false;
    mNode.Owner(NULL);
  }
}

//---------------------------------------------------------------------------
//
// NAME:           ActiveConnection::~ActiveConnection
//
// ACCESS:         public
//
// DESCRIPTION:    Destroy a connection management object.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
ActiveConnection::~ActiveConnection()
{
  if (mNode.Owner() != NULL)
  {
    gSensorMutex.Lock();
    mNode.Remove();
    gSensorMutex.Unlock();
  }
}

class WebSocketsHandler : public HttpdHandler
{
public:
  WebSocketsHandler() { mpPrefix = "/sensordata"; }

protected:
  bool Handle(HttpdRequest *p_req);
};

//---------------------------------------------------------------------------
//
// NAME:           WebSocketsHandler::Handle
//
// ACCESS:         protected
//
// DESCRIPTION:    Handle a sensor data request.
//
// RETURNS:        True if the request was for the target URL or false
//                 otherwise.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
bool WebSocketsHandler::Handle(HttpdRequest *p_req)
{
  // Is this request for the socket URL?
  if (IsMe(p_req) == NULL)
    return (false);

  // Try to connect.
  HttpdWebSocket sock;
  if (!HttpdWebSocket::Setup(p_req, sock))
    return (true);

  // N.B. Keep this declared outside the loop so we don't reallocate the
  // buffer for each iteration.
  HttpdStringSink tx_buffer;
  bool ok;
  ActiveConnection conn(ok);

  if (!ok)
  {
    // No resources left to serve this connection -- not much else we can do.
    sock.Close();
    return (true);
  }

  for(;;)
  {
    // To handle PINGs and check the state of the connection block waiting
    // for a message from the client.
    HttpdWebSocket::Message rx_message;
    switch (sock.Received(rx_message, 999999, conn.mSignaler.Handle()))
    {
      case 0:
        sock.Finish(rx_message);
        // Fallthrough:

      case HttpdOpSys::ERR_NOTREADY:
        break;

      default:
        sock.Close();
        return (true);
    }

    for(size_t i = 0; i < NUM_SENSORS; i++)
    {
      if (i > 0)
        tx_buffer.WriteString(" ");

      tx_buffer.Printf("%d", conn.mSensorValues[i]);
    }
   
    HttpdWebSocket::Message update(tx_buffer.String());
    if (sock.Send(update) != 0)
      return (true);

    tx_buffer.Clear();
    conn.mSignaler.Clear();
  }
}

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

//---------------------------------------------------------------------------
//
// NAME:           failure
//
// DESCRIPTION:    Handle a fatal error.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
static void failure(const char *p_file, int line, const char *p_msg)
{
  assert(p_file != NULL);
  assert(line   >  0);
  assert(p_msg  != NULL);
  
  fprintf(stderr, "%s:%d Call to %s failed\n", p_file, line, p_msg);
  exit(EXIT_FAILURE);
}

//---------------------------------------------------------------------------
//
// NAME:           UpdateSensors
//
// DESCRIPTION:    A thread to randomly update the sensor data.
//
// REMARKS:        In the real world the sensor data would come from
//                 actual sensors. But since this is only an example the
//                 data is simply random.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
static void UpdateSensors
 (
   HttpdParameter p1,
   HttpdParameter p2,
   HttpdParameter p3
 )
{
  for(;;)
  {
    HttpdOpSys::TaskSleep(800);

    gSensorMutex.Lock();

    HttpdListIterator walk(gClientList.Head());
    while (walk.Continue())
    {
      ActiveConnection  *p_connection = (ActiveConnection *)(void *)walk;
      walk.Next();

      for(size_t i = 0; i < NUM_SENSORS; i++)
      {
        if (HttpdOpSys::Entropy(10) < 5)
        {
          // Don't let the fake sensors swing wildly. Make them change slowly
          // just like real hardware sensors do.
          int delta = HttpdOpSys::Entropy(18);
          if (HttpdOpSys::Entropy(10) < 5)
            delta = -delta;

          p_connection->mSensorValues[i] += delta;
          if (p_connection->mSensorValues[i] < 0)
            p_connection->mSensorValues[i] = 0;
          else if (p_connection->mSensorValues[i] > 100)
            p_connection->mSensorValues[i] = 100;
        }
      }

      // Wake the socket thread.
      p_connection->mSignaler.Signal();
    }

    gSensorMutex.Unlock();
  }
}

//---------------------------------------------------------------------------
//
// NAME:           Initialize_The_Web_Server
//
// DESCRIPTION:    Start and configure Seminole.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
static void Initialize_The_Web_Server()
{
  
  //--------------------------------------------------------
  // create the webserver instance.  This just initializes
  // the object---it doesn't start it up
  //--------------------------------------------------------

  gpWebServer = new Httpd(gServername, gServerport);
  if (gpWebServer == NULL)
    failure(__FILE__, __LINE__, "Httpd::Httpd()");

  //--------------------------------------------------------
  // Instantiate the request handlers and add them.
  //--------------------------------------------------------

  static WebSocketsHandler wshand;
  gpWebServer->Install(&wshand);

  static HttpdMemoryDataSource builtin(SemWeb, sizeof(SemWeb));
  static HttpdRomFileSystem    romfs;
  if (romfs.Mount(&builtin) != 0)
    failure(__FILE__, __LINE__, "HttpdRomFileSystem::Mount()");
    
  static HttpdFileHandler filhand(&romfs, "/", "/");
  gpWebServer->Install(&filhand);

  printf("Server started on host %s port %d\n", gServername, gServerport);
}

//---------------------------------------------------------------------------
//
// NAME:           Initialize_Daemons
//
// DESCRIPTION:    Start up the background threads.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
static void Initialize_Daemons()
{
  //-------------------------------------------------------
  // initialize the environment.
  //-------------------------------------------------------
  if (Httpd::Init() != 0)
    failure(__FILE__, __LINE__, "Httpd::Init()");

  gClientList.Initialize();

  if (gSensorMutex.Create() != 0)
    failure(__FILE__, __LINE__, "HttpdMutex::Create()");

  HttpdParameter p0;
  p0.mpVoid = NULL;
  if (!HttpdOpSys::Fork(UpdateSensors, p0, p0, p0, HTTPD_PRI_WORKER))
    failure(__FILE__, __LINE__, "HttpdOpSys::Fork()");
}

//---------------------------------------------------------------------------
//
// NAME:           main
//
// DESCRIPTION:    Entry point for the demo.
//
// AUTHOR:         M. Grosberg
// CREATED:        Oct 14, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
int main(int argc, const char *const *argv)
{
  char buf[4];

  Initialize_Daemons();
  Initialize_The_Web_Server();

  gpWebServer->Start();
  
  printf("Press <ENTER> to stop.\n");
  fgets(buf, sizeof(buf), stdin);
  
#if HTTPD_INC_SHUTDOWN
  gpWebServer->Stop(HARD);
#endif

  return (EXIT_SUCCESS);
}
