//---------------------------------------------------------------------------
//
// 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:     Another example program using the Seminole embedded 
//                  web server.
//
// AUTHOR:          S. Conner
//
// CREATION DATE:   Mar  5, 2004
//
//---------------------------------------------------------------------------
//
// HISTORY OF CHANGE  
//
// -----+--------+------------------------------------------+------+---------
// VERS | DATE   | DESCRIPTION OF CHANGE                    | INIT | PR#
// =====+========+==========================================+======+=========
//  001 |03/05/04| File creation.                           | SPC  |
// -----+--------+------------------------------------------+------+---------
//  002 |06/12/09| Const correctness.                       | RAH  |
// -----+--------+------------------------------------------+------+---------
//---------------------------------------------------------------------------

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

#include "seminole.h"

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

class DemoSessionHandler : public HttpdHandler
{
  HttpdSessionManager  mSessions;

  void Output(HttpdRequest *p_req, const char *p_text,
              const char *p_ses_id = NULL);
  void ExistingSession(HttpdRequest *p_req, const char *p_id);
  void NewSession(HttpdRequest *p_req);

public:
  DemoSessionHandler(const char *p_prefix);
  virtual ~DemoSessionHandler();
  int Create();
 
  virtual bool Handle(HttpdRequest *p_req);
};

class DemoSessionObject : public HttpdSessionObject
{
public:
  unsigned int  mVisitCount;
};

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

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

//---------------------------------------------------------------------------
//
// NAME:           DemoSessionHandler::DemoSessionHandler
//
// DESCRIPTION:    Construct the demo session handler.
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
DemoSessionHandler::DemoSessionHandler(const char *p_prefix)
{
  mpPrefix = HttpdUtilities::SaveString(p_prefix);
}

//---------------------------------------------------------------------------
//
// NAME:           DemoSessionHandler::~DemoSessionHandler
//
// DESCRIPTION:    Destroy the demo session handler.
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
DemoSessionHandler::~DemoSessionHandler()
{
  HttpdOpSys::Free((char *)mpPrefix);
}

//---------------------------------------------------------------------------
//
// NAME:           DemoSessionHandler::Create
//
// DESCRIPTION:    Perform the heavyweight object construction of the
//                 session handler (by initializing the session manager).
//
// RETURNS:        Status code.
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
int DemoSessionHandler::Create()
{
#if HTTPD_INC_BACKGROUND_SESSION_PURGE
  mSessions.MaxSessionAge(24UL * 60 * 60);
  mSessions.ScrubbingBatchSize(5);
  mSessions.CycleTime(10000UL);
#endif

  return (mSessions.Create(64));
}

//---------------------------------------------------------------------------
//
// NAME:           DemoSessionHandler::Output
//
// DESCRIPTION:    Generate the response body.
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
void DemoSessionHandler::Output
 (
   HttpdRequest   *p_req,
   const char     *p_text,
   const char     *p_ses_id
 )
{
  bool is_head = p_req->IsHeadRequest();

  HttpdDynamicOutput output(p_req, is_head);
  p_req->Respond(HTTPD_RESP_OK);
  output.Header("Content-Type", "text/html");

  if (p_ses_id != NULL)
    HttpdCookies::SendCookie(&output,
                             "session",
                             p_ses_id,
                             "path",
                             Prefix(),
                             (const char *)0);
  output.HeaderComplete();

  output.Body()->Printf("<html>\n"
                        "<head>\n"
                        "  <title>Session &amp; Cookie Demo</title>\n"
                        "</head>\n"
                        "<body>\n"
                        "  %s\n"
                        "</body>\n"
                        "</html>\n"
                        "\n",
                        p_text); 
}

//---------------------------------------------------------------------------
//
// NAME:           DemoSessionHandler::ExistingSession
//
// DESCRIPTION:    Handle the case where the request has already allocated
//                 a session.. 
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
void DemoSessionHandler::ExistingSession
 (
   HttpdRequest  *p_req,
   const char    *p_id
 )
{
  // Find the session.
  HttpdSessionObject *p_so;
  if (mSessions.Find(p_id, p_so) != 0)
  {
    NewSession(p_req);
    return;
  }

  DemoSessionObject *p_session = (DemoSessionObject *)p_so;

  char  buffer[128];
  sprintf(buffer,
          "Welcome back. You have visited %u times so far.",  
          p_session->mVisitCount++);
  mSessions.Unlock(p_so);
  Output(p_req, buffer);
}

//---------------------------------------------------------------------------
//
// NAME:           DemoSessionHandler::NewSession
//
// DESCRIPTION:    Handle the case where the request has not already 
//                 been allocated a session.. 
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
void DemoSessionHandler::NewSession(HttpdRequest *p_req)
{
  //-----------------------------------------------------------------
  // Create a new session object on the heap. When the session is
  // expired the session manager will automatically free the object.
  //-----------------------------------------------------------------

  void *p_buffer = HttpdOpSys::Malloc(sizeof(DemoSessionObject));
  if (p_buffer == NULL)
  {
    p_req->Respond(HTTPD_RESP_SRV_ERROR);
    return;
  }
  DemoSessionObject *p_obj = new(p_buffer) DemoSessionObject;

  //-----------------------------------------------------------------
  // Initialize the session and insert it into the manager.
  //-----------------------------------------------------------------

  p_obj->mVisitCount = 1;
  if (mSessions.Insert(p_obj) != 0)
  {
    delete p_obj;
    p_req->Respond(HTTPD_RESP_SRV_ERROR);
    return;
  }

  // Get the session ID so it can be sent as a cookie.
  char ses_id[HTTPD_SESSION_KEY_LEN];
  p_obj->SessionId(ses_id);
  mSessions.Unlock(p_obj);

  // Send the first page.
  Output(p_req, "Welcome new visitor! I'll count your visits.", ses_id);
}

//---------------------------------------------------------------------------
//
// NAME:           DemoCgiHandler::Handle
//
// DESCRIPTION:    Process the request.
//
// RETURNS:        True if the request was handled by this handler,
//                 false if no processing has been performed.
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
bool DemoSessionHandler::Handle(HttpdRequest *p_req)
{
  assert(p_req != NULL);
  
  if (IsMe(p_req))
  {
    HttpdCookies  cookie_jar(p_req->Headers());

    while (cookie_jar.NextCookie())
    {
      if (strcmp(cookie_jar.Key(), "session") == 0)
      {
        ExistingSession(p_req, cookie_jar.Value());
        return (true);
      }
    }

    NewSession(p_req);
    return (true);
  }

  return (false);
}

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

//---------------------------------------------------------------------------
//
// NAME:           failure
//
// DESCRIPTION:    Handle a fatal error.
//
// AUTHOR:         S. Conner
// CREATED:        Mar  6, 2004
// 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:           Initialize_The_Web_Server
//
// DESCRIPTION:    Start and configure Seminole.
//
// AUTHOR:         M. Grosberg
// CREATED:        May 25, 2006
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
static void Initialize_The_Web_Server()
{
  //-------------------------------------------------------
  // Initialize the environment.
  //-------------------------------------------------------
  
  if (Httpd::Init() != 0)
    failure(__FILE__, __LINE__, "Httpd::Init()");
  
  //--------------------------------------------------------
  // 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()");

  //---------------------------------------------------------
  // Install the Session handler as the root.
  //---------------------------------------------------------
  
  static DemoSessionHandler hand("/");
  if (hand.Create() != 0)
    failure(__FILE__, __LINE__, "DemoSessionHandler::Create()");

  gpWebServer->Install(&hand);

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

//---------------------------------------------------------------------------
//
// NAME:           main
//
// DESCRIPTION:    Entry point for the demo.
//
// AUTHOR:         S. Conner
// CREATED:        Mar  6, 2004
// LAST MODIFIED:  N/A           BY:  N/A
//
//--------------------------------------------------------------------------
int main(int argc, const char *const *argv)
{
  char buf[4];

  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);
}
