//---------------------------------------------------------------------------
//
// 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:          A. Hoerter
//
// CREATION DATE:   Oct 12, 2013
//
//---------------------------------------------------------------------------
//
// HISTORY OF CHANGE  
//
// -----+--------+------------------------------------------+------+---------
// VERS | DATE   | DESCRIPTION OF CHANGE                    | INIT | PR#
// =====+========+==========================================+======+=========
//  001 |10/12/13| File creation.                           | AMH  |
// -----+--------+------------------------------------------+------+---------
// -----+--------+------------------------------------------+------+---------
//---------------------------------------------------------------------------

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

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

#include "sitedata.h"

#define NUM_SENSORS 8

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

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:         A. Hoerter
// CREATED:        Oct 12, 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;

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

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

    tx_buffer.Clear();

    // 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, 1))
    {
      case 0:
        sock.Finish(rx_message);
        // Fallthrough:

      case HttpdOpSys::ERR_NOTREADY:
        continue;

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

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

//---------------------------------------------------------------------------
//
// NAME:           failure
//
// DESCRIPTION:    Handle a fatal error.
//
// AUTHOR:         A. Hoerter
// CREATED:        Oct 12, 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:         A. Hoerter
// CREATED:        Oct 12, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
static void UpdateSensors
 (
   HttpdParameter p1,
   HttpdParameter p2,
   HttpdParameter p3
 )
{
  for(;;)
  {
    HttpdOpSys::TaskSleep(800);

    gSensorMutex.Lock();
    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;

        gSensorValues[i] += delta;
        if (gSensorValues[i] < 0)
          gSensorValues[i] = 0;
        else if (gSensorValues[i] > 100)
          gSensorValues[i] = 100;
      }
    }
    gSensorMutex.Unlock();
  }
}

//---------------------------------------------------------------------------
//
// NAME:           Initialize_The_Web_Server
//
// DESCRIPTION:    Start and configure Seminole.
//
// AUTHOR:         A. Hoerter
// CREATED:        Oct 12, 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:         A. Hoerter
// CREATED:        Oct 12, 2013
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
static void Initialize_Daemons()
{
  // Initialize the environment.
  if (Httpd::Init() != 0)
    failure(__FILE__, __LINE__, "Httpd::Init()");

  // Initialize all the sensors to the halfway point.
  for(size_t i = 0; i < HTTPD_NUMELEM(gSensorValues); i++)
    gSensorValues[i] = 50;

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

  // Spawn the update thread.
  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:         A. Hoerter
// CREATED:        Oct 12, 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);
}
